广义上来说,你如何编写声明文件取决于库的使用方式。
在 JavaScript 中,有多种方式提供库供使用,你需要根据使用方式编写相应的声明文件。
本指南介绍了如何识别常见的库模式,以及如何编写与这些模式相对应的声明文件。
🌐 Broadly speaking, the way you structure your declaration file depends on how the library is consumed. There are many ways of offering a library for consumption in JavaScript, and you’ll need to write your declaration file to match it. This guide covers how to identify common library patterns, and how to write declaration files which correspond to that pattern.
每种主要的库结构模式在 Templates 部分都有对应的文件。你可以从这些模板开始,这将帮助你更快地入手。
🌐 Each type of major library structuring pattern has a corresponding file in the Templates section. You can start with these templates to help you get going faster.
识别库的种类
🌐 Identifying Kinds of Libraries
首先,我们将回顾 TypeScript 声明文件可以表示的库的类型。我们将简要展示每种类型的库是如何被使用的、如何被编写的,并列出一些现实世界中的示例库。
🌐 First, we’ll review the kinds of libraries TypeScript declaration files can represent. We’ll briefly show how each kind of library is used, how it is written, and list some example libraries from the real world.
识别一个库的结构是编写其声明文件的第一步。我们将提供关于如何基于其_使用情况_和_代码_来识别结构的提示。根据库的文档和组织方式,其中一种方法可能比另一种更容易。我们建议使用你觉得更舒适的方法。
🌐 Identifying the structure of a library is the first step in writing its declaration file. We’ll give hints on how to identify structure both based on its usage and its code. Depending on the library’s documentation and organization, one might be easier than the other. We recommend using whichever is more comfortable to you.
你应该找什么?
🌐 What should you look for?
在查看你尝试输入的库时问自己的问题。
🌐 Question to ask yourself while looking at a library you are trying to type.
-
你如何获得库?
例如,你是只能通过 npm 获取它,还是只能从 CDN 获取它?
-
你会如何导入它?
它会添加全局对象吗?它使用
require还是import/export语句?
不同类型库的较小样本
🌐 Smaller samples for different types of libraries
模块化库
🌐 Modular Libraries
几乎所有现代的 Node.js 库都属于模块家族。这类库只在有模块加载器的 JS 环境中工作。例如,express 只能在 Node.js 中使用,并且必须通过 CommonJS 的 require 函数加载。
🌐 Almost every modern Node.js library falls into the module family.
These type of libraries only work in a JS environment with a module loader.
For example, express only works in Node.js and must be loaded using the CommonJS require function.
ECMAScript 2015(也称为 ES2015、ECMAScript 6 和 ES6)、CommonJS 和 RequireJS 对于 导入 模块 有类似的概念。 例如,在 JavaScript CommonJS(Node.js)中,你可以这样写
🌐 ECMAScript 2015 (also known as ES2015, ECMAScript 6, and ES6), CommonJS, and RequireJS have similar notions of importing a module. In JavaScript CommonJS (Node.js), for example, you would write
jsvar fs = require("fs");
在 TypeScript 或 ES6 中,import 关键字具有相同的作用:
🌐 In TypeScript or ES6, the import keyword serves the same purpose:
tsimport * as fs from "fs";
你通常会看到模块化库在其文档中包含以下行之一:
🌐 You’ll typically see modular libraries include one of these lines in their documentation:
jsvar someLib = require("someLib");
or
jsdefine(..., ['someLib'], function(someLib) {});
与全局模块一样,你可能会在 UMD 模块的文档中看到这些示例,因此一定要查看代码或文档。
🌐 As with global modules, you might see these examples in the documentation of a UMD module, so be sure to check the code or documentation.
从代码中识别模块库
🌐 Identifying a Module Library from Code
模块化库通常至少有以下一些:
🌐 Modular libraries will typically have at least some of the following:
- 对
require或define的无条件调用 - 像
import * as a from 'b';或export c;这样的声明 - 对
exports或module.exports的赋值
他们很少会:
🌐 They will rarely have:
- 对
window或global属性的赋值
模块模板
🌐 Templates For Modules
模块有四个可用模板,module.d.ts、module-class.d.ts、module-function.d.ts 和 module-plugin.d.ts。
🌐 There are four templates available for modules,
module.d.ts, module-class.d.ts, module-function.d.ts and module-plugin.d.ts.
你应该先阅读 module.d.ts,了解它们的工作方式概况。
🌐 You should first read module.d.ts for an overview on the way they all work.
如果你的模块可以像函数一样被调用,则使用模板 module-function.d.ts :
🌐 Then use the template module-function.d.ts if your module can be called like a function:
jsconst x = require("foo");// Note: calling 'x' as a functionconst y = x(42);
如果你的模块可以使用 new 构造,请使用模板 module-class.d.ts:
🌐 Use the template module-class.d.ts if your module can be constructed using new:
jsconst x = require("bar");// Note: using 'new' operator on the imported variableconst y = new x("hello");
如果你有一个模块,在导入时会对其他模块进行修改,请使用模板 module-plugin.d.ts:
🌐 If you have a module which when imported, makes changes to other modules use template module-plugin.d.ts:
jsconst jest = require("jest");require("jest-matchers-files");
全局库
🌐 Global Libraries
一个 全局 库是指可以从全局作用域访问的库(即无需使用任何形式的 import)。
许多库只是暴露一个或多个全局变量以供使用。
例如,如果你使用的是 jQuery,可以通过简单地引用 $ 变量来使用它:
🌐 A global library is one that can be accessed from the global scope (i.e. without using any form of import).
Many libraries simply expose one or more global variables for use.
For example, if you were using jQuery, the $ variable can be used by simply referring to it:
ts$(() => {console.log("hello!");});
你通常会在全局库的文档中看到有关如何在 HTML 脚本标记中使用该库的指南:
🌐 You’ll usually see guidance in the documentation of a global library of how to use the library in an HTML script tag:
html<script src="http://a.great.cdn.for/someLib.js"></script>
如今,大多数全球可访问的流行库实际上都是以 UMD 库的形式编写的(见下文)。 UMD 库的文档很难与全局库的文档区分开来。 在编写全局声明文件之前,确保该库实际上不是 UMD 库。
🌐 Today, most popular globally-accessible libraries are actually written as UMD libraries (see below). UMD library documentation is hard to distinguish from global library documentation. Before writing a global declaration file, make sure the library isn’t actually UMD.
从代码中识别全局库
🌐 Identifying a Global Library from Code
全局库代码通常非常简单。 一个全局的“Hello, world”库可能看起来像这样:
🌐 Global library code is usually extremely simple. A global “Hello, world” library might look like this:
jsfunction createGreeting(s) {return "Hello, " + s;}
或者像这样:
🌐 or like this:
js// Webwindow.createGreeting = function (s) {return "Hello, " + s;};// Nodeglobal.createGreeting = function (s) {return "Hello, " + s;};// Potentially any runtimeglobalThis.createGreeting = function (s) {return "Hello, " + s;};
查看全局库的代码时,你通常会看到:
🌐 When looking at the code of a global library, you’ll usually see:
- 顶层的
var语句或function声明 - 一个或多个对
window.someName的赋值 - 假设 DOM 原语如
document或window存在
你不会看到:
🌐 You won’t see:
- 检查或使用诸如
require或define之类的模块加载器 - CommonJS/Node.js 风格的导入,形式为
var fs = require("fs"); - 对
define(...)的调用 - 文档描述了如何
require或导入该库
全局库的示例
🌐 Examples of Global Libraries
因为通常将一个全局库转换为 UMD 库很容易,所以很少有流行的库仍然以全局风格编写。然而,那些体积小且依赖 DOM(或没有任何依赖)的库仍可能是全局的。
🌐 Because it’s usually easy to turn a global library into a UMD library, very few popular libraries are still written in the global style. However, libraries that are small and require the DOM (or have no dependencies) may still be global.
全局库模板
🌐 Global Library Template
模板文件 global.d.ts 定义了一个示例库 myLib。请务必阅读“防止名称冲突”注释。
🌐 The template file global.d.ts defines an example library myLib.
Be sure to read the “Preventing Name Conflicts” footnote.
UMD
UMD 模块是指可以 既 作为模块使用(通过导入),也可以作为全局使用(在没有模块加载器的环境中运行时)。 许多流行的库,如 Moment.js,都是这种方式编写的。 例如,在 Node.js 或使用 RequireJS 时,你可以这样写:
🌐 A UMD module is one that can either be used as module (through an import), or as a global (when run in an environment without a module loader). Many popular libraries, such as Moment.js, are written this way. For example, in Node.js or using RequireJS, you would write:
tsimport moment = require("moment");console.log(moment.format());
而在 vanilla 浏览器环境中你会写:
🌐 whereas in a vanilla browser environment you would write:
jsconsole.log(moment.format());
识别 UMD 库
🌐 Identifying a UMD library
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 库。
🌐 If you see tests for typeof define, typeof window, or typeof module in the code of a library, especially at the top of the file, it’s almost always a UMD library.
UMD 库的文档通常还会演示一个“在 Node.js 中使用”的示例,展示 require,以及一个“在浏览器中使用”的示例,展示如何使用 <script> 标签来加载脚本。
🌐 Documentation for UMD libraries will also often demonstrate a “Using in Node.js” example showing require,
and a “Using in the browser” example showing using a <script> tag to load the script.
UMD 库的示例
🌐 Examples of UMD libraries
大多数流行的库现在都可以作为 UMD 包使用。例子包括 jQuery、Moment.js、lodash 等等。
🌐 Most popular libraries are now available as UMD packages. Examples include jQuery, Moment.js, lodash, and many more.
模板
🌐 Template
使用 module-plugin.d.ts 模板。
🌐 Use the module-plugin.d.ts template.
引用依赖
🌐 Consuming Dependencies
你的库可能有多种依赖。 本节将演示如何将它们导入到声明文件中。
🌐 There are several kinds of dependencies your library might have. This section shows how to import them into the declaration file.
对全局库的依赖
🌐 Dependencies on Global Libraries
如果你的库依赖于一个全局库,请使用 /// <reference types="..." /> 指令:
🌐 If your library depends on a global library, use a /// <reference types="..." /> directive:
ts/// <reference types="someLib" />function getThing(): someLib.thing;
对模块的依赖
🌐 Dependencies on Modules
如果你的库依赖于某个模块,请使用 import 语句:
🌐 If your library depends on a module, use an import statement:
tsimport * as moment from "moment";function getThing(): moment;
对 UMD 库的依赖
🌐 Dependencies on UMD libraries
来自全局库
🌐 From a Global Library
如果你的全局库依赖于 UMD 模块,请使用 /// <reference types 指令:
🌐 If your global library depends on a UMD module, use a /// <reference types directive:
ts/// <reference types="moment" />function getThing(): moment;
来自模块或 UMD 库
🌐 From a Module or UMD Library
如果你的模块或 UMD 库依赖于另一个 UMD 库,请使用 import 语句:
🌐 If your module or UMD library depends on a UMD library, use an import statement:
tsimport * as someLib from "someLib";
不要使用 /// <reference 指令来声明对 UMD 库的依赖!
🌐 Do not use a /// <reference directive to declare a dependency to a UMD library!
脚注
🌐 Footnotes
防止名称冲突
🌐 Preventing Name Conflicts
请注意,在编写全局声明文件时,可以在全局作用域中定义许多类型。我们强烈不建议这样做,因为当项目中存在许多声明文件时,这可能会导致无法解决的名称冲突。
🌐 Note that it’s possible to define many types in the global scope when writing a global declaration file. We strongly discourage this as it leads to possible unresolvable name conflicts when many declaration files are in a project.
一个简单的规则是,只声明由库定义的任何全局变量命名空间化的类型。 例如,如果库定义了全局值 ‘cats’,你应该这样写
🌐 A simple rule to follow is to only declare types namespaced by whatever global variable the library defines. For example, if the library defines the global value ‘cats’, you should write
tsdeclare namespace cats {interface KittySettings {}}
但是 不是
🌐 But not
ts// at top-levelinterface CatsKittySettings {}
该指南还确保可以在不中断声明文件用户的情况下将库转换为 UMD。
🌐 This guidance also ensures that the library can be transitioned to UMD without breaking declaration file users.
ES6 对模块调用签名的影响
🌐 The Impact of ES6 on Module Call Signatures
许多流行的库,例如 Express,在被导入时会以可调用函数的形式呈现。例如,典型的 Express 用法如下:
🌐 Many popular libraries, such as Express, expose themselves as a callable function when imported. For example, the typical Express usage looks like this:
tsimport exp = require("express");var app = exp();
在符合 ES6 的模块加载器中,顶层对象(这里导入为 exp)只能有属性;顶层模块对象绝不能被调用。
🌐 In ES6-compliant module loaders, the top-level object (here imported as exp) can only have properties;
the top-level module object can never be callable.
这里最常见的解决方案是为一个可调用/可构造的对象定义一个 default 导出;模块加载器通常会自动检测这种情况,并将顶层对象替换为 default 导出。如果你在 tsconfig.json 中有 "esModuleInterop": true,TypeScript 可以为你处理这个问题。
🌐 The most common solution here is to define a default export for a callable/constructable object;
module loaders commonly detect this situation automatically and replace the top-level object with the default export.
TypeScript can handle this for you, if you have "esModuleInterop": true in your tsconfig.json.