Skip to main content

Modules_TypeScript Notes 13

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

Compatible and inclusive module mechanism

I. Syntax Format

TypeScript is compatible with ES Module specification, file is module

Simply speaking, if a file contains legal import or export statements, it will be treated as a module (has module scope), otherwise it will run under global scope. For example:

let x = 1
function f() { }
// Will be compiled to
var x = 1;
function f() { }

// And
let x = 1
export function f() { }
// Will be compiled to (using AMD format as example)
define(["require", "exports"], function (require, exports) {
  "use strict";
  Object.defineProperty(exports, "__esModule", { value: true });
  var x = 1;
  function f() { }
  exports.f = f;
});

Any declaration can be import/export, including interfaces, type aliases, etc.:

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

Specially, pure declaration files (such as d.ts) although won't generate code with actual meaning, still have module (scope) isolation:

// Above example will be compiled to
define(["require", "exports"], function (require, exports) {
  "use strict";
  Object.defineProperty(exports, "__esModule", { value: true });
});

This is also one of the bases for d.ts classification

P.S. import/export specific syntax see ES Module, won't expand here

CommonJS Module Support

To support [CommonJS and AMD Modules](/articles/module-es6 笔记 13/#articleHeader2), TypeScript provides a special syntax:

export = something;

Used to define a module's export object, similar to in NodeJS:

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

Rewritten to TypeScript is like this:

let x = {a: 1};
export = x;
// Will be compiled to
define(["require", "exports"], function (require, exports) {
  "use strict";
  var x = { a: 1 };
  return x;
});

Corresponding import syntax is also different from NodeJS (require('./myModule.js')):

import module = require("myModule")

II. Module Code Generation

Can specify generated code's module format through compilation options (--module or -m):

  // tsc -m xxx
 'commonjs' # NodeJS module definition
 'amd'      # AMD
 'system'   # SystemJS
 'umd'      # UMD
 'es6'      # ES Module
 'es2015'   # Equivalent to es6
 'esnext'   # Frontier module definitions not yet included in ES specification, such as `import(), import.meta` etc.
 'none'     # Disable all module definitions, such as import, export etc. (will error if used)

Default module format is CommonJS or ES6, related to --target option (target === "ES3" or "ES5" ? "CommonJS" : "ES6"). If future new version ES specification has changes in module definition, will also add es2019, es2020... etc. values, corresponding to module definitions in each version of ES specification (if module definition has no changes, then not add)

P.S. Specific module generation examples, see Code Generation for Modules

--module and --target

--target (or -t) option is very similar to --module, values are as follows:

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

Indicates which version of specification's defined language features the generated target code supports (default ES3), independent from --module option:

The module system is independent of the language implementation.

Because completely can compile generate ES5 code satisfying ES6 module format, for example:

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

Additionally, values also different from --module, each version of ES specification will correspond to a --target specific value, because each version will have new features added

P.S. More related discussions, see Understanding "target" and "module" in tsconfig

P.S. Note, --module and --target are both for the target code to be generated, unrelated to source code (source code can completely have all ES features open, such as --lib specify ES2016, es2017...)

III. Module Import

Generally, import/require will import target module source code, and extract type information from it, for example:

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

// index.ts
import MyModule from './MyModule';
let m = MyModule;
// m's type is { name: string; f(): void; }
m.f();

(Under --module commonjs) index.ts compilation result is:

exports.__esModule = true;
var MyModule_1 = require("./MyModule");
var m = MyModule_1["default"];
// m's type is { name: string; f(): void; }
m.f();

On-demand Loading

Specially, if generated target code doesn't use the imported module (such as only used in type annotation), will automatically remove module reference during compilation:

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

// Compilation result
exports.__esModule = true;
var m;

This removing non-essential references (reference-elision) feature is especially important in on-demand loading scenarios:

// Import type
import MyModule from './MyModule';
declare function require(moduleName: string): any;
let someCondition: boolean;
if (someCondition) {
  let m: typeof MyModule = require('./MyModule');
  // Also has correct type
  m.f();
}

// Compilation result
"use strict";
exports.__esModule = true;
var someCondition;
if (someCondition) {
  var m = require('./MyModule');
  // Also has correct type
  m.f();
}

IV. Module Type Declaration

For third-party modules lacking types, can supplement type declarations for them through Declaration Files (d.ts)

Specifically, declare module 'my-module' {} syntax can declare a module (can be import/require):

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

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

Can use this method to fill in third-party module's types, but if just want to use quickly (unwilling to manually supplement types), can omit member declarations, all its members will be any type:

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

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

Wildcards

Specially, certain loading systems support importing non-JavaScript content, such as AMD:

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

At this time can use module wildcards to define its type:

// Describe types of all modules starting with text!
declare module "text!*" {
  const content: string;
  export default content;
}
// Describe types of all modules ending with !text
declare module "*!text" {
  const content: string;
  export default content;
}

These special modules then have type information:

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

UMD Modules

UMD's characteristic is both compatible with CommonJS and AMD module loading, can also expose to global for direct use, therefore its module declaration is also special:

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

Two reference methods:

// Directly access through global variable
mathLib.isPrime(12);
// Module import
import { isPrime } from './math-lib';
isPrime(122);

Reference Materials

Comments

No comments yet. Be the first to share your thoughts.

Leave a comment