TypeScript - Modules

   

이 포스트는 TypeScript - Modules를 번역 및 학습한 내용입니다.

  
ECMAScript 2015(ES6)에 추가된 모듈 기능을 TypeScript에서도 똑같이 사용 할 수 있다. 모듈은 자신만의 스코프를 가지고 있으며, 모듈 내부에 선언된 변수, 함수, 클래스 등은 export 되지 않는 한 외부에서 접근 할 수 없다.
export된 모듈은 다른 모듈에서 import 키워드를 통해 불러올 수 있다. 이를 가능하게 하는 것은 모듈 로더이다. 모듈 로더는 런타임에 import된 모듈(디펜던시)의 위치를 확인한다. 자바스크립트에서 사용되는 모듈 로더의 종류는 크게 두 가지이다.
  • CommonJS 모듈을 위한 Node.js의 로더
  • AMD 모듈을 위한 RequireJS 로더
  • import, 혹은 export 키워드를 포함한 파일은 모듈로 처리된다. 그 외(import, export 키워드가 없는 파일)는 일반 스크립트(글로벌 스코프를 공유하는)로 처리된다.
     
    Export
    export 키워드를 사용하면, 선언된 모든 식별자(변수, 함수, 클래스, 타입, 인터페이스 등)를 export 할 수 있다.
    // StringValidator.ts
    export interface StringValidator {
      isAcceptable(s: string): boolean;
    }
    // ZipCodeValidator.ts
    import { StringValidator } from './StringValidator';
    
    export const numberRegex = /^[0-9]+$/;
    
    export class ZipCodeValidator implements StringValidator {
      isAcceptable(s: string) {
        return s.length === 5 && numberRegex.test(s);
      }
    }
      
    export 문 작성 시 export 대상의 이름을 변경 할 수 있다. 위 예제를 아래와 같이 작성 할 수 있다.
    // ZipCodeValidator.ts
    import { StringValidator } from './StringValidator';
    
    export const numberRegex = /^[0-9]+$/;
    
    class ZipCodeValidator implements StringValidator {
      isAcceptable(s: string) {
        return s.legnth === 5 && numberRegex.test(s);
      }
    }
    
    // mainValidator로 이름 변경 후 export
    export { ZipCodeValidator as mainValidator };
       
    특정 모듈을 extend하여, 해당 모듈의 일부 기능을 부분적으로 re-export 할 수 있다. 예를 들어, ParseIntBasedZipCodeValidator.ts에서 ZipCodeValidator.ts에 작성된 ZipCodeValidator 클래스를 re-export 할 수 있다. 이때 ZipCodeValidator를 import하지 않는다는 것에 주의해야한다.
    // ParseIntBasedZipCodeValidator.ts
    export class ParseIntBasedZipCodeValidator {
      isAcceptable(s: string) {
        return s.length === 5 && parseInt(s).toString() === s;
      }
    }
    
    // ZipCodeValidator를 rename하여 re-export
    export { ZipCodeValidator as RegExpBasedZipCodeValidator } from "./ZipCodeValidator";
      
    선택적으로, 하나의 모듈에서 여러 모듈을 한꺼번에 export 할 수 있다. 이때 export * from 'module' 문법을 사용한다.
    // AllValidators.ts
    
    // StringValidator 인터페이스 export
    export * from './StringValidator';
    
    // ZipCodeValidator 클래스, numberRegexp 변수 export
    export * from './ZipCodeValidator';
    
    // ParseIntBasedZipCodeValidator 클래스 export 
    // RegExpBasedZipCodeValidator 클래스 export (ZipCodeValidator.ts의 ZipCodeValidator 클래스를 rename하여 re-export)
    export * from "./ParseIntBasedZipCodeValidator";
     
    export * as namespace 문법을 사용하여 export 대상을 네임스페이스로 랩핑하여 re-export 할 수 있다. 이를 적용하여 위 예제를 일부 수정하면 아래와 같다.
    // AllValidators.ts
    
    // ZipCodeValidator 클래스, numberRegexp 변수를 validator 네임스페이스로 래핑하여 export
    export * as validator from './ZipCodeValidator';
      
    Import
    import 키워드를 사용하여 export된 모듈을 로드 할 수 있다.
    import { ZipCodeValidator } from "./ZipCodeValidator";
    
    const myValidator = new ZipCodeValidator();
      
    import 시 모듈의 이름을 rename 할 수 있다. 위의 예제를 아래와 같이 작성 할 수 있다.
    // ZipCodeValidator를 ZCV로 rename
    import { ZipCodeValidator as ZCV } from "./ZipCodeValidator";
    
    const myValidator = new ZCV();
      
    만약 특정 모듈에서 export되는 모든 대상을 하나의 네임스페이스로 import 하고 싶다면 아래와 같이 import 할 수 있다.
    import * as validator from './ZipCodeValidator';
    
    const myVlidator = new validator.ZipCodeValidator();
      
    일부 모듈은 side-effect만을 위해 사용된다 (ex. polyfill, core-js 등). 이러한 모듈은 export 문을 포함하지 않을 수도 있고, 혹은 해당 모듈을 사용하는 입장에서 무엇이 export되는지 알 필요가 없을 수도 있다. 이러한 모듈은 아래와 같이 import 한다. (좋은 방법은 아니다.)
    import './my-module.js';
      
    타입스크립트에서 type을 import하기 위해서는 import type 문법을 사용했다. 하지만 3.8버전부터 import 키워드로 type을 import 할 수 있다.
    // import 키워드 사용
    import { APIResponseType } from "./api";
    
    // import type 사용
    import type { APIResponseType } from "./api";
    import type 문은 컴파일 시 제거된다는 것이 보장된다.
       
    Default exports
    모듈은 선택적으로 default export 할 수 있다. Default export는 default 키워드를 사용하며, 하나의 모듈에서 한 번만 사용 가능하다. default export된 모듈을 import 할 때는 이전에 사용했던 문법과 다른 문법을 사용한다.
    // JQuery.d.ts
    declare let $: JQuery;
    export default $;
    // App.ts
    import $ from 'jquery';
    
    // 꼭 같은 이름으로 import 할 필요는 없다. 원하는 이름으로 import 할 수 있다.
    // import jquery from 'jquery';
    
    $("button.continue").html("Next Step...");
       
    클래스 혹은 함수 선언 시 default 키워드를 바로 사용 할 수 있다. 이때 클래스 혹은 함수 이름 작성을 생략할 수 있다.
    // ZipCodeValidator.ts
    
    // with name
    export default class ZipCodeValidator {
      static numberRegexp = /^[0-9]+$/;
      isAcceptable(s: string) {
        return s.length === 5 && ZipCodeValidator.numberRegexp.test(s);
      }
    }
    // ZipCodeValidator.ts
    
    // without name
    export default class {
      static numberRegexp = /^[0-9]+$/;
      isAcceptable(s: string) {
        return s.length === 5 && ZipCodeValidator.numberRegexp.test(s);
      }
    }
    // Tests.ts
    import validator from "./ZipCodeValidator";
    
    let myValidator = new validator();
      
    함수, 클래스 뿐만 아닌 자바스크립트에서 값으로 평가되는 모든 것은 default export 할 수 있다.
    // OneTwoThree.ts
    export default '123';
    // Log.ts
    import num from "./OneTwoThree";
    
    console.log(num); // "123"
       
    export =, import = require()
    타입스크립트는 CommonJS와 AMD를 모두 사용 할 수 있도록 export = 문법을 지원한다. export = 문법 사용 시 하나의 객체만을 export 할 수 있다. 이때 export 대상은 클래스, 인터페이스, 네임스페이스, 함수, 혹은 열거형(Enum)이 될 수 있다.
    타입스크립트에서 export = 문법을 사용하여 export된 모듈을 import 할 때 import module = require("module") 문법을 사용해야한다.
    // ZipCodeValidator.ts
    let numberRegexp = /^[0-9]+$/;
    
    class ZipCodeValidator {
      isAcceptable(s: string) {
        return s.length === 5 && numberRegexp.test(s);
      }
    }
    export = ZipCodeValidator;
    // Test.ts
    import zip = require("./ZipCodeValidator");
    
    let validator = new zip();
     
    모듈 코드 생성
    모듈 타겟이 무엇인지에 따라 컴파일된 코드가 달라진다. 아래는 각 타겟 별 SimpleModule 모듈을 컴파일한 결과이다.
    // SimpleModule.ts
    import m = require("mod");
    export let t = m.something + 1;
       
    Target: AMD (RequireJS)
    // SimpleModule.js
    define(["require", "exports", "./mod"], function (require, exports, mod_1) {
      exports.t = mod_1.something + 1;
    });
       
    Target: CommonJS (Node)
    // SimpleModule.js
    var mod_1 = require("./mod");
    exports.t = mod_1.something + 1;
      
    Target: UMD
    // SimpleModule.js
    (function (factory) {
      if (typeof module === "object" && typeof module.exports === "object") {
        var v = factory(require, exports);
        if (v !== undefined) module.exports = v;
      } else if (typeof define === "function" && define.amd) {
        define(["require", "exports", "./mod"], factory);
      }
    })(function (require, exports) {
      var mod_1 = require("./mod");
      exports.t = mod_1.something + 1;
    });
     
    Target: System
    // SimpleModule.js
    System.register(["./mod"], function (exports_1) {
      var mod_1;
      var t;
      return {
        setters: [
          function (mod_1_1) {
            mod_1 = mod_1_1;
          },
        ],
        execute: function () {
          exports_1("t", (t = mod_1.something + 1));
        },
      };
    });
       
    Target: ES6
    // SimpleModule.js
    import { something } from "./mod";
    export var t = something + 1;
      
    선택적 모듈 로딩
    컴파일러는 import된 모듈이 emit된 자바스크립트 파일에서 사용되는지 검사한다. 만약 모듈 식별자가 표현식이 아닌 타입 표기로만 사용된다면, 해당 모듈의 require 호출문은 emit된 자바스크립트 파일에 포함되지 않는다.
    import id = require("...")문을 사용하면 해당 모듈의 타입에 접근 할 수 있다. 아래 코드는 Node.js에서 Dynamic 모듈 로딩을 구현한 예제이다.
    declare function require(moduleName: string): any;
    
    // 1. Zip은 타입 표기로만 사용된다. 즉, emit된 JS 파일에 require("./ZipCodeValidator")문이 포함되지 않는다.
    import { ZipCodeValidator as Zip } from './ZipCodeValidator';
    
    if (needZipValidation) {
      // 2. ZipCodeValidator가 필요한 경우, require문으로 import한다.
      let ZipCodeValidator: typeof Zip = require("./ZipCodeValidator");
    }
     
    자바스크립트 라이브러리 사용하기 - Ambient 모듈
    자바스크립트로 작성된 라이브러리의 구조를 나타내기 위해서는, 해당 라이브러리에서 제공하는 API를 선언(declare) 할 필요가 있다. implementation을 정의하지 않은 선언을 "Ambient"라고 한다. Ambient 선언은 보통 .d.ts 파일에 작성되어 있다.
    // node.d.ts
    declare module "url" {
      export interface Url {
        protocol?: string;
        hostname?: string;
        pathname?: string;
      }
    
      export function parse(
        urlStr: string,
        parseQueryString?: string,
        slashesDenoteHost?: string
      ): Url;
    }
    
    declare module "path" {
      export function normalize(p: string): string;
      export function join(...paths: any[]): string;
      export var sep: string;
    }
       
    위에서 작성한 Ambient 모듈을 사용하기 위해서는 node.d.ts 파일을 /// <reference>로 추가하면된다.
    /// <reference path="node.d.ts"/>
    import * as URL from 'url';
    let myUrl = URL.parse("http://www.typescriptlang.org");
      
    만약 위 예제에서 라이브러리 API를 선언하지 않고, 모듈을 바로 사용하고 싶다면 단축 선언(shorthand declaration)을 작성하면된다.
    declare module "url";
    import { parse } from 'url';
    
    parse("...");
    // 주의: shorthand declaration으로 작성된 모듈은 any type이다.
      
    UMD 모듈
    일부 라이브러리는 다양한 모듈 로더, 혹은 0개의 모듈 로더를 사용할 수 있도록 작성되어있다. 이러한 모듈을 UMD(Universal Module Definition) 모듈이라고 한다. UMD 모듈은 import하여 사용하거나, 전역 변수로서 사용한다. 아래 예제를 살펴보자.
    // math-lib.d.ts
    export function isPrime(x: number): boolean;
    export as namespace mathLib;
    math-lib 라이브러리를 모듈에서 사용 할 경우 import 하면된다.
    import { isPrime } from "math-lib";
    
    isPrime(2);
    mathLib.isPrime(2); // ERROR: can't use the global definition from inside a module
    math-lib 라이브러리를 전역 변수로 사용하기 위해서는 모듈 파일이 아닌 일반 스크립트 파일에서 사용해야한다.
    mathLib.isPrime(2);
       
    모듈 구조화 가이드
    1. Export as close to top-level as possible
  • 모듈을 네임스페이스로 래핑하여 export하는 것은 불필요한 레이어를 추가하는 것일 수 있다. 모듈 사용자로 하여금 실수를 유발 할 수 있게 한다.
  • export된 클래스의 static 메소드를 사용할 때, 클래스 자체가 불필요한 레이어가 될 수 있다. 클래스를 네임스페이스로 사용하는 것이 작성된 코드의 의도를 더 뚜렷히 나타낼 수 있는 것이 아니라면 개별 함수를 export 하는 것이 바람직하다.
  • 만약 1개의 클래스 혹은 함수만을 export 한다면 export default 문법을 사용한다. default export된 모듈을 import 할 때 원하는 이름으로 rename 할 수 있고, 불필요한 . 체이닝을 줄일 수 있다.
  • 여러 모듈을 export 할 경우 top-level에 작성한다. (export * as namespace X)
  • 여러 모듈을 import 할 경우 import된 모듈 이름을 명시적으로 작성한다 (import * as namespace X). 단, import 하는 모듈이 너무 많을 경우 namespace import를 사용한다.
  •    
    2. Re-export to extend
    모듈의 기능을 확장할 경우, 기존의 모듈을 변경하지 않고 새로운 기능을 제공하는 개체를 export한다. 예를 들어, Calculator 클래스를 확장한 ProgrammerCalculator 클래스를 export할 때 아래와 같이 작성 할 수 있다.
    // Calculator.ts
    export class Calculator {
      private current = 0;
      private memory = 0;
      private operator: string;
    
      protected processDigit(digit: string, currentValue: number) {
        // ...
      }
    
      protected processOperator(operator: string) {
        // ...
      }
    
      protected evaluateOperator(
        operator: string,
        left: number,
        right: number
      ): number {
        // ...
      }
    
      private evaluate() {
        // ...
      }
    
      public handleChar(char: string) {
        // ...
      }
    
      public getResult() {
        // ...
      }
    }
    
    export function test(c: Calculator, input: string) {
      // ...
    }
    // ProgrammerCalculator.ts
    import { Calculator } from "./Calculator";
    
    class ProgrammerCalculator extends Calculator {
      static digits = [ /* ... */ ];
    
      constructor(public base: number) {
        super();
        // ...
      }
    
      protected processDigit(digit: string, currentValue: number) {
        // ...
      }
    }
    
    // 기존 Calculator를 변경하지 않고 확장하여 export
    export { ProgrammerCalculator as Calculator };
    
    // 기존 test를 re-export
    export { test } from "./Calculator";
      
    3. Do not use namespaces in modules
    모듈은 자신만의 스코프를 가지고 있으며, 오직 export된 모듈만 외부에서 접근 할 수 있다. 이 사실 자체만으로 namespce는 큰 의미가 없다. namespace는 전역 스코프에서 이름 충돌의 위험이 있는 식별자를 계층적으로 구분하기 위해 사용된다. 하지만 모듈은 경로와 파일 이름을 resolve하여 사용되므로 이미 파일 시스템에 의해 계층이 구분되어 있다.
     
    4. 주의사항
  • namespace만을 top-level export하는 모듈(ex. export namespace Foo {...})은 해당 namespace를 제거한 후 하위에 선언된 모든 것을 1레벨 올린다.
  • 여러 파일에서 작성된 top-level export namespace Foo {...}는 하나의 Foo로 병합되지 않는다.
  •    
    출처

    37

    This website collects cookies to deliver better user experience

    TypeScript - Modules