Library Structures
Library Structures (라이브러리 구조)
대략적으로 말하면, 선언 파일을 어떻게 구조화 하느냐는 그 라이브러리가 어떻게 사용되는지에 달려 있어요. JavaScript에서 라이브러리를 제공하는 방식은 다양하고, 그에 맞춰 선언 파일도 작성해야 하죠. 이 가이드는 흔한 라이브러리 패턴을 식별하는 방법과, 각 패턴에 대응하는 선언 파일을 작성하는 방법을 다룹니다.
주요 라이브러리 구조 패턴마다 Templates 섹션에 대응하는 파일이 있어요. 이 템플릿들로 시작하면 훨씬 빨리 진행할 수 있습니다.
출처: TypeScript 핸드북
라이브러리 종류 식별하기
먼저 TypeScript 선언 파일이 표현할 수 있는 라이브러리의 종류를 살펴봐요. 각 종류의 라이브러리가 어떻게 사용 되고 어떻게 작성 되는지 간단히 보여주고, 실제 세상의 예시 라이브러리 몇 개를 나열할게요.
라이브러리의 구조를 식별하는 것은 선언 파일을 작성하는 첫 단계예요. 구조를 어떻게 식별하는지 사용법 과 코드 두 측면에서 힌트를 알려드릴게요. 라이브러리의 문서와 구성에 따라 어느 쪽이 더 쉬울 수 있어요. 자기한테 더 편한 쪽을 쓰면 됩니다.
무엇을 찾아봐야 할까?
타이핑하려는 라이브러리를 보면서 스스로에게 물어볼 질문이에요.
-
라이브러리를 어떻게 얻는가?
예를 들어 npm으로만 얻을 수 있나요, 아니면 CDN에서만 얻을 수 있나요?
-
어떻게 import 할까?
전역 객체를 추가하나요?
require나import/export문을 사용하나요?
라이브러리 종류별 작은 예시
모듈 라이브러리 (Modular Libraries)
거의 모든 현대 Node.js 라이브러리는 모듈 계열에 속해요. 이런 타입의 라이브러리는 모듈 로더가 있는 JS 환경에서만 동작합니다. 예를 들어 express는 Node.js에서만 동작하고 CommonJS의 require 함수로 로드되어야 해요.
ECMAScript 2015(ES2015, ECMAScript 6, ES6라고도 함), CommonJS, RequireJS는 모듈 을 가져오는 비슷한 개념을 가지고 있어요. 예를 들어 JavaScript CommonJS(Node.js)에서는 이렇게 작성합니다.
var fs = require("fs");
TypeScript나 ES6에서는 import 키워드가 같은 목적을 수행해요.
import * as fs from "fs";
모듈 라이브러리 문서에는 보통 다음 줄 중 하나가 포함되어 있을 거예요.
var someLib = require("someLib");
또는
define(..., ['someLib'], function(someLib) {
});
전역 모듈과 마찬가지로, 이런 예시는 UMD 모듈 문서에서도 볼 수 있으니 코드나 문서를 꼭 확인하세요.
코드에서 모듈 라이브러리 식별하기
모듈 라이브러리는 보통 다음 중 적어도 일부를 갖고 있어요.
- 무조건적인(무건부)
require또는define호출 import * as a from 'b';또는export c;같은 선언exports나module.exports에 대한 할당
다음은 거의 갖고 있지 않아요.
window나global의 속성에 대한 할당
모듈용 템플릿
모듈에는 네 가지 템플릿이 있어요. module.d.ts, module-class.d.ts, module-function.d.ts, module-plugin.d.ts가 그것이에요.
먼저 module.d.ts를 읽어서 모두 어떻게 동작하는지 개요를 잡아 보세요.
모듈이 함수처럼 _호출_될 수 있다면 템플릿 module-function.d.ts를 사용하세요.
const x = require("foo");
// Note: calling 'x' as a function
const y = x(42);
모듈이 new로 구성(construct) 될 수 있다면 템플릿 module-class.d.ts를 사용하세요.
const x = require("bar");
// Note: using 'new' operator on the imported variable
const y = new x("hello");
import했을 때 다른 모듈을 변경시키는 모듈이라면 템플릿 module-plugin.d.ts를 사용하세요.
const jest = require("jest");
require("jest-matchers-files");
전역 라이브러리 (Global Libraries)
전역 라이브러리는 어떤 형태의 import도 사용하지 않고 전역 스코프에서 접근할 수 있는 라이브러리예요. 많은 라이브러리가 사용을 위해 하나 이상의 전역 변수를 그냥 노출합니다. 예를 들어 jQuery를 쓴다면 $ 변수를 그냥 참조해서 사용할 수 있죠.
$(() => {
console.log("hello!");
});
전역 라이브러리 문서에서는 HTML script 태그에서 라이브러리를 사용하는 방법을 안내하는 걸 보통 볼 수 있어요.
<script src="http://a.great.cdn.for/someLib.js"></script>
오늘날 가장 인기 있는 전역 접근 라이브러리들은 사실 UMD 라이브러리로 작성되어 있어요(아래 참조). UMD 라이브러리 문서는 전역 라이브러리 문서와 구분하기 어렵습니다. 전역 선언 파일을 작성하기 전에 그 라이브러리가 사실은 UMD가 아닌지 꼭 확인하세요.
코드에서 전역 라이브러리 식별하기
전역 라이브러리 코드는 보통 아주 단순해요. 전역 "Hello, world" 라이브러리는 이렇게 생겼을 거예요.
function createGreeting(s) {
return "Hello, " + s;
}
또는 이렇게요.
// Web
window.createGreeting = function (s) {
return "Hello, " + s;
};
// Node
global.createGreeting = function (s) {
return "Hello, " + s;
};
// Potentially any runtime
globalThis.createGreeting = function (s) {
return "Hello, " + s;
};
전역 라이브러리의 코드를 보면 보통 다음을 볼 수 있어요.
- 최상위
var문 또는function선언 window.someName에 대한 하나 이상의 할당document나window같은 DOM 기본 요소가 존재한다는 가정
다음은 보이지 않을 거예요.
require나define같은 모듈 로더의 검사 또는 사용var fs = require("fs");형태의 CommonJS/Node.js 방식 importdefine(...)호출- 라이브러리를
require하거나 import하는 방법을 설명하는 문서
전역 라이브러리의 예시
전역 라이브러리를 UMD 라이브러리로 바꾸는 게 보통 쉬워서, 여전히 전역 스타일로 작성된 인기 라이브러리는 거의 없어요. 하지만 작고 DOM이 필요하거나(혹은 의존성이 없는) 라이브러리는 여전히 전역일 수 있습니다.
전역 라이브러리 템플릿
템플릿 파일 global.d.ts는 예시 라이브러리 myLib를 정의해요. "이름 충돌 방지" 각주를 꼭 읽어 보세요.
UMD
UMD 모듈은 모듈(import를 통해서)로 또는 전역(모듈 로더가 없는 환경에서 실행할 때)으로 사용될 수 있는 모듈이에요. Moment.js 같은 많은 인기 라이브러리가 이렇게 작성되어 있어요. 예를 들어 Node.js나 RequireJS에서는 이렇게 작성합니다.
import moment = require("moment");
console.log(moment.format());
반면 순수 브라우저 환경에서는 이렇게 작성해요.
console.log(moment.format());
UMD 라이브러리 식별하기
UMD 모듈은 모듈 로더 환경의 존재 여부를 확인합니다. 이건 쉽게 알아볼 수 있는 패턴으로, 대략 이렇게 생겼어요.
(function (root, factory) {
if (typeof define === "function" && define.amd) {
define(["libName"], factory);
} else if (typeof module === "object" && module.exports) {
module.exports = factory(require("libName"));
} else {
root.returnExports = factory(root.libName);
}
}(this, function (b) {
라이브러리 코드에서 typeof define, typeof window, typeof module에 대한 검사를 본다면, 특히 파일 상단에 있다면 거의 틀림없이 UMD 라이브러리예요.
UMD 라이브러리 문서는 보통 require를 보여주는 "Node.js에서 사용하기" 예시와, <script> 태그로 스크립트를 로드하는 "브라우저에서 사용하기" 예시를 함께 보여줍니다.
UMD 라이브러리의 예시
대부분의 인기 라이브러리는 이제 UMD 패키지로 제공돼요. jQuery, Moment.js, lodash 등이 그 예시입니다.
템플릿
module-plugin.d.ts 템플릿을 사용하세요.
의존성 사용하기 (Consuming Dependencies)
여러분의 라이브러리가 가질 수 있는 의존성에는 여러 종류가 있어요. 이 섹션에서는 그것들을 선언 파일에 어떻게 import하는지 보여줍니다.
전역 라이브러리에 대한 의존성
라이브러리가 전역 라이브러리에 의존한다면 /// <reference types="..." /> 지시문을 사용하세요.
/// <reference types="someLib" />
function getThing(): someLib.thing;
모듈에 대한 의존성
라이브러리가 모듈에 의존한다면 import 문을 사용하세요.
import * as moment from "moment";
function getThing(): moment;
UMD 라이브러리에 대한 의존성
전역 라이브러리에서
전역 라이브러리가 UMD 모듈에 의존한다면 /// <reference types 지시문을 사용하세요.
/// <reference types="moment" />
function getThing(): moment;
모듈 또는 UMD 라이브러리에서
모듈이나 UMD 라이브러리가 UMD 라이브러리에 의존한다면 import 문을 사용하세요.
import * as someLib from "someLib";
UMD 라이브러리에 대한 의존성을 선언하기 위해 /// <reference 지시문을 사용하지 마세요!
각주 (Footnotes)
이름 충돌 방지 (Preventing Name Conflicts)
전역 선언 파일을 작성할 때 전역 스코프에 많은 타입을 정의할 수 있다는 점을 주목하세요. 프로젝트에 선언 파일이 여러 개 있을 때 해결할 수 없는 이름 충돌이 생길 수 있기 때문에, 우리는 이걸 강하게 권장하지 않습니다.
따르기 쉬운 규칙 하나는, 라이브러리가 정의하는 전역 변수에 네임스페이스화된 타입만 선언하라는 거예요. 예를 들어 라이브러리가 전역 값 'cats'를 정의한다면 이렇게 작성해야 합니다.
declare namespace cats {
interface KittySettings {}
}
하지만 이렇게 작성하지 마세요.
// at top-level
interface CatsKittySettings {}
이 안내는 또한 라이브러리가 선언 파일 사용자를 망가뜨리지 않고 UMD로 전환될 수 있게 보장해 줍니다.
ES6가 모듈 호출 시그니처에 미치는 영향
Express 같은 많은 인기 라이브러리는 import하면 호출 가능한 함수로 자신을 노출해요. 예를 들어 전형적인 Express 사용은 이렇게 생겼습니다.
import exp = require("express");
var app = exp();
ES6 호환 모듈 로더에서는 최상위 객체(여기서는 exp로 import된)는 속성만 가질 수 있어요. 최상위 모듈 객체는 절대 호출 가능할 수 없습니다.
여기서 가장 흔한 해결책은 호출 가능/구성 가능한 객체에 default export를 정의하는 거예요. 모듈 로더는 보통 이 상황을 자동으로 감지하고 최상위 객체를 default export로 대체합니다. tsconfig.json에 "esModuleInterop": true를 설정해 두면 TypeScript가 이걸 처리해 줍니다.