UMD
UMD 模組可以同時作為模組(透過匯入)或全域(在沒有模組載入器的環境中執行時)使用。許多熱門函式庫,例如 Moment.js,都是以這種方式編寫的。例如,在 Node.js 或使用 RequireJS 時,您可以撰寫
ts
import moment = require("moment");console.log(moment.format());
而在純瀏覽器環境中,您會撰寫
js
console.log(moment.format());
識別 UMD 函式庫
UMD 模組會檢查模組載入器環境是否存在。這是一個容易發現的模式,看起來像這樣
js
(function (root, factory) {if (typeof define === "function" && define.amd) {define(["libName"], factory);} else if (typeof module === "object" && module.exports) {module.exports = factory(require("libName"));} else {root.returnExports = factory(root.libName);}}(this, function (b) {
如果您在函式庫的程式碼中看到對 typeof define
、typeof window
或 typeof module
的測試,特別是在檔案開頭,那幾乎總是 UMD 函式庫。
UMD 函式庫的文件通常也會展示一個「在 Node.js 中使用」範例,顯示 require
,以及一個「在瀏覽器中使用」範例,顯示使用 <script>
標籤載入指令碼。
UMD 函式庫範例
現在大多數熱門函式庫都以 UMD 套件的形式提供。範例包括 jQuery、Moment.js、lodash 等許多其他函式庫。
範本
模組有三個可用的範本,module.d.ts
、module-class.d.ts
和 module-function.d.ts
。
如果您的模組可以像函式一樣被呼叫,請使用 module-function.d-ts
js
var x = require("foo");// Note: calling 'x' as a functionvar y = x(42);
如果您的模組可以使用 new
建構,請使用 module-class.d.ts
js
var x = require("bar");// Note: using 'new' operator on the imported variablevar y = new x("hello");
相同的註腳適用於這些模組。
如果您的模組不可呼叫或不可建構,請使用 module.d.ts
檔案。
模組外掛程式或UMD 外掛程式
模組外掛程式會變更另一個模組(UMD 或模組)的形狀。例如,在 Moment.js 中,moment-range
會新增一個新的 range
方法到 moment
物件。
為了撰寫宣告檔,不論要變更的模組是純模組或 UMD 模組,您都會撰寫相同的程式碼。
範本
使用 module-plugin.d.ts
範本。
全球外掛
全球外掛是會改變某個全域變數形狀的全球程式碼。與修改全域變數的模組一樣,這些外掛程式會導致執行時期衝突。
例如,有些函式庫會新增新的函式到 Array.prototype
或 String.prototype
。
辨識全球外掛
從文件通常很容易辨識出全球外掛。
您會看到類似這樣的範例
js
var x = "hello, world";// Creates new methods on built-in typesconsole.log(x.startsWithHello());var y = [1, 2, 3];// Creates new methods on built-in typesconsole.log(y.reverseAndSort());
範本
使用 global-plugin.d.ts
範本。
全域修改模組
全域修改模組在匯入時會變更全域範圍內現有的值。例如,可能有一個函式庫在匯入時會新增新的成員到 String.prototype
。這種模式有點危險,因為可能會發生執行時期衝突,但我們仍然可以為它撰寫宣告檔。
識別全域修改模組
全域修改模組通常很容易從其文件識別。一般來說,它們類似於全域外掛程式,但需要 require
呼叫才能啟動其效果。
您可能會看到類似這樣的文件
js
// 'require' call that doesn't use its return valuevar unused = require("magic-string-time");/* or */require("magic-string-time");var x = "hello, world";// Creates new methods on built-in typesconsole.log(x.startsWithHello());var y = [1, 2, 3];// Creates new methods on built-in typesconsole.log(y.reverseAndSort());
範本
使用 global-modifying-module.d.ts
範本。
使用相依性
您的函式庫可能有多種相依性。本節說明如何將它們匯入宣告檔案。
對全球函式庫的相依性
如果您的函式庫依賴於全球函式庫,請使用 /// <reference types="..." />
指令
ts
/// <reference types="someLib" />function getThing(): someLib.thing;
對模組的相依性
如果您的函式庫依賴於模組,請使用 import
陳述式
ts
import * as moment from "moment";function getThing(): moment;
UMD 函式庫的相依性
來自全域函式庫
如果您的全域函式庫依賴於 UMD 模組,請使用 /// <reference types
指令
ts
/// <reference types="moment" />function getThing(): moment;
來自模組或 UMD 函式庫
如果你的模組或 UMD 函式庫依賴 UMD 函式庫,請使用 import
陳述式
ts
import * as someLib from "someLib";
請不要使用 /// <reference
指令來宣告對 UMD 函式庫的依賴關係!
腳註
防止名稱衝突
請注意,在撰寫全域宣告檔時,可以在全域範圍內定義許多類型。我們強烈建議不要這麼做,因為當專案中有多個宣告檔時,可能會導致無法解決的名稱衝突。
要遵循的簡單規則是,僅宣告函式庫定義的任何全域變數所命名空間的類型。例如,如果函式庫定義全域值「cats」,您應該撰寫
ts
declare namespace cats {interface KittySettings {}}
但不要
ts
// at top-levelinterface CatsKittySettings {}
此指南還可確保函式庫可以在不中斷宣告檔使用者的情況下轉換為 UMD。
ES6 對模組外掛程式的影響
有些外掛程式會新增或修改現有模組的頂層匯出。儘管這在 CommonJS 和其他載入器中是合法的,但 ES6 模組被視為不可變,因此這種模式將不可行。由於 TypeScript 與載入器無關,因此不會在編譯時強制執行此政策,但打算轉換到 ES6 模組載入器的開發人員應注意這一點。
ES6 對模組呼叫簽章的影響
許多熱門函式庫(例如 Express)在匯入時會將自己公開為可呼叫函式。例如,典型的 Express 使用方式如下所示
ts
import exp = require("express");var app = exp();
在 ES6 模組載入器中,頂層物件(在此匯入為 exp
)只能有屬性;頂層模組物件永遠不可呼叫。最常見的解決方案是在此為可呼叫/可建構物件定義 default
匯出;有些模組載入器 shim 會自動偵測此情況,並將頂層物件替換為 default
匯出。
函式庫檔案配置
宣告檔案的配置應反映函式庫的配置。
函式庫可以包含多個模組,例如
myLib +---- index.js +---- foo.js +---- bar +---- index.js +---- baz.js
這些模組可以匯入為
js
var a = require("myLib");var b = require("myLib/foo");var c = require("myLib/bar");var d = require("myLib/bar/baz");
因此,您的宣告檔案應為
@types/myLib +---- index.d.ts +---- foo.d.ts +---- bar +---- index.d.ts +---- baz.d.ts
ts
// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]// Project: [~THE PROJECT NAME~]// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>/*~ This template shows how to write a global plugin. *//*~ Write a declaration for the original type and add new members.*~ For example, this adds a 'toBinaryString' method with overloads to*~ the built-in number type.*/interface Number {toBinaryString(opts?: MyLibrary.BinaryFormatOptions): string;toBinaryString(callback: MyLibrary.BinaryFormatCallback,opts?: MyLibrary.BinaryFormatOptions): string;}/*~ If you need to declare several types, place them inside a namespace*~ to avoid adding too many things to the global namespace.*/declare namespace MyLibrary {type BinaryFormatCallback = (n: number) => string;interface BinaryFormatOptions {prefix?: string;padding: number;}}