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);
请务必阅读脚注“ES6 对模块调用签名的影响”
如果您的模块可以使用 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
会向 moment
对象添加一个新的 range
方法。
为了编写声明文件,无论被修改的模块是普通模块还是 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
模板。
使用依赖项
你的库可能有多种依赖项。本节介绍如何将它们导入声明文件。
全局库的依赖项
如果你的库依赖于全局库,请使用 `///
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
导出;一些模块加载器垫片会自动检测这种情况并将顶级对象替换为 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;}}