一般來講,你組織聲明文件的方式取決于庫是如何被使用的。 在JavaScript里提供了很多庫的使用方法,這就需要你書寫聲明文件去匹配它們。 這篇指南涵蓋了如何識別常見庫的模式,與怎么樣書寫符合相應(yīng)模式的聲明文件。
針對每種主要的庫的組織模式,在模版一節(jié)都有對應(yīng)的文件。 你可以利用它們幫助你快速上手。
首先,我們先看一下TypeScript聲明文件能夠表示的庫的類型。 這里會簡單展示每種類型的庫的使用方式,如何去書寫,還有一些真實(shí)案例。
識別庫的類型是書寫聲明文件的第一步。 我們將會給出一些提示,關(guān)于怎樣通過庫的 使用方法及其源碼來識別庫的類型。 根據(jù)庫的文檔及組織結(jié)構(gòu)不同,這兩種方式可能一個會比另外的那個簡單一些。 我們推薦你使用任意你喜歡的方式。
全局庫是指能在全局命名空間下訪問的(例如:不需要使用任何形式的import
)。 許多庫都是簡單的暴露出一個或多個全局變量。 比如,如果你使用過 jQuery,$
變量可以被夠簡單的引用:
$(() => { console.log('hello!'); } );
你經(jīng)常會在全局庫的指南文檔上看到如何在HTML里用腳本標(biāo)簽引用庫:
<script src="http://a.great.cdn.for/someLib.js" rel="external nofollow" ></script>
目前,大多數(shù)流行的全局訪問型庫實(shí)際上都以UMD庫的形式進(jìn)行書寫(見后文)。 UMD庫的文檔很難與全局庫文檔兩者之間難以區(qū)分。 在書寫全局聲明文件前,一定要確認(rèn)一下庫是否真的不是UMD。
全局庫的代碼通常都十分簡單。 一個全局的“Hello, world”庫可能是這樣的:
function createGreeting(s) {
return "Hello, " + s;
}
或這樣:
window.createGreeting = function(s) {
return "Hello, " + s;
}
當(dāng)你查看全局庫的源代碼時,你通常會看到:
var
語句或function
聲明window.someName
document
或window
是存在的你不會看到:
require
或define
var fs = require("fs");
define(...)
調(diào)用require
或?qū)脒@個庫由于把一個全局庫轉(zhuǎn)變成UMD庫是非常容易的,所以很少流行的庫還再使用全局的風(fēng)格。 然而,小型的且需要DOM(或 沒有依賴)的庫可能還是全局類型的。
模版文件global.d.ts
定義了myLib
庫作為例子。 一定要閱讀 "防止命名沖突"補(bǔ)充說明。
一些庫只能工作在模塊加載器的環(huán)境下。 比如,像 express
只能在Node.js里工作所以必須使用CommonJS的require
函數(shù)加載。
ECMAScript 2015(也就是ES2015,ECMAScript 6或ES6),CommonJS和RequireJS具有相似的導(dǎo)入一個模塊的表示方法。 例如,對于JavaScript CommonJS (Node.js),有下面的代碼
var fs = require("fs");
對于TypeScript或ES6,import
關(guān)鍵字也具有相同的作用:
import fs = require("fs");
你通常會在模塊化庫的文檔里看到如下說明:
var someLib = require('someLib');
或
define(..., ['someLib'], function(someLib) {
});
與全局模塊一樣,你也可能會在UMD模塊的文檔里看到這些例子,因此要仔細(xì)查看源碼和文檔。
模塊庫至少會包含下列具有代表性的條目之一:
require
或define
import * as a from 'b';
or export c;
這樣的聲明exports
或module.exports
它們極少包含:
window
或global
的賦值許多流行的Node.js庫都是這種模塊化的,例如express
,gulp
和 request
。
UMD模塊是指那些既可以作為模塊使用(通過導(dǎo)入)又可以作為全局(在沒有模塊加載器的環(huán)境里)使用的模塊。 許多流行的庫,比如 Moment.js,就是這樣的形式。 比如,在Node.js或RequireJS里,你可以這樣寫:
import moment = require("moment");
console.log(moment.format());
然而在純凈的瀏覽器環(huán)境里你也可以這樣寫:
console.log(moment.format());
UMD模塊會檢查是否存在模塊加載器環(huán)境。 這是非常形容觀察到的模塊,它們會像下面這樣:
(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庫的文檔里經(jīng)常會包含通過require
“在Node.js里使用”例子, 和“在瀏覽器里使用”的例子,展示如何使用<script>
標(biāo)簽去加載腳本。
大多數(shù)流行的庫現(xiàn)在都能夠被當(dāng)成UMD包。 比如 jQuery,Moment.js,lodash和許多其它的。
針對模塊有三種可用的模塊, module.d.ts
, module-class.d.ts
and module-function.d.ts
.
使用module-function.d.ts
,如果模塊能夠作為函數(shù)調(diào)用。
var x = require("foo");
// Note: calling 'x' as a function
var y = x(42);
一定要閱讀補(bǔ)充說明: “ES6模塊調(diào)用簽名的影響”
使用module-class.d.ts
如果模塊能夠使用new
來構(gòu)造:
var x = require("bar");
// Note: using 'new' operator on the imported variable
var y = new x("hello");
相同的補(bǔ)充說明作用于這些模塊。
如果模塊不能被調(diào)用或構(gòu)造,使用module.d.ts
文件。
一個模塊插件可以改變一個模塊的結(jié)構(gòu)(UMD或模塊)。 例如,在Moment.js里, moment-range
添加了新的range
方法到monent
對象。
對于聲明文件的目標(biāo),我們會寫相同的代碼不論被改變的模塊是一個純粹的模塊還是UMD模塊。
使用module-plugin.d.ts
模版。
一個全局插件是全局代碼,它們會改變?nèi)謱ο蟮慕Y(jié)構(gòu)。 對于 全局修改的模塊,在運(yùn)行時存在沖突的可能。
比如,一些庫往Array.prototype
或String.prototype
里添加新的方法。
全局通常很容易地從它們的文檔識別出來。
你會看到像下面這樣的例子:
var x = "hello, world";
// Creates new methods on built-in types
console.log(x.startsWithHello());
var y = [1, 2, 3];
// Creates new methods on built-in types
console.log(y.reverseAndSort());
使用global-plugin.d.ts
模版。
當(dāng)一個全局修改的模塊被導(dǎo)入的時候,它們會改變?nèi)肿饔糜蚶锏闹怠?比如,存在一些庫它們添加新的成員到String.prototype
當(dāng)導(dǎo)入它們的時候。 這種模式很危險,因?yàn)榭赡茉斐蛇\(yùn)行時的沖突, 但是我們?nèi)匀豢梢詾樗鼈儠鴮懧暶魑募?/p>
全局修改的模塊通??梢院苋菀椎貜乃鼈兊奈臋n識別出來。 通常來講,它們與全局插件相似,但是需要 require
調(diào)用來激活它們的效果。
你可能會看到像下面這樣的文檔:
// 'require' call that doesn't use its return value
var unused = require("magic-string-time");
/* or */
require("magic-string-time");
var x = "hello, world";
// Creates new methods on built-in types
console.log(x.startsWithHello());
var y = [1, 2, 3];
// Creates new methods on built-in types
console.log(y.reverseAndSort());
使用global-modifying-module.d.ts
模版。
可能會有以下幾種依賴。
如果你的庫依賴于某個全局庫,使用/// <reference types="..." />
指令:
/// <reference types="someLib" />
function getThing(): someLib.thing;
如果你的庫依賴于模塊,使用import
語句:
import * as moment from "moment";
function getThing(): moment;
如果你的全局庫依賴于某個UMD模塊,使用/// <reference types
指令:
/// <reference types="moment" />
function getThing(): moment;
如果你的模塊或UMD庫依賴于一個UMD庫,使用import
語句:
import * as someLib from 'someLib';
不要使用/// <reference
指令去聲明UMD庫的依賴!
注意,在書寫全局聲明文件時,允許在全局作用域里定義很多類型。 我們十分不建義這樣做,當(dāng)一個工程里有許多聲明文件時,它會導(dǎo)致無法處理的命名沖突。
一個簡單的規(guī)則是使用庫定義的全局變量名來聲明命名空間類型。 比如,庫定義了一個全局的值 cats
,你可以這樣寫
declare namespace cats {
interface KittySettings { }
}
不要
// at top-level
interface CatsKittySettings { }
這樣也保證了庫在轉(zhuǎn)換成UMD的時候沒有任何的破壞式改變,對于聲明文件用戶來說。
一些插件添加或修改已存在的頂層模塊的導(dǎo)出部分。 當(dāng)然這在CommonJS和其它加載器里是允許的,ES模塊被當(dāng)作是不可改變的因此這種模式就不可行了。 因?yàn)門ypeScript是能不預(yù)知加載器類型的,所以沒在編譯時保證,但是開發(fā)者如果要轉(zhuǎn)到ES6模塊加載器上應(yīng)該注意這一點(diǎn)。
很多流行庫,比如Express,暴露出自己作為可以調(diào)用的函數(shù)。 比如,典型的Express使用方法如下:
import exp = require("express");
var app = exp();
在ES6模塊加載器里,頂層的對象(這里以exp
導(dǎo)入)只能具有屬性; 頂層的模塊對象 永遠(yuǎn)不能被調(diào)用。 十分常見的解決方法是定義一個 default
導(dǎo)出到一個可調(diào)用的/可構(gòu)造的對象; 一會模塊加載器助手工具能夠自己探測到這種情況并且使用 default
導(dǎo)出來替換頂層對象。
更多建議: