库结构

从广义上讲,构造声明文件的方式取决于库的使用方式。在 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.

每种类型的主要库结构模式在 模板 部分都有对应的文件。你可以从这些模板开始,以帮助你更快地开始。

¥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.

  1. 你如何获得库?

    ¥How do you obtain the library?

    例如,你只能通过 npm 或仅从 CDN 获取它吗?

    ¥For example, can you only get it through npm or only from a CDN?

  2. 你会如何导入它?

    ¥How would you import it?

    它是否添加了一个全局对象?它使用 requireimport/export 语句吗?

    ¥Does it add a global object? Does it use require or import/export statements?

不同类型库的较小样本

¥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

js
var fs = require("fs");

在 TypeScript 或 ES6 中,import 关键字的作用相同:

¥In TypeScript or ES6, the import keyword serves the same purpose:

ts
import * as fs from "fs";

你通常会看到模块化库在其文档中包含以下行之一:

¥You’ll typically see modular libraries include one of these lines in their documentation:

js
var someLib = require("someLib");

或者

¥or

js
define(..., ['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:

  • 无条件调用 requiredefine

    ¥Unconditional calls to require or define

  • import * as a from 'b';export c; 这样的声明

    ¥Declarations like import * as a from 'b'; or export c;

  • 分配给 exportsmodule.exports

    ¥Assignments to exports or module.exports

他们很少会:

¥They will rarely have:

  • windowglobal 的属性赋值

    ¥Assignments to properties of window or global

模块模板

¥Templates For Modules

有四个模板可供模块使用,module.d.tsmodule-class.d.tsmodule-function.d.tsmodule-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:

js
const x = require("foo");
// Note: calling 'x' as a function
const y = x(42);

如果你的模块可以使用 new 构建,请使用模板 module-class.d.ts

¥Use the template module-class.d.ts if your module can be constructed using new:

js
const x = require("bar");
// Note: using 'new' operator on the imported variable
const 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:

js
const 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:

js
function createGreeting(s) {
return "Hello, " + s;
}

或者像这样:

¥or like this:

js
// Web
window.createGreeting = function (s) {
return "Hello, " + s;
};
// Node
global.createGreeting = function (s) {
return "Hello, " + s;
};
// Potentially any runtime
globalThis.createGreeting = function (s) {
return "Hello, " + s;
};

查看全局库的代码时,你通常会看到:

¥When looking at the code of a global library, you’ll usually see:

  • 顶层 var 语句或 function 声明

    ¥Top-level var statements or function declarations

  • window.someName 的一项或多项任务

    ¥One or more assignments to window.someName

  • 假设存在像 documentwindow 这样的 DOM 基础类型

    ¥Assumptions that DOM primitives like document or window exist

你不会看到:

¥You won’t see:

  • 检查或使用模块加载器,如 requiredefine

    ¥Checks for, or usage of, module loaders like require or define

  • 表单 var fs = require("fs"); 的 CommonJS/Node.js 样式导入

    ¥CommonJS/Node.js-style imports of the form var fs = require("fs");

  • define(...) 调用

    ¥Calls to define(...)

  • 描述如何 require 或导入库的文档

    ¥Documentation describing how to require or import the library

全局库的示例

¥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:

ts
import moment = require("moment");
console.log(moment.format());

而在 vanilla 浏览器环境中你会写:

¥whereas in a vanilla browser environment you would write:

js
console.log(moment.format());

识别 UMD 库

¥Identifying a UMD library

UMD 模块 检查是否存在模块加载器环境。这是一个易于发现的模式,看起来像这样:

¥UMD modules check for the existence of a module loader environment. This is an easy-to-spot pattern that looks something like this:

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 definetypeof windowtypeof 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 库的文档通常还会演示一个显示 require 的 “在 Node.js 中使用” 示例,以及一个显示使用 <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 包使用。示例包括 jQueryMoment.jslodash 等等。

¥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:

ts
import * 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:

ts
import * 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

ts
declare namespace cats {
interface KittySettings {}
}

但不是

¥But not

ts
// at top-level
interface 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:

ts
import 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.