본문으로 건너뛰기

모듈_TypeScript 노트 13

무료2019-03-30#TypeScript#TypeScript reference elision#ES Module in TypeScript#TypeScript模块化#TypeScript按需加载#TypeScript dynamic require#TypeScript动态加载

兼容并包의 모듈 메커니즘

일.구문 포맷

TypeScript 는 ES Module 규범과 호환성이 있으며, 파일이 모듈입니다

간단히 말해, 파일 중에 합법적인 import 또는 export 문이 포함되어 있으면, 모듈로 취급됩니다 (모듈 스코프를 가짐).否则글로벌 스코프 하에서 실행됩니다. 예를 들어:

let x = 1
function f() { }
// 会被编译成
var x = 1;
function f() { }

// 而
let x = 1
export function f() { }
// 会被编译成(以 AMD 形式为例)
define(["require", "exports"], function (require, exports) {
  "use strict";
  Object.defineProperty(exports, "__esModule", { value: true });
  var x = 1;
  function f() { }
  exports.f = f;
});

모든 선언은 import/export 가능하며, 인터페이스, 타입 별칭 등을 포함합니다:

export interface StringValidator {
  isAcceptable(s: string): boolean;
}
export type PhoneNumber = string;

特殊的、순수한 선언 파일 (예를 들어 d.ts) 는 실제적인 의미가 있는 코드를 생성하지 않지만, 그래도 모듈 (스코프) 격리를 가집니다:

// 上例会被编译成
define(["require", "exports"], function (require, exports) {
  "use strict";
  Object.defineProperty(exports, "__esModule", { value: true });
});

이것도 d.ts 분류 의 근거의 1 つ입니다

P.S. import/export 具体구문은 ES Module 참조. 여기서는 전개하지 않습니다

CommonJS 모듈 서포트

