모듈: 플러그인

모듈: 플러그인 (Module: Plugin)

다른 라이브러리를 확장하는 JavaScript 코드를 다뤄야 할 때가 있어요. 예를 들어 어떤 모듈을 가져다 쓰다가, 그 모듈에 없는 기능을 추가로 붙이고 싶은 상황이죠. 이런 경우에 이 모듈 플러그인 템플릿이 필요합니다.

출처: TypeScript 공식문서

본문

바로 예시부터 볼게요. super-greeter라는 모듈을 가져와서 쓰고 있는 중이라고 해볼게요.

import { greeter } from "super-greeter";

// Normal Greeter API
greeter(2);
greeter("Hello world");

// Now we extend the object with a new function at runtime
import "hyper-super-greeter";
greeter.hyperGreet();

여기서 super-greeter라는 모듈의 정의는 이렇게 생겼어요. 함수에 오버로드가 여럿 붙어 있는 모습이 보이죠.

/*~ This example shows how to have multiple overloads for your function */
export interface GreeterFunction {
  (name: string): void
  (time: number): void
}

/*~ This example shows how to export a function specified by an interface */
export const greeter: GreeterFunction;

이제 기존 모듈을 플러그인 방식으로 확장해 볼게요. 핵심은 import로 원래 모듈을 가져온 다음, 같은 이름의 모듈을 다시 export module로 선언해서 그 안에서 타입을 덧붙이는 거예요.

// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]
// Project: [~THE PROJECT NAME~]
// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>

/*~ This is the module plugin template file. You should rename it to index.d.ts
 *~ and place it in a folder with the same name as the module.
 *~ For example, if you were writing a file for "super-greeter", this
 *~ file should be 'super-greeter/index.d.ts'
 */

/*~ On this line, import the module which this module adds to */
import { greeter } from "super-greeter";

/*~ Here, declare the same module as the one you imported above
 *~ then we expand the existing declaration of the greeter function
 */
export module "super-greeter" {
  export interface GreeterFunction {
    /** Greets even better! */
    hyperGreet(): void;
  }
}

이런 방식으로 기존 선언을 확장하는 걸 선언 병합(declaration merging)이라고 해요.

ES6가 모듈 플러그인에 미치는 영향

일부 플러그인은 기존 모듈의 최상위 export를 추가하거나 수정해요. 이건 CommonJS를 비롯한 여러 로더에서는 문제없이 성립하는 패턴이에요. 하지만 ES6 모듈은 불변(immutable)으로 간주되기 때문에 이 패턴을 쓸 수 없어요. TypeScript는 로더에 무관(loader-agnostic)해서 컴파일 타임에 이 제약을 강제하지는 않아요. 다만 나중에 ES6 모듈 로더로 전환할 생각이 있는 개발자라면 이 부분을 꼭 알아두어야 해요.

더 알아보기