一.语法格式
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 分类的依据之一
P.S.import/export
具体语法见ES Module,这里不展开
CommonJS 模块支持
为了支持CommonJS 和 AMD 模块,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 规范都会对应一个--target
具体值,因为每一版都会有新的特性加入
P.S.更多相关讨论,见Understanding “target” and “module” in tsconfig
P.S.注意,--module
和--target
都是针对将要生成的目标代码的,与源码无关(源码完全可以 ES 特性全开,如--lib
指定ES2016, 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;
两种引用方式:
// 直接通过全局变量访问
mathLib.isPrime(12);
// 模块引入
import { isPrime } from './math-lib';
isPrime(122);