[CommonJS 와 AMD 모듈](/articles/module-es6 笔记 13/#articleHeader2) 을 서포트하기 위해, TypeScript 는 특수한 구문을 제공합니다:

export = something;

모듈의 익스포트 오브젝트를 정의하는 데 사용되며, NodeJS 내의 다음과 유사:

// NodeJS 모듈 (CommonJS)
let x = {a: 1};
exports.x = x;
module.exports = x;

TypeScript 로 다시 쓰면 이렇습니다:

let x = {a: 1};
export = x;
// 会被编译成
define(["require", "exports"], function (require, exports) {
  "use strict";
  var x = { a: 1 };
  return x;
});

대응하는 인포트 구문도 NodeJS(require('./myModule.js')) 와는 다릅니다:

import module = require("myModule")

이.모듈 코드 생성

컴파일 옵션 (--module 또는 -m) 을 통해 생성 코드의 모듈 포맷을 지정할 수 있습니다:

  // tsc -m xxx
 'commonjs' # NodeJS 모듈 정의
 'amd'      # AMD
 'system'   # SystemJS
 'umd'      # UMD
 'es6'      # ES Module
 'es2015'   # es6 와 동등
 'esnext'   # 아직 ES 규범에 수록되지 않은 최첨단 모듈 정의, 예를 들어 `import(), import.meta` 등
 'none'     # 모든 모듈 정의를 무효화, 예를 들어 import, export 등 (사용하면 오류)

기본 모듈 포맷은 CommonJS 또는 ES6 으로, --target 옵션과 관계있습니다 (target === "ES3" or "ES5" ? "CommonJS" : "ES6"). 만약 장래의 새 버전 ES 규범 중에서 모듈 정의에 변경이 있는 경우, 더욱 es2019, es2020... 등의 값이 추가되며, ES 규범의 각 버전 중의 모듈 정의에 대응합니다 (모듈 정의에 변경이 없는 경우는, 추가하지 않습니다)

P.S. 具体의 모듈 생성 예는, Code Generation for Modules 참조

--module--target

--target(또는 -t) 옵션은 --module 과 매우 유사하며, 값은 다음과 같습니다:

// tsc -t xxx
'es3'
'es5'
'es2015'
'es2016'
'es2017'
'es2018'
'esnext'

생성되는 목표 코드가 어느 버전의 규범으로 정의된 언어 특성을 서포트하는지를 나타냅니다 (기본 ES3). --module 옵션과는 독립적입니다:

The module system is independent of the language implementation.

완전히 ES6 모듈 포맷을 만족하는 ES5 코드를 컴파일 생성할 수 있기 때문입니다. 예를 들어:

// tsconfig.json
"compilerOptions": {
  "target": "es5",
  "module": "es6"
}

게다가, 값도 --module 과는 다르며, 각 버전의 ES 규범은 1 개의 --target 具体값에 대응합니다. 각 버전마다 새로운 특성이 추가되기 때문입니다

P.S. 더 많은 관련 논의는, Understanding "target" and "module" in tsconfig 참조

P.S. 주의, --module--target 는 모두생성되는 목표 코드를 대상으로 한 것으로, 소스 코드와는 무관계 입니다 (소스 코드는 완전히 ES 특성 전개해도 상관없습니다. 예를 들어 --libES2016, es2017... 를 지정)

삼.모듈 인포트

일반적으로, import/require 는 목표 모듈 소스 코드를 인포트하고, 거기서 타입 정보를 추출합니다. 예를 들어:

// myModule.ts
export default {
  name: 'my-module',
  f() {
    console.log('this is my module.');
  }
}

// index.ts
import MyModule from './MyModule';
let m = MyModule;
// m 의 타입为 { name: string; f(): void; }
m.f();

(--module commonjs 하에서) index.ts 의 컴파일 결과는:

exports.__esModule = true;
var MyModule_1 = require("./MyModule");
var m = MyModule_1["default"];
// m 의 타입为 { name: string; f(): void; }
m.f();

온디맨드 로드

特殊的, 생성되는 목표 코드 중에서 인포트된 모듈이 사용되지 않은 경우 (예를 들어 타입 주석 중에서의만 사용), 컴파일 시에 모듈 참조가 자동으로 삭제됩니다:

// index.ts
import MyModule from './MyModule';
let m: typeof MyModule;

// 编译结果
exports.__esModule = true;
var m;

비필수 참조를 제거 (reference-elision) 하는 특성은, 온디맨드 로드의 시나리오에서 특히 중요합니다:

// 타입을 인포트
import MyModule from './MyModule';
declare function require(moduleName: string): any;
let someCondition: boolean;
if (someCondition) {
  let m: typeof MyModule = require('./MyModule');
  // 동様に 올바른 타입을 가집니다
  m.f();
}

// 编译结果
"use strict";
exports.__esModule = true;
var someCondition;
if (someCondition) {
  var m = require('./MyModule');
  // 동様に 올바른 타입을 가집니다
  m.f();
}

사.모듈 타입 선언

타입이 부족한 서드파티 모듈에 대해, 선언 파일 (d.ts) 를 통해 타입 선언을 보충할 수 있습니다

구체적으로는, declare module 'my-module' {} 구문은 모듈을 선언할 수 있습니다 (import/require 가능):

// types.d.ts
declare module "my-module" {
  function f(): string;
}

// index.ts
import { f } from "my-module";
const result: string = f();

이 방식을 통해 서드파티 모듈의 타입을 보충할 수 있지만, 만약 빠르게 사용하고 싶을 뿐 (수동으로 타입을 보충하기 싫은) 경우, 멤버 선언을 생략할 수 있습니다. 그 모든 멤버는 any 타입이 됩니다:

// types.d.ts
declare module "my-module";

// index.ts
import x, {y} from "my-module";
x(y);

와일드카드

特殊的, 일부 로딩 시스템은 비 JavaScript 콘텐츠의 인포트를 서포트합니다. 예를 들어 AMD :

define(['text!../templates/start.html'], function (template) {
    //do something with the template text string.
});

此時모듈 와일드카드를 통해 그 타입을 정의할 수 있습니다:

// text! 로 시작하는 모든 모듈의 타입을 기술
declare module "text!*" {
  const content: string;
  export default content;
}
// !text 로 끝나는 모든 모듈의 타입을 기술
declare module "*!text" {
  const content: string;
  export default content;
}

이러한 특수한 모듈은 타입 정보를 가집니다:

import html from 'text!../templates/start.html';
// 正确
html.trim();

UMD 모듈

UMD 의 특징은 CommonJS 와 AMD 모듈 로딩과 호환성이 있으며, 글로벌에 노출하여 직접 사용도 할 수 있기 때문에, 그 모듈 선언도 비교적 특수합니다:

// math-lib.d.ts
export function isPrime(x: number): boolean;
export as namespace mathLib;

2 종류의 참조 방식:

// 직접 글로벌 변수를 통해 액세스
mathLib.isPrime(12);
// 모듈 인포트
import { isPrime } from './math-lib';
isPrime(122);

참고 자료

댓글

아직 댓글이 없습니다

댓글 작성