一。語法格式
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 模組](/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 規範都會對應一個 --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);
暫無評論,快來發表你的看法吧