模块 - 参考

模块语法

¥Module syntax

TypeScript 编译器可识别 TypeScript 和 JavaScript 文件中的标准 ECMAScript 模块语法 以及 JavaScript 文件中多种形式的 CommonJS 语法

¥The TypeScript compiler recognizes standard ECMAScript module syntax in TypeScript and JavaScript files and many forms of CommonJS syntax in JavaScript files.

还有一些 TypeScript 特定的语法扩展可以在 TypeScript 文件和/或 JSDoc 注释中使用。

¥There are also a few TypeScript-specific syntax extensions that can be used in TypeScript files and/or JSDoc comments.

导入和导出特定于 TypeScript 的声明

¥Importing and exporting TypeScript-specific declarations

类型别名、接口、枚举和命名空间可以使用 export 修饰符从模块中导出,就像任何标准 JavaScript 声明一样:

¥Type aliases, interfaces, enums, and namespaces can be exported from a module with an export modifier, like any standard JavaScript declaration:

ts
// Standard JavaScript syntax...
export function f() {}
// ...extended to type declarations
export type SomeType = /* ... */;
export interface SomeInterface { /* ... */ }

它们也可以在命名导出中引用,甚至与标准 JavaScript 声明的引用一起引用:

¥They can also be referenced in named exports, even alongside references to standard JavaScript declarations:

ts
export { f, SomeType, SomeInterface };

导出的类型(以及其他 TypeScript 特定的声明)可以通过标准 ECMAScript 导入来导入:

¥Exported types (and other TypeScript-specific declarations) can be imported with standard ECMAScript imports:

ts
import { f, SomeType, SomeInterface } from "./module.js";

使用命名空间导入或导出时,在类型位置引用时导出的类型在命名空间上可用:

¥When using namespace imports or exports, exported types are available on the namespace when referenced in a type position:

ts
import * as mod from "./module.js";
mod.f();
mod.SomeType; // Property 'SomeType' does not exist on type 'typeof import("./module.js")'
let x: mod.SomeType; // Ok

仅类型导入和导出

¥Type-only imports and exports

默认情况下,当向 JavaScript 触发导入和导出时,TypeScript 会自动忽略(不触发)仅在类型位置中使用的导入和仅引用类型的导出。仅类型导入和导出可用于强制执行此行为并使省略变得明确。使用 import type 编写的导入声明、使用 export type { ... } 编写的导出声明以及以 type 关键字为前缀的导入或导出说明符都保证从输出 JavaScript 中删除。

¥When emitting imports and exports to JavaScript, by default, TypeScript automatically elides (does not emit) imports that are only used in type positions and exports that only refer to types. Type-only imports and exports can be used to force this behavior and make the elision explicit. Import declarations written with import type, export declarations written with export type { ... }, and import or export specifiers prefixed with the type keyword are all guaranteed to be elided from the output JavaScript.

ts
// @Filename: main.ts
import { f, type SomeInterface } from "./module.js";
import type { SomeType } from "./module.js";
class C implements SomeInterface {
constructor(p: SomeType) {
f();
}
}
export type { C };
// @Filename: main.js
import { f } from "./module.js";
class C {
constructor(p) {
f();
}
}

甚至可以使用 import type 导入值,但由于它们不会存在于输出 JavaScript 中,因此它们只能用在非触发位置:

¥Even values can be imported with import type, but since they won’t exist in the output JavaScript, they can only be used in non-emitting positions:

ts
import type { f } from "./module.js";
f(); // 'f' cannot be used as a value because it was imported using 'import type'
let otherFunction: typeof f = () => {}; // Ok

仅类型导入声明不能同时声明默认导入和命名绑定,因为 type 应用于默认导入还是整个导入声明似乎不明确。相反,将导入声明分成两部分,或使用 default 作为命名绑定:

¥A type-only import declaration may not declare both a default import and named bindings, since it appears ambiguous whether type applies to the default import or to the entire import declaration. Instead, split the import declaration into two, or use default as a named binding:

ts
import type fs, { BigIntOptions } from "fs";
// ^^^^^^^^^^^^^^^^^^^^^
// Error: A type-only import can specify a default import or named bindings, but not both.
import type { default as fs, BigIntOptions } from "fs"; // Ok

import() 类型

¥import() types

TypeScript 提供了类似于 JavaScript 的动态 import 的类型语法,用于引用模块的类型,而无需编写导入声明:

¥TypeScript provides a type syntax similar to JavaScript’s dynamic import for referencing the type of a module without writing an import declaration:

ts
// Access an exported type:
type WriteFileOptions = import("fs").WriteFileOptions;
// Access the type of an exported value:
type WriteFileFunction = typeof import("fs").writeFile;

这在 JavaScript 文件中的 JSDoc 注释中特别有用,否则无法导入类型:

¥This is especially useful in JSDoc comments in JavaScript files, where it’s not possible to import types otherwise:

ts
/** @type {import("webpack").Configuration} */
module.exports = {
// ...
}

export =import = require()

¥export = and import = require()

当触发 CommonJS 模块时,TypeScript 文件可以使用 module.exports = ...const mod = require("...") JavaScript 语法的直接模拟:

¥When emitting CommonJS modules, TypeScript files can use a direct analog of module.exports = ... and const mod = require("...") JavaScript syntax:

ts
// @Filename: main.ts
import fs = require("fs");
export = fs.readFileSync("...");
// @Filename: main.js
"use strict";
const fs = require("fs");
module.exports = fs.readFileSync("...");

此语法优于 JavaScript 语法,因为变量声明和属性赋值不能引用 TypeScript 类型,而特殊的 TypeScript 语法可以:

¥This syntax was used over its JavaScript counterparts since variable declarations and property assignments could not refer to TypeScript types, whereas special TypeScript syntax could:

ts
// @Filename: a.ts
interface Options { /* ... */ }
module.exports = Options; // Error: 'Options' only refers to a type, but is being used as a value here.
export = Options; // Ok
// @Filename: b.ts
const Options = require("./a");
const options: Options = { /* ... */ }; // Error: 'Options' refers to a value, but is being used as a type here.
// @Filename: c.ts
import Options = require("./a");
const options: Options = { /* ... */ }; // Ok

环境模块

¥Ambient modules

TypeScript 支持脚本(非模块)文件中的语法,用于声明运行时中存在但没有相应文件的模块。这些环境模块通常代表运行时提供的模块,例如 Node.js 中的 "fs""path"

¥TypeScript supports a syntax in script (non-module) files for declaring a module that exists in the runtime but has no corresponding file. These ambient modules usually represent runtime-provided modules, like "fs" or "path" in Node.js:

ts
declare module "path" {
export function normalize(p: string): string;
export function join(...paths: any[]): string;
export var sep: string;
}

一旦环境模块加载到 TypeScript 程序中,TypeScript 将识别其他文件中声明的模块的导入:

¥Once an ambient module is loaded into a TypeScript program, TypeScript will recognize imports of the declared module in other files:

ts
// 👇 Ensure the ambient module is loaded -
// may be unnecessary if path.d.ts is included
// by the project tsconfig.json somehow.
/// <reference path="path.d.ts" />
import { normalize, join } from "path";

环境模块声明很容易与 模块增强 混淆,因为它们使用相同的语法。当文件是模块时,此模块声明语法成为模块扩充,这意味着它具有顶层 importexport 语句(或受 --moduleDetection forceauto 影响):

¥Ambient module declarations are easy to confuse with module augmentations since they use identical syntax. This module declaration syntax becomes a module augmentation when the file is a module, meaning it has a top-level import or export statement (or is affected by --moduleDetection force or auto):

ts
// Not an ambient module declaration anymore!
export {};
declare module "path" {
export function normalize(p: string): string;
export function join(...paths: any[]): string;
export var sep: string;
}

环境模块可以在模块声明主体内使用导入来引用其他模块,而无需将包含文件转换为模块(这将使环境模块声明成为模块增强):

¥Ambient modules may use imports inside the module declaration body to refer to other modules without turning the containing file into a module (which would make the ambient module declaration a module augmentation):

ts
declare module "m" {
// Moving this outside "m" would totally change the meaning of the file!
import { SomeType } from "other";
export function f(): SomeType;
}

模式环境模块的名称中包含单个 * 通配符,与导入路径中的零个或多个字符匹配。这对于声明自定义加载器提供的模块很有用:

¥A pattern ambient module contains a single * wildcard character in its name, matching zero or more characters in import paths. This can be useful for declaring modules provided by custom loaders:

ts
declare module "*.html" {
const content: string;
export default content;
}

module 编译器选项

¥The module compiler option

本节讨论每个 module 编译器选项值的详细信息。有关该选项是什么以及它如何适应整个编译过程的更多背景信息,请参阅 模块输出格式 理论部分。简而言之,module 编译器选项历史上仅用于控制触发的 JavaScript 文件的输出模块格式。然而,最近的 node16nodenext 值描述了 Node.js 模块系统的广泛特性,包括支持哪些模块格式、如何确定每个文件的模块格式以及不同模块格式如何互操作。

¥This section discusses the details of each module compiler option value. See the Module output format theory section for more background on what the option is and how it fits into the overall compilation process. In brief, the module compiler option was historically only used to control the output module format of emitted JavaScript files. The more recent node16 and nodenext values, however, describe a wide range of characteristics of Node.js’s module system, including what module formats are supported, how the module format of each file is determined, and how different module formats interoperate.

node16, nodenext

Node.js 支持 CommonJS 和 ECMAScript 模块,并具有每个文件的格式以及如何允许这两种格式互操作的特定规则。node16nodenext 描述了 Node.js 双格式模块系统的全部行为,并以 CommonJS 或 ESM 格式触发文件。这与其他所有 module 选项不同,后者与运行时无关,并强制所有输出文件采用单一格式,由用户来确保输出对其运行时有效。

¥Node.js supports both CommonJS and ECMAScript modules, with specific rules for which format each file can be and how the two formats are allowed to interoperate. node16 and nodenext describe the full range of behavior for Node.js’s dual-format module system, and emit files in either CommonJS or ESM format. This is different from every other module option, which are runtime-agnostic and force all output files into a single format, leaving it to the user to ensure the output is valid for their runtime.

一个常见的误解是 node16nodenext 只触发 ES 模块。实际上,node16nodenext 描述的是支持 ES 模块的 Node.js 版本,而不仅仅是使用 ES 模块的项目。基于每个文件的 检测到的模块格式,ESM 和 CommonJS 触发都受支持。因为 node16nodenext 是唯一反映 Node.js 双模块系统复杂性的 module 选项,所以对于打算在 Node.js v12 或更高版本中运行的所有应用和库来说,它们是唯一正确的 module 选项,无论它们是否使用 ES 模块与否。

¥A common misconception is that node16 and nodenext only emit ES modules. In reality, node16 and nodenext describe versions of Node.js that support ES modules, not just projects that use ES modules. Both ESM and CommonJS emit are supported, based on the detected module format of each file. Because node16 and nodenext are the only module options that reflect the complexities of Node.js’s dual module system, they are the only correct module options for all apps and libraries that are intended to run in Node.js v12 or later, whether they use ES modules or not.

node16nodenext 目前相同,除了 暗示不同的 target 选项值。如果 Node.js 将来对其模块系统进行重大更改,node16 将被冻结,而 nodenext 将被更新以反映新行为。

¥node16 and nodenext are currently identical, with the exception that they imply different target option values. If Node.js makes significant changes to its module system in the future, node16 will be frozen while nodenext will be updated to reflect the new behavior.

模块格式检测

¥Module format detection

  • .mts/.mjs/.d.mts 文件始终是 ES 模块。

    ¥.mts/.mjs/.d.mts files are always ES modules.

  • .cts/.cjs/.d.cts 文件始终是 CommonJS 模块。

    ¥.cts/.cjs/.d.cts files are always CommonJS modules.

  • 如果最近的祖级 package.json 文件包含 "type": "module",则 .ts/.tsx/.js/.jsx/.d.ts 文件是 ES 模块,否则是 CommonJS 模块。

    ¥.ts/.tsx/.js/.jsx/.d.ts files are ES modules if the nearest ancestor package.json file contains "type": "module", otherwise CommonJS modules.

检测到的输入 .ts/.tsx/.mts/.cts 文件的模块格式决定了触发的 JavaScript 文件的模块格式。因此,例如,一个完全由 .ts 文件组成的项目将默认在 --module nodenext 下触发所有 CommonJS 模块,并且可以通过将 "type": "module" 添加到项目 package.json 来触发所有 ES 模块。

¥The detected module format of input .ts/.tsx/.mts/.cts files determines the module format of the emitted JavaScript files. So, for example, a project consisting entirely of .ts files will emit all CommonJS modules by default under --module nodenext, and can be made to emit all ES modules by adding "type": "module" to the project package.json.

互操作性规则

¥Interoperability rules

  • 当 ES 模块引用 CommonJS 模块时:

    ¥When an ES module references a CommonJS module:

    • CommonJS 模块的 module.exports 可作为 ES 模块的默认导入。

      ¥The module.exports of the CommonJS module is available as a default import to the ES module.

    • CommonJS 模块的 module.exports 的属性(default 除外)可能会也可能不会作为 ES 模块的命名导入提供。Node.js 尝试通过 静态分析 提供它们。TypeScript 无法从声明文件中知道静态分析是否会成功,并且乐观地假设它会成功。这限制了 TypeScript 捕获可能在运行时崩溃的命名导入的能力。有关详细信息,请参阅 #54018

      ¥Properties (other than default) of the CommonJS module’s module.exports may or may not be available as named imports to the ES module. Node.js attempts to make them available via static analysis. TypeScript cannot know from a declaration file whether that static analysis will succeed, and optimistically assumes it will. This limits TypeScript’s ability to catch named imports that may crash at runtime. See #54018 for more details.

  • 当 CommonJS 模块引用 ES 模块时:

    ¥When a CommonJS module references an ES module:

    • require 无法引用 ES 模块。对于 TypeScript,这包括 detected 为 CommonJS 模块的文件中的 import 语句,因为这些 import 语句将在触发的 JavaScript 中转换为 require 调用。

      ¥require cannot reference an ES module. For TypeScript, this includes import statements in files that are detected to be CommonJS modules, since those import statements will be transformed to require calls in the emitted JavaScript.

    • 动态 import() 调用可用于导入 ES 模块。它返回模块的模块命名空间对象的 Promise(你可以从另一个 ES 模块的 import * as ns from "./module.js" 获得的内容)。

      ¥A dynamic import() call may be used to import an ES module. It returns a Promise of the module’s Module Namespace Object (what you’d get from import * as ns from "./module.js" from another ES module).

触发

¥Emit

每个文件的 emit 格式由每个文件的 检测到的模块格式 决定。ESM emit 与 --module esnext 类似,但对 import x = require("...") 有特殊的转换,这在 --module esnext 中是不允许的:

¥The emit format of each file is determined by the detected module format of each file. ESM emit is similar to --module esnext, but has a special transformation for import x = require("..."), which is not allowed in --module esnext:

ts
// @Filename: main.ts
import x = require("mod");
js
// @Filename: main.js
import { createRequire as _createRequire } from "module";
const __require = _createRequire(import.meta.url);
const x = __require("mod");

CommonJS 的 emit 与 --module commonjs 类似,但动态 import() 调用没有转换。此处的触发显示启用了 esModuleInterop

¥CommonJS emit is similar to --module commonjs, but dynamic import() calls are not transformed. Emit here is shown with esModuleInterop enabled:

ts
// @Filename: main.ts
import fs from "fs"; // transformed
const dynamic = import("mod"); // not transformed
js
// @Filename: main.js
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs_1 = __importDefault(require("fs")); // transformed
const dynamic = import("mod"); // not transformed

隐式和强制选项

¥Implied and enforced options

  • --module nodenextnode16 暗示并强制执行同名的 moduleResolution

    ¥--module nodenext or node16 implies and enforces the moduleResolution with the same name.

  • --module nodenext 意味着 --target esnext

    ¥--module nodenext implies --target esnext.

  • --module node16 意味着 --target es2022

    ¥--module node16 implies --target es2022.

  • --module nodenextnode16 意味着 --esModuleInterop

    ¥--module nodenext or node16 implies --esModuleInterop.

概括

¥Summary

  • 对于打算在 Node.js v12 或更高版本中运行的所有应用和库,无论它们是否使用 ES 模块,node16nodenext 是唯一正确的 module 选项。

    ¥node16 and nodenext are the only correct module options for all apps and libraries that are intended to run in Node.js v12 or later, whether they use ES modules or not.

  • node16nodenext 根据每个文件的 检测到的模块格式 以 CommonJS 或 ESM 格式触发文件。

    ¥node16 and nodenext emit files in either CommonJS or ESM format, based on the detected module format of each file.

  • Node.js ESM 和 CJS 之间的互操作规则体现在类型检查中。

    ¥Node.js’s interoperability rules between ESM and CJS are reflected in type checking.

  • ESM 触发将 import x = require("...") 转换为从 createRequire 导入构造的 require 调用。

    ¥ESM emit transforms import x = require("...") to a require call constructed from a createRequire import.

  • CommonJS 触发未转换的动态 import() 调用,因此 CommonJS 模块可以异步导入 ES 模块。

    ¥CommonJS emit leaves dynamic import() calls untransformed, so CommonJS modules can asynchronously import ES modules.

preserve

--module preserve(TypeScript 5.4 中的 added)中,在输入文件中编写的 ECMAScript 导入和导出将保留在输出中,并且 CommonJS 样式的 import x = require("...")export = ... 语句将作为 CommonJS requiremodule.exports 触发。换句话说,每个单独的导入或导出语句的格式都会被保留,而不是被强制转换为整个编译(甚至整个文件)的单一格式。

¥In --module preserve (added in TypeScript 5.4), ECMAScript imports and exports written in input files are preserved in the output, and CommonJS-style import x = require("...") and export = ... statements are emitted as CommonJS require and module.exports. In other words, the format of each individual import or export statement is preserved, rather than being coerced into a single format for the whole compilation (or even a whole file).

虽然很少需要在同一文件中混合导入和 require 调用,但这种 module 模式最好地反映了大多数现代打包器以及 Bun 运行时的功能。

¥While it’s rare to need to mix imports and require calls in the same file, this module mode best reflects the capabilities of most modern bundlers, as well as the Bun runtime.

为什么要关心 TypeScript 使用打包器或 Bun 触发的 module,你可能还会设置 noEmit?TypeScript 的类型检查和模块解析行为受到其触发的模块格式的影响。设置 module 为 TypeScript 提供有关打包程序或运行时如何处理导入和导出的信息,这确保你在导入值上看到的类型准确反映运行时或打包后将发生的情况。更多讨论请参见 --moduleResolution bundler

¥Why care about TypeScript’s module emit with a bundler or with Bun, where you’re likely also setting noEmit? TypeScript’s type checking and module resolution behavior are affected by the module format that it would emit. Setting module gives TypeScript information about how your bundler or runtime will process imports and exports, which ensures that the types you see on imported values accurately reflect what will happen at runtime or after bundling. See --moduleResolution bundler for more discussion.

示例

¥Examples

ts
import x, { y, z } from "mod";
import mod = require("mod");
const dynamic = import("mod");
export const e1 = 0;
export default "default export";
js
import x, { y, z } from "mod";
const mod = require("mod");
const dynamic = import("mod");
export const e1 = 0;
export default "default export";

隐式和强制选项

¥Implied and enforced options

  • --module preserve 意味着 --moduleResolution bundler

    ¥--module preserve implies --moduleResolution bundler.

  • --module preserve 意味着 --esModuleInterop

    ¥--module preserve implies --esModuleInterop.

默认情况下,选项 --esModuleInterop--module preserve 中启用,仅用于其 类型检查 行为。由于导入永远不会转换为 --module preserve 中的 require 调用,因此 --esModuleInterop 不会影响触发的 JavaScript。

¥The option --esModuleInterop is enabled by default in --module preserve only for its type checking behavior. Since imports never transform into require calls in --module preserve, --esModuleInterop does not affect the emitted JavaScript.

es2015, es2020, es2022, esnext

概括

¥Summary

  • esnext--moduleResolution bundler 用于打包器、Bun 和 tsx。

    ¥Use esnext with --moduleResolution bundler for bundlers, Bun, and tsx.

  • 请勿用于 Node.js。在 package.json 中使用 node16nodenext"type": "module" 来为 Node.js 触发 ES 模块。

    ¥Do not use for Node.js. Use node16 or nodenext with "type": "module" in package.json to emit ES modules for Node.js.

  • 非声明文件中不允许使用 import mod = require("mod")

    ¥import mod = require("mod") is not allowed in non-declaration files.

  • es2020 添加了对 import.meta 属性的支持。

    ¥es2020 adds support for import.meta properties.

  • es2022 添加了对顶层 await 的支持。

    ¥es2022 adds support for top-level await.

  • esnext 是一个移动目标,可能包括对 ECMAScript 模块的第 3 阶段提案的支持。

    ¥esnext is a moving target that may include support for Stage 3 proposals to ECMAScript modules.

  • 触发的文件是 ES 模块,但依赖可以是任何格式。

    ¥Emitted files are ES modules, but dependencies may be any format.

示例

¥Examples

ts
// @Filename: main.ts
import x, { y, z } from "mod";
import * as mod from "mod";
const dynamic = import("mod");
console.log(x, y, z, mod, dynamic);
export const e1 = 0;
export default "default export";
js
// @Filename: main.js
import x, { y, z } from "mod";
import * as mod from "mod";
const dynamic = import("mod");
console.log(x, y, z, mod, dynamic);
export const e1 = 0;
export default "default export";

commonjs

概括

¥Summary

  • 你可能不应该使用这个。使用 node16nodenext 为 Node.js 触发 CommonJS 模块。

    ¥You probably shouldn’t use this. Use node16 or nodenext to emit CommonJS modules for Node.js.

  • 触发的文件是 CommonJS 模块,但依赖可以是任何格式。

    ¥Emitted files are CommonJS modules, but dependencies may be any format.

  • 动态 import() 转换为 require() 调用的 Promise。

    ¥Dynamic import() is transformed to a Promise of a require() call.

  • esModuleInterop 影响默认和命名空间导入的输出代码。

    ¥esModuleInterop affects the output code for default and namespace imports.

示例

¥Examples

输出显示为 esModuleInterop: false

¥Output is shown with esModuleInterop: false.

ts
// @Filename: main.ts
import x, { y, z } from "mod";
import * as mod from "mod";
const dynamic = import("mod");
console.log(x, y, z, mod, dynamic);
export const e1 = 0;
export default "default export";
js
// @Filename: main.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.e1 = void 0;
const mod_1 = require("mod");
const mod = require("mod");
const dynamic = Promise.resolve().then(() => require("mod"));
console.log(mod_1.default, mod_1.y, mod_1.z, mod);
exports.e1 = 0;
exports.default = "default export";
ts
// @Filename: main.ts
import mod = require("mod");
console.log(mod);
export = {
p1: true,
p2: false
};
js
// @Filename: main.js
"use strict";
const mod = require("mod");
console.log(mod);
module.exports = {
p1: true,
p2: false
};

system

概括

¥Summary

示例

¥Examples

ts
// @Filename: main.ts
import x, { y, z } from "mod";
import * as mod from "mod";
const dynamic = import("mod");
console.log(x, y, z, mod, dynamic);
export const e1 = 0;
export default "default export";
js
// @Filename: main.js
System.register(["mod"], function (exports_1, context_1) {
"use strict";
var mod_1, mod, dynamic, e1;
var __moduleName = context_1 && context_1.id;
return {
setters: [
function (mod_1_1) {
mod_1 = mod_1_1;
mod = mod_1_1;
}
],
execute: function () {
dynamic = context_1.import("mod");
console.log(mod_1.default, mod_1.y, mod_1.z, mod, dynamic);
exports_1("e1", e1 = 0);
exports_1("default", "default export");
}
};
});

amd

概括

¥Summary

  • 专为 RequireJS 等 AMD 加载器而设计。

    ¥Designed for AMD loaders like RequireJS.

  • 你可能不应该使用这个。请改用打包器。

    ¥You probably shouldn’t use this. Use a bundler instead.

  • 触发的文件是 AMD 模块,但依赖可以是任何格式。

    ¥Emitted files are AMD modules, but dependencies may be any format.

  • 支持 outFile

    ¥Supports outFile.

示例

¥Examples

ts
// @Filename: main.ts
import x, { y, z } from "mod";
import * as mod from "mod";
const dynamic = import("mod");
console.log(x, y, z, mod, dynamic);
export const e1 = 0;
export default "default export";
js
// @Filename: main.js
define(["require", "exports", "mod", "mod"], function (require, exports, mod_1, mod) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.e1 = void 0;
const dynamic = new Promise((resolve_1, reject_1) => { require(["mod"], resolve_1, reject_1); });
console.log(mod_1.default, mod_1.y, mod_1.z, mod, dynamic);
exports.e1 = 0;
exports.default = "default export";
});

umd

概括

¥Summary

  • 专为 AMD 或 CommonJS 加载器设计。

    ¥Designed for AMD or CommonJS loaders.

  • 不像大多数其他 UMD 封装器那样公开全局变量。

    ¥Does not expose a global variable like most other UMD wrappers.

  • 你可能不应该使用这个。请改用打包器。

    ¥You probably shouldn’t use this. Use a bundler instead.

  • 触发的文件是 UMD 模块,但依赖可以是任何格式。

    ¥Emitted files are UMD modules, but dependencies may be any format.

示例

¥Examples

ts
// @Filename: main.ts
import x, { y, z } from "mod";
import * as mod from "mod";
const dynamic = import("mod");
console.log(x, y, z, mod, dynamic);
export const e1 = 0;
export default "default export";
js
// @Filename: main.js
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "mod", "mod"], factory);
}
})(function (require, exports) {
"use strict";
var __syncRequire = typeof module === "object" && typeof module.exports === "object";
Object.defineProperty(exports, "__esModule", { value: true });
exports.e1 = void 0;
const mod_1 = require("mod");
const mod = require("mod");
const dynamic = __syncRequire ? Promise.resolve().then(() => require("mod")) : new Promise((resolve_1, reject_1) => { require(["mod"], resolve_1, reject_1); });
console.log(mod_1.default, mod_1.y, mod_1.z, mod, dynamic);
exports.e1 = 0;
exports.default = "default export";
});

moduleResolution 编译器选项

¥The moduleResolution compiler option

本节介绍多种 moduleResolution 模式共享的模块解析功能和流程,然后指定每种模式的详细信息。有关该选项是什么以及它如何适应整个编译过程的更多背景信息,请参阅 模块解析 理论部分。简而言之,moduleResolution 控制 TypeScript 如何将模块说明符(import/export/require 语句中的字符串字面量)解析为磁盘上的文件,并且应设置为与目标运行时或打包程序使用的模块解析器相匹配。

¥This section describes module resolution features and processes shared by multiple moduleResolution modes, then specifies the details of each mode. See the Module resolution theory section for more background on what the option is and how it fits into the overall compilation process. In brief, moduleResolution controls how TypeScript resolves module specifiers (string literals in import/export/require statements) to files on disk, and should be set to match the module resolver used by the target runtime or bundler.

共同特性和流程

¥Common features and processes

文件扩展名替换

¥File extension substitution

TypeScript 始终希望在内部解析为可以提供类型信息的文件,同时确保运行时或打包器可以使用相同的路径解析为提供 JavaScript 实现的文件。对于根据指定的 moduleResolution 算法触发在运行时或打包器中查找 JavaScript 文件的任何模块说明符,TypeScript 将首先尝试查找具有相同名称和类似文件扩展名的 TypeScript 实现文件或类型声明文件。

¥TypeScript always wants to resolve internally to a file that can provide type information, while ensuring that the runtime or bundler can use the same path to resolve to a file that provides a JavaScript implementation. For any module specifier that would, according to the moduleResolution algorithm specified, trigger a lookup of a JavaScript file in the runtime or bundler, TypeScript will first try to find a TypeScript implementation file or type declaration file with the same name and analagous file extension.

运行时查找 TypeScript 查找 #1 TypeScript 查找 #2 TypeScript 查找#3 TypeScript 查找#4 TypeScript 查找 #5
/mod.js /mod.ts /mod.tsx /mod.d.ts /mod.js ./mod.jsx
/mod.mjs /mod.mts /mod.d.mts /mod.mjs
/mod.cjs /mod.cts /mod.d.cts /mod.cjs

请注意,此行为与导入中写入的实际模块说明符无关。这意味着即使模块说明符显式使用 .js 文件扩展名,TypeScript 也可以解析为 .ts.d.ts 文件:

¥Note that this behavior is independent of the actual module specifier written in the import. This means that TypeScript can resolve to a .ts or .d.ts file even if the module specifier explicitly uses a .js file extension:

ts
import x from "./mod.js";
// Runtime lookup: "./mod.js"
// TypeScript lookup #1: "./mod.ts"
// TypeScript lookup #2: "./mod.d.ts"
// TypeScript lookup #3: "./mod.js"

请参阅 TypeScript 模仿宿主的模块解析,但具有类型 了解 TypeScript 模块解析为何以这种方式工作的解释。

¥See TypeScript imitates the host’s module resolution, but with types for an explanation of why TypeScript’s module resolution works this way.

相对文件路径解析

¥Relative file path resolution

所有 TypeScript 的 moduleResolution 算法都支持通过包含文件扩展名的相对路径引用模块(将根据 上述规则 进行替换):

¥All of TypeScript’s moduleResolution algorithms support referencing a module by a relative path that includes a file extension (which will be substituted according to the rules above):

ts
// @Filename: a.ts
export {};
// @Filename: b.ts
import {} from "./a.js"; // ✅ Works in every `moduleResolution`

无扩展相对路径

¥Extensionless relative paths

在某些情况下,运行时或打包程序允许从相对路径中省略 .js 文件扩展名。TypeScript 支持此行为,其中 moduleResolution 设置和上下文表明运行时或打包程序支持它:

¥In some cases, the runtime or bundler allows omitting a .js file extension from a relative path. TypeScript supports this behavior where the moduleResolution setting and the context indicate that the runtime or bundler supports it:

ts
// @Filename: a.ts
export {};
// @Filename: b.ts
import {} from "./a";

如果 TypeScript 确定运行时将在给定模块说明符 "./a" 的情况下执行 ./a.js 的查找,则 ./a.js 将经历 扩展替换,并解析为本示例中的文件 a.ts

¥If TypeScript determines that the runtime will perform a lookup for ./a.js given the module specifier "./a", then ./a.js will undergo extension substitution, and resolve to the file a.ts in this example.

Node.js 中的 import 路径不支持无扩展名相对路径,并且 package.json 文件中指定的文件路径并不总是支持无扩展名相对路径。TypeScript 目前从不支持省略 .mjs/.mts.cjs/.cts 文件扩展名,尽管某些运行时和打包器支持。

¥Extensionless relative paths are not supported in import paths in Node.js, and are not always supported in file paths specified in package.json files. TypeScript currently never supports omitting a .mjs/.mts or .cjs/.cts file extension, even though some runtimes and bundlers do.

目录模块(索引文件解析)

¥Directory modules (index file resolution)

在某些情况下,可以将目录(而不是文件)作为模块引用。在最简单和最常见的情况下,这涉及运行时或打包程序在目录中查找 index.js 文件。TypeScript 支持此行为,其中 moduleResolution 设置和上下文表明运行时或打包程序支持它:

¥In some cases, a directory, rather than a file, can be referenced as a module. In the simplest and most common case, this involves the runtime or bundler looking for an index.js file in a directory. TypeScript supports this behavior where the moduleResolution setting and the context indicate that the runtime or bundler supports it:

ts
// @Filename: dir/index.ts
export {};
// @Filename: b.ts
import {} from "./dir";

如果 TypeScript 确定运行时将在给定模块说明符 "./dir" 的情况下执行 ./dir/index.js 的查找,则 ./dir/index.js 将经历 扩展替换,并解析为本示例中的文件 dir/index.ts

¥If TypeScript determines that the runtime will perform a lookup for ./dir/index.js given the module specifier "./dir", then ./dir/index.js will undergo extension substitution, and resolve to the file dir/index.ts in this example.

目录模块还可能包含 package.json 文件,其中支持 "main""types" 字段的解析,并优先于 index.js 查找。目录模块也支持 "typesVersions" 字段。

¥Directory modules may also contain a package.json file, where resolution of the "main" and "types" fields are supported, and take precedence over index.js lookups. The "typesVersions" field is also supported in directory modules.

请注意,目录模块与 node_modules 不同,仅支持包可用功能的子集,并且在某些上下文中根本不支持。Node.js 将它们视为 旧版特性

¥Note that directory modules are not the same as node_modules packages and only support a subset of the features available to packages, and are not supported at all in some contexts. Node.js considers them a legacy feature.

paths

概述

¥Overview

TypeScript 提供了一种使用 paths 编译器选项覆盖编译器对裸说明符的模块解析的方法。虽然该功能最初设计为与 AMD 模块加载器(一种在 ESM 存在或打包器广泛使用之前在浏览器中运行模块的方法)一起使用,但如今当运行时或打包器支持 TypeScript 所支持的模块解析功能时,它仍然有用。 不是模型。例如,当使用 --experimental-network-imports 运行 Node.js 时,你可以为特定的 https:// 导入手动指定本地类型定义文件:

¥TypeScript offers a way to override the compiler’s module resolution for bare specifiers with the paths compiler option. While the feature was originally designed to be used with the AMD module loader (a means of running modules in the browser before ESM existed or bundlers were widely used), it still has uses today when a runtime or bundler supports module resolution features that TypeScript does not model. For example, when running Node.js with --experimental-network-imports, you can manually specify a local type definition file for a specific https:// import:

json
{
"compilerOptions": {
"module": "nodenext",
"paths": {
"https://esm.sh/lodash@4.17.21": ["./node_modules/@types/lodash/index.d.ts"]
}
}
}
ts
// Typed by ./node_modules/@types/lodash/index.d.ts due to `paths` entry
import { add } from "https://esm.sh/lodash@4.17.21";

使用打包器构建的应用通常会在其打包器配置中定义便利路径别名,然后使用 paths 通知 TypeScript 这些别名:

¥It’s also common for apps built with bundlers to define convenience path aliases in their bundler configuration, and then inform TypeScript of those aliases with paths:

json
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler",
"paths": {
"@app/*": ["./src/*"]
}
}
}

paths 不影响触发

¥paths does not affect emit

paths 选项不会更改 TypeScript 触发的代码中的导入路径。因此,创建看似在 TypeScript 中工作但在运行时会崩溃的路径别名非常容易:

¥The paths option does not change the import path in the code emitted by TypeScript. Consequently, it’s very easy to create path aliases that appear to work in TypeScript but will crash at runtime:

json
{
"compilerOptions": {
"module": "nodenext",
"paths": {
"node-has-no-idea-what-this-is": ["./oops.ts"]
}
}
}
ts
// TypeScript: ✅
// Node.js: 💥
import {} from "node-has-no-idea-what-this-is";

虽然打包应用可以设置 paths,但发布的库不要设置 paths 是非常重要的,因为如果用户没有为 TypeScript 及其打包器设置相同的别名,则触发的 JavaScript 将无法为库的使用者工作。库和应用都可以将 package.json "imports" 视为方便 paths 别名的标准替代品。

¥While it’s ok for bundled apps to set up paths, it’s very important that published libraries do not, since the emitted JavaScript will not work for consumers of the library without those users setting up the same aliases for both TypeScript and their bundler. Both libraries and apps can consider package.json "imports" as a standard replacement for convenience paths aliases.

paths 不应指向大仓包或 node_modules 包

¥paths should not point to monorepo packages or node_modules packages

虽然与 paths 别名匹配的模块说明符是裸说明符,但解析别名后,模块解析将在解析的路径上作为相对路径进行。因此,当 paths 别名匹配时,针对 node_modules 包查找 发生的解析功能(包括 package.json "exports" 字段支持)不会生效。如果 paths 用于指向 node_modules 包,这可能会导致令人惊讶的行为:

¥While module specifiers that match paths aliases are bare specifiers, once the alias is resolved, module resolution proceeds on the resolved path as a relative path. Consequently, resolution features that happen for node_modules package lookups, including package.json "exports" field support, do not take effect when a paths alias is matched. This can lead to surprising behavior if paths is used to point to a node_modules package:

ts
{
"compilerOptions": {
"paths": {
"pkg": ["./node_modules/pkg/dist/index.d.ts"],
"pkg/*": ["./node_modules/pkg/*"]
}
}
}

虽然此配置可能会模拟包解析的某些行为,但它会覆盖包的 package.json 文件定义的任何 maintypesexportstypesVersions,并且从包导入可能会在运行时失败。

¥While this configuration may simulate some of the behavior of package resolution, it overrides any main, types, exports, and typesVersions the package’s package.json file defines, and imports from the package may fail at runtime.

同样的警告也适用于在单一存储库中相互引用的包。与其使用 paths 使 TypeScript 人为地将 "@my-scope/lib" 解析为同级包,不如使用工作区通过 npmyarnpnpm 将包符号链接到 node_modules,这样 TypeScript 和运行时或打包器都会执行真正的 node_modules 包查找。如果 monorepo 包将发布到 npm,这一点尤其重要 - 用户安装后,这些包将通过 node_modules 包查找相互引用,并且使用工作区允许你在本地开发期间测试该行为。

¥The same caveat applies to packages referencing each other in a monorepo. Instead of using paths to make TypeScript artificially resolve "@my-scope/lib" to a sibling package, it’s best to use workspaces via npm, yarn, or pnpm to symlink your packages into node_modules, so both TypeScript and the runtime or bundler perform real node_modules package lookups. This is especially important if the monorepo packages will be published to npm—the packages will reference each other via node_modules package lookups once installed by users, and using workspaces allows you to test that behavior during local development.

baseUrl 的关系

¥Relationship to baseUrl

当提供 baseUrl 时,每个 paths 数组中的值将相对于 baseUrl 进行解析。否则,它们将相对于定义它们的 tsconfig.json 文件进行解析。

¥When baseUrl is provided, the values in each paths array are resolved relative to the baseUrl. Otherwise, they are resolved relative to the tsconfig.json file that defines them.

通配符替换

¥Wildcard substitutions

paths 模式可以包含单个 * 通配符,它与任何字符串匹配。然后可以在文件路径值中使用 * 标记来替换匹配的字符串:

¥paths patterns can contain a single * wildcard, which matches any string. The * token can then be used in the file path values to substitute the matched string:

json
{
"compilerOptions": {
"paths": {
"@app/*": ["./src/*"]
}
}
}

解析 "@app/components/Button" 的导入时,TypeScript 将匹配 @app/*,将 * 绑定到 components/Button,然后尝试解析相对于 tsconfig.json 路径的路径 ./src/components/Button。根据 moduleResolution 设置,此查找的其余部分将遵循与任何其他 相对路径查找 相同的规则。

¥When resolving an import of "@app/components/Button", TypeScript will match on @app/*, binding * to components/Button, and then attempt to resolve the path ./src/components/Button relative to the tsconfig.json path. The remainder of this lookup will follow the same rules as any other relative path lookup according to the moduleResolution setting.

当多个模式与模块说明符匹配时,使用任何 * 标记之前具有最长匹配前缀的模式:

¥When multiple patterns match a module specifier, the pattern with the longest matching prefix before any * token is used:

json
{
"compilerOptions": {
"paths": {
"*": ["./src/foo/one.ts"],
"foo/*": ["./src/foo/two.ts"],
"foo/bar": ["./src/foo/three.ts"]
}
}
}

解析 "foo/bar" 的导入时,所有三个 paths 模式都匹配,但使用最后一个模式,因为 "foo/bar""foo/""" 长。

¥When resolving an import of "foo/bar", all three paths patterns match, but the last is used because "foo/bar" is longer than "foo/" and "".

回退

¥Fallbacks

可以为路径映射提供多个文件路径。如果一条路径解析失败,将尝试数组中的下一条路径,直到解析成功或到达数组末尾。

¥Multiple file paths can be provided for a path mapping. If resolution fails for one path, the next one in the array will be attempted until resolution succeeds or the end of the array is reached.

json
{
"compilerOptions": {
"paths": {
"*": ["./vendor/*", "./types/*"]
}
}
}

baseUrl

baseUrl 设计用于与 AMD 模块加载器一起使用。如果你不使用 AMD 模块加载器,你可能不应该使用 baseUrl。从 TypeScript 4.1 开始,baseUrl 不再需要使用 paths,并且不应仅用于设置从中解析 paths 值的目录。

¥baseUrl was designed for use with AMD module loaders. If you aren’t using an AMD module loader, you probably shouldn’t use baseUrl. Since TypeScript 4.1, baseUrl is no longer required to use paths and should not be used just to set the directory paths values are resolved from.

baseUrl 编译器选项可以与任何 moduleResolution 模式结合使用,并指定从中解析裸说明符(不以 ./..// 开头的模块说明符)的目录。在支持 moduleResolution 的模式中,baseUrl 的优先级高于 node_modules 包查找

¥The baseUrl compiler option can be combined with any moduleResolution mode and specifies a directory that bare specifiers (module specifiers that don’t begin with ./, ../, or /) are resolved from. baseUrl has a higher precedence than node_modules package lookups in moduleResolution modes that support them.

执行 baseUrl 查找时,解析按照与其他相对路径解析相同的规则进行。例如,在支持 无扩展相对路径moduleResolution 模式中,如果 baseUrl 设置为 /src,则模块说明符 "some-file" 可能解析为 /src/some-file.ts

¥When performing a baseUrl lookup, resolution proceeds with the same rules as other relative path resolutions. For example, in a moduleResolution mode that supports extensionless relative paths a module specifier "some-file" may resolve to /src/some-file.ts if baseUrl is set to /src.

相关模块说明符的解析永远不会受到 baseUrl 选项的影响。

¥Resolution of relative module specifiers are never affected by the baseUrl option.

node_modules 包查找

¥node_modules package lookups

Node.js 将非相对路径、绝对路径或 URL 的模块说明符视为对其在 node_modules 子目录中查找的包的引用。Bundlers 方便地采用了这种行为,允许用户使用相同的依赖管理系统,甚至通常是相同的依赖,就像在 Node.js 中一样。除了 classic 之外,所有 TypeScript 的 moduleResolution 选项都支持 node_modules 查找。(当其他解析方式失败时,classic 支持在 node_modules/@types 中查找包,但从不直接在 node_modules 中查找包。)每个 node_modules 包查找都具有以下结构(从更高优先级的裸说明符规则开始,例如 pathsbaseUrl、自名导入和 package.json "imports" 查找已用尽):

¥Node.js treats module specifiers that aren’t relative paths, absolute paths, or URLs as references to packages that it looks up in node_modules subdirectories. Bundlers conveniently adopted this behavior to allow their users to use the same dependency management system, and often even the same dependencies, as they would in Node.js. All of TypeScript’s moduleResolution options except classic support node_modules lookups. (classic supports lookups in node_modules/@types when other means of resolution fail, but never looks for packages in node_modules directly.) Every node_modules package lookup has the following structure (beginning after higher precedence bare specifier rules, like paths, baseUrl, self-name imports, and package.json "imports" lookups have been exhausted):

  1. 对于导入文件的每个祖级目录,如果其中存在 node_modules 目录:

    ¥For each ancestor directory of the importing file, if a node_modules directory exists within it:

    1. 如果 node_modules 内存在与包同名的目录:

      ¥If a directory with the same name as the package exists within node_modules:

      1. 尝试从包目录解析类型。

        ¥Attempt to resolve types from the package directory.

      2. 如果找到结果,则返回该结果并停止搜索。

        ¥If a result is found, return it and stop the search.

    2. 如果 node_modules/@types 内存在与包同名的目录:

      ¥If a directory with the same name as the package exists within node_modules/@types:

      1. 尝试从 @types 包目录解析类型。

        ¥Attempt to resolve types from the @types package directory.

      2. 如果找到结果,则返回该结果并停止搜索。

        ¥If a result is found, return it and stop the search.

  2. 对所有 node_modules 目录重复之前的搜索,但这一次,允许 JavaScript 文件作为结果,并且不在 @types 目录中搜索。

    ¥Repeat the previous search through all node_modules directories, but this time, allow JavaScript files as a result, and do not search in @types directories.

所有 moduleResolution 模式(classic 除外)都遵循此模式,但它们如何从包目录解析(一旦找到)的详细信息会有所不同,并将在以下部分中进行解释。

¥All moduleResolution modes (except classic) follow this pattern, while the details of how they resolve from a package directory, once located, differ, and are explained in the following sections.

package.json "exports"

moduleResolution 设置为 node16nodenextbundler,并且未禁用 resolvePackageJsonExports 时,TypeScript 在从 裸说明符 node_modules 包查找 触发的包目录解析时遵循 Node.js 的 package.json "exports" 规范

¥When moduleResolution is set to node16, nodenext, or bundler, and resolvePackageJsonExports is not disabled, TypeScript follows Node.js’s package.json "exports" spec when resolving from a package directory triggered by a bare specifier node_modules package lookup.

TypeScript 通过 "exports" 将模块说明符解析为文件路径的实现完全遵循 Node.js。然而,一旦文件路径被解析,TypeScript 仍将 尝试多个文件扩展名 以便优先考虑查找类型。

¥TypeScript’s implementation for resolving a module specifier through "exports" to a file path follows Node.js exactly. Once a file path is resolved, however, TypeScript will still try multiple file extensions in order to prioritize finding types.

通过 有条件 "exports" 解析时,TypeScript 始终匹配 "types""default" 条件(如果存在)。此外,TypeScript 将根据 "typesVersions" 中实现的相同版本匹配规则,匹配 "types@{selector}" 形式的版本化类型条件(其中 {selector}"typesVersions" 兼容的版本选择器)。其他不可配置的条件取决于 moduleResolution 模式并在以下部分中指定。可以配置其他条件以与 customConditions 编译器选项匹配。

¥When resolving through conditional "exports", TypeScript always matches the "types" and "default" conditions if present. Additionally, TypeScript will match a versioned types condition in the form "types@{selector}" (where {selector} is a "typesVersions"-compatible version selector) according to the same version-matching rules implemented in "typesVersions". Other non-configurable conditions are dependent on the moduleResolution mode and specified in the following sections. Additional conditions can be configured to match with the customConditions compiler option.

请注意,"exports" 的存在会阻止解析 "exports" 中的模式未显式列出或匹配的任何子路径。

¥Note that the presence of "exports" prevents any subpaths not explicitly listed or matched by a pattern in "exports" from being resolved.

示例:子路径、条件和扩展替换

¥Example: subpaths, conditions, and extension substitution

设想:使用以下 package.json 的包目录中的条件 ["types", "node", "require"](由 moduleResolution 设置和触发模块解析请求的上下文确定)请求 "pkg/subpath"

¥Scenario: "pkg/subpath" is requested with conditions ["types", "node", "require"] (determined by moduleResolution setting and the context that triggered the module resolution request) in a package directory with the following package.json:

json
{
"name": "pkg",
"exports": {
".": {
"import": "./index.mjs",
"require": "./index.cjs"
},
"./subpath": {
"import": "./subpath/index.mjs",
"require": "./subpath/index.cjs"
}
}
}

包目录内解析过程:

¥Resolution process within the package directory:

  1. "exports" 存在吗?是的。

    ¥Does "exports" exist? Yes.

  2. "exports""./subpath" 条目吗?是的。

    ¥Does "exports" have a "./subpath" entry? Yes.

  3. exports["./subpath"] 处的值是一个对象 - 它必须指定条件。

    ¥The value at exports["./subpath"] is an object—it must be specifying conditions.

  4. 第一个条件 "import" 是否符合此请求?不。

    ¥Does the first condition "import" match this request? No.

  5. 第二个条件 "require" 是否符合该请求?是的。

    ¥Does the second condition "require" match this request? Yes.

  6. 路径 "./subpath/index.cjs" 是否具有可识别的 TypeScript 文件扩展名?不,所以使用扩展名替换。

    ¥Does the path "./subpath/index.cjs" have a recognized TypeScript file extension? No, so use extension substitution.

  7. 通过 扩展替换,尝试以下路径,返回第一个存在的路径,否则返回 undefined

    ¥Via extension substitution, try the following paths, returning the first one that exists, or undefined otherwise:

    1. ./subpath/index.cts
    2. ./subpath/index.d.cts
    3. ./subpath/index.cjs

如果 ./subpath/index.cts./subpath.d.cts 存在,则解析完成。否则,解析将根据 node_modules 包查找 规则搜索 node_modules/@types/pkg 和其他 node_modules 目录以尝试解析类型。如果未找到类型,则第二次遍历所有 node_modules 时会解析为 ./subpath/index.cjs(假设存在),这算作成功解析,但不提供类型的解析会导致 any 类型的导入,如果启用,则会导致 noImplicitAny 错误。

¥If ./subpath/index.cts or ./subpath.d.cts exists, resolution is complete. Otherwise, resolution searches node_modules/@types/pkg and other node_modules directories in an attempt to resolve types, according to the node_modules package lookups rules. If no types are found, a second pass through all node_modules resolves to ./subpath/index.cjs (assuming it exists), which counts as a successful resolution, but one that does not provide types, leading to any-typed imports and a noImplicitAny error if enabled.

示例:显式 "types" 条件

¥Example: explicit "types" condition

设想:使用以下 package.json 的包目录中的条件 ["types", "node", "import"](由 moduleResolution 设置和触发模块解析请求的上下文确定)请求 "pkg/subpath"

¥Scenario: "pkg/subpath" is requested with conditions ["types", "node", "import"] (determined by moduleResolution setting and the context that triggered the module resolution request) in a package directory with the following package.json:

json
{
"name": "pkg",
"exports": {
"./subpath": {
"import": {
"types": "./types/subpath/index.d.mts",
"default": "./es/subpath/index.mjs"
},
"require": {
"types": "./types/subpath/index.d.cts",
"default": "./cjs/subpath/index.cjs"
}
}
}
}

包目录内解析过程:

¥Resolution process within the package directory:

  1. "exports" 存在吗?是的。

    ¥Does "exports" exist? Yes.

  2. "exports""./subpath" 条目吗?是的。

    ¥Does "exports" have a "./subpath" entry? Yes.

  3. exports["./subpath"] 处的值是一个对象 - 它必须指定条件。

    ¥The value at exports["./subpath"] is an object—it must be specifying conditions.

  4. 第一个条件 "import" 是否符合此请求?是的。

    ¥Does the first condition "import" match this request? Yes.

  5. exports["./subpath"].import 处的值是一个对象 - 它必须指定条件。

    ¥The value at exports["./subpath"].import is an object—it must be specifying conditions.

  6. 第一个条件 "types" 是否符合此请求?是的。

    ¥Does the first condition "types" match this request? Yes.

  7. 路径 "./types/subpath/index.d.mts" 是否具有可识别的 TypeScript 文件扩展名?是的,所以不要使用扩展名替换。

    ¥Does the path "./types/subpath/index.d.mts" have a recognized TypeScript file extension? Yes, so don’t use extension substitution.

  8. 如果文件存在则返回路径 "./types/subpath/index.d.mts",否则返回 undefined

    ¥Return the path "./types/subpath/index.d.mts" if the file exists, undefined otherwise.

示例:版本 "types" 条件

¥Example: versioned "types" condition

设想:使用 TypeScript 4.7.5,使用以下 package.json 的包目录中的条件 ["types", "node", "import"](由 moduleResolution 设置和触发模块解析请求的上下文确定)请求 "pkg/subpath"

¥Scenario: using TypeScript 4.7.5, "pkg/subpath" is requested with conditions ["types", "node", "import"] (determined by moduleResolution setting and the context that triggered the module resolution request) in a package directory with the following package.json:

json
{
"name": "pkg",
"exports": {
"./subpath": {
"types@>=5.2": "./ts5.2/subpath/index.d.ts",
"types@>=4.6": "./ts4.6/subpath/index.d.ts",
"types": "./tsold/subpath/index.d.ts",
"default": "./dist/subpath/index.js"
}
}
}

包目录内解析过程:

¥Resolution process within the package directory:

  1. "exports" 存在吗?是的。

    ¥Does "exports" exist? Yes.

  2. "exports""./subpath" 条目吗?是的。

    ¥Does "exports" have a "./subpath" entry? Yes.

  3. exports["./subpath"] 处的值是一个对象 - 它必须指定条件。

    ¥The value at exports["./subpath"] is an object—it must be specifying conditions.

  4. 第一个条件 "types@>=5.2" 是否符合此请求?不,4.7.5 不大于或等于 5.2。

    ¥Does the first condition "types@>=5.2" match this request? No, 4.7.5 is not greater than or equal to 5.2.

  5. 第二个条件 "types@>=4.6" 是否符合该请求?是的,4.7.5 大于或等于 4.6。

    ¥Does the second condition "types@>=4.6" match this request? Yes, 4.7.5 is greater than or equal to 4.6.

  6. 路径 "./ts4.6/subpath/index.d.ts" 是否具有可识别的 TypeScript 文件扩展名?是的,所以不要使用扩展名替换。

    ¥Does the path "./ts4.6/subpath/index.d.ts" have a recognized TypeScript file extension? Yes, so don’t use extension substitution.

  7. 如果文件存在则返回路径 "./ts4.6/subpath/index.d.ts",否则返回 undefined

    ¥Return the path "./ts4.6/subpath/index.d.ts" if the file exists, undefined otherwise.

示例:子路径模式

¥Example: subpath patterns

设想:使用以下 package.json 的包目录中的条件 ["types", "node", "import"](由 moduleResolution 设置和触发模块解析请求的上下文确定)请求 "pkg/wildcard.js"

¥Scenario: "pkg/wildcard.js" is requested with conditions ["types", "node", "import"] (determined by moduleResolution setting and the context that triggered the module resolution request) in a package directory with the following package.json:

json
{
"name": "pkg",
"type": "module",
"exports": {
"./*.js": {
"types": "./types/*.d.ts",
"default": "./dist/*.js"
}
}
}

包目录内解析过程:

¥Resolution process within the package directory:

  1. "exports" 存在吗?是的。

    ¥Does "exports" exist? Yes.

  2. "exports""./wildcard.js" 条目吗?不。

    ¥Does "exports" have a "./wildcard.js" entry? No.

  3. 任何带有 * 的键都与 "./wildcard.js" 匹配吗?是的,"./*.js" 匹配并将 wildcard 设置为替补。

    ¥Does any key with a * in it match "./wildcard.js"? Yes, "./*.js" matches and sets wildcard to be the substitution.

  4. exports["./*.js"] 处的值是一个对象 - 它必须指定条件。

    ¥The value at exports["./*.js"] is an object—it must be specifying conditions.

  5. 第一个条件 "types" 是否符合此请求?是的。

    ¥Does the first condition "types" match this request? Yes.

  6. ./types/*.d.ts 中,将 * 替换为替换 wildcard./types/wildcard.d.ts

    ¥In ./types/*.d.ts, replace * with the substitution wildcard. ./types/wildcard.d.ts

  7. 路径 "./types/wildcard.d.ts" 是否具有可识别的 TypeScript 文件扩展名?是的,所以不要使用扩展名替换。

    ¥Does the path "./types/wildcard.d.ts" have a recognized TypeScript file extension? Yes, so don’t use extension substitution.

  8. 如果文件存在则返回路径 "./types/wildcard.d.ts",否则返回 undefined

    ¥Return the path "./types/wildcard.d.ts" if the file exists, undefined otherwise.

示例:"exports" 阻止其他子路径

¥Example: "exports" block other subpaths

设想:在具有以下 package.json 的包目录中请求 "pkg/dist/index.js"

¥Scenario: "pkg/dist/index.js" is requested in a package directory with the following package.json:

json
{
"name": "pkg",
"main": "./dist/index.js",
"exports": "./dist/index.js"
}

包目录内解析过程:

¥Resolution process within the package directory:

  1. "exports" 存在吗?是的。

    ¥Does "exports" exist? Yes.

  2. exports 处的值是一个字符串 — 它必须是包根 (".") 的文件路径。

    ¥The value at exports is a string—it must be a file path for the package root (".").

  3. 请求 "pkg/dist/index.js" 是针对包根目录吗?不,它有一个子路径 dist/index.js

    ¥Is the request "pkg/dist/index.js" for the package root? No, it has a subpath dist/index.js.

  4. 解析失败;返回 undefined

    ¥Resolution fails; return undefined.

如果没有 "exports",请求可能会成功,但 "exports" 的存在会阻止解析无法通过 "exports" 匹配的任何子路径。

¥Without "exports", the request could have succeeded, but the presence of "exports" prevents resolving any subpaths that cannot be matched through "exports".

package.json "typesVersions"

node_modules目录模块 可以在其 package.json 中指定 "typesVersions" 字段,以根据 TypeScript 编译器版本重定向 TypeScript 的解析过程,对于 node_modules 包,根据正在解析的子路径。这允许包作者在一组类型定义中包含新的 TypeScript 语法,同时提供另一组类型定义以向后兼容旧的 TypeScript 版本(通过 downlevel-dts 等工具)。所有 moduleResolution 模式均支持 "typesVersions";但是,在读取 package.json "exports" 的情况下,不会读取该字段。

¥A node_modules package or directory module may specify a "typesVersions" field in its package.json to redirect TypeScript’s resolution process according to the TypeScript compiler version, and for node_modules packages, according to the subpath being resolved. This allows package authors to include new TypeScript syntax in one set of type definitions while providing another set for backward compatibility with older TypeScript versions (through a tool like downlevel-dts). "typesVersions" is supported in all moduleResolution modes; however, the field is not read in situations when package.json "exports" are read.

示例:将所有请求重定向到子目录

¥Example: redirect all requests to a subdirectory

设想:模块使用 TypeScript 5.2 导入 "pkg",其中 node_modules/pkg/package.json 是:

¥Scenario: a module imports "pkg" using TypeScript 5.2, where node_modules/pkg/package.json is:

json
{
"name": "pkg",
"version": "1.0.0",
"types": "./index.d.ts",
"typesVersions": {
">=3.1": {
"*": ["ts3.1/*"]
}
}
}

解决过程:

¥Resolution process:

  1. (取决于编译器选项)"exports" 存在吗?不。

    ¥(Depending on compiler options) Does "exports" exist? No.

  2. "typesVersions" 存在吗?是的。

    ¥Does "typesVersions" exist? Yes.

  3. TypeScript 版本是 >=3.1 吗?是的。记住映射 "*": ["ts3.1/*"]

    ¥Is the TypeScript version >=3.1? Yes. Remember the mapping "*": ["ts3.1/*"].

  4. 我们是否在包名称之后解析子路径?不,只是根 "pkg"

    ¥Are we resolving a subpath after the package name? No, just the root "pkg".

  5. "types" 存在吗?是的。

    ¥Does "types" exist? Yes.

  6. "typesVersions" 中的任何键是否与 ./index.d.ts 匹配?是的,"*" 匹配并将 index.d.ts 设置为替补。

    ¥Does any key in "typesVersions" match ./index.d.ts? Yes, "*" matches and sets index.d.ts to be the substitution.

  7. ts3.1/* 中,将 * 替换为替换 ./index.d.tsts3.1/index.d.ts

    ¥In ts3.1/*, replace * with the substitution ./index.d.ts: ts3.1/index.d.ts.

  8. 路径 ./ts3.1/index.d.ts 是否具有可识别的 TypeScript 文件扩展名?是的,所以不要使用扩展名替换。

    ¥Does the path ./ts3.1/index.d.ts have a recognized TypeScript file extension? Yes, so don’t use extension substitution.

  9. 如果文件存在则返回路径 ./ts3.1/index.d.ts,否则返回 undefined

    ¥Return the path ./ts3.1/index.d.ts if the file exists, undefined otherwise.

示例:重定向特定文件的请求

¥Example: redirect requests for a specific file

设想:模块使用 TypeScript 3.9 导入 "pkg",其中 node_modules/pkg/package.json 是:

¥Scenario: a module imports "pkg" using TypeScript 3.9, where node_modules/pkg/package.json is:

json
{
"name": "pkg",
"version": "1.0.0",
"types": "./index.d.ts",
"typesVersions": {
"<4.0": { "index.d.ts": ["index.v3.d.ts"] }
}
}

解决过程:

¥Resolution process:

  1. (取决于编译器选项)"exports" 存在吗?不。

    ¥(Depending on compiler options) Does "exports" exist? No.

  2. "typesVersions" 存在吗?是的。

    ¥Does "typesVersions" exist? Yes.

  3. TypeScript 版本是 <4.0 吗?是的。记住映射 "index.d.ts": ["index.v3.d.ts"]

    ¥Is the TypeScript version <4.0? Yes. Remember the mapping "index.d.ts": ["index.v3.d.ts"].

  4. 我们是否在包名称之后解析子路径?不,只是根 "pkg"

    ¥Are we resolving a subpath after the package name? No, just the root "pkg".

  5. "types" 存在吗?是的。

    ¥Does "types" exist? Yes.

  6. "typesVersions" 中的任何键是否与 ./index.d.ts 匹配?是的,"index.d.ts" 匹配。

    ¥Does any key in "typesVersions" match ./index.d.ts? Yes, "index.d.ts" matches.

  7. 路径 ./index.v3.d.ts 是否具有可识别的 TypeScript 文件扩展名?是的,所以不要使用扩展名替换。

    ¥Does the path ./index.v3.d.ts have a recognized TypeScript file extension? Yes, so don’t use extension substitution.

  8. 如果文件存在则返回路径 ./index.v3.d.ts,否则返回 undefined

    ¥Return the path ./index.v3.d.ts if the file exists, undefined otherwise.

package.json "main""types"

¥package.json "main" and "types"

如果未读取目录的 package.json "exports" 字段(由于编译器选项,或者因为它不存在,或者因为该目录被解析为 目录模块 而不是 node_modules)并且模块说明符在包名称后没有子路径 或包含 package.json 的目录,TypeScript 将尝试按顺序解析这些 package.json 字段,以尝试找到该包或目录的主模块:

¥If a directory’s package.json "exports" field is not read (either due to compiler options, or because it is not present, or because the directory is being resolved as a directory module instead of a node_modules package) and the module specifier does not have a subpath after the package name or package.json-containing directory, TypeScript will attempt to resolve from these package.json fields, in order, in an attempt to find the main module for the package or directory:

  • "types"

  • "typings"(旧版)

    ¥"typings" (legacy)

  • "main"

假定在 "types" 处找到的声明文件是在 "main" 处找到的实现文件的准确表示。如果 "types""typings" 不存在或无法解析,TypeScript 将读取 "main" 字段并执行 扩展替换 来查找声明文件。

¥The declaration file found at "types" is assumed to be an accurate representation of the implementation file found at "main". If "types" and "typings" are not present or cannot be resolved, TypeScript will read the "main" field and perform extension substitution to find a declaration file.

将类型化包发布到 npm 时,建议包含 "types" 字段,即使 扩展替换package.json "exports" 不需要它,因为仅当 package.json 包含 "types" 字段时,npm 才会在包注册表列表中显示 TS 图标。

¥When publishing a typed package to npm, it’s recommended to include a "types" field even if extension substitution or package.json "exports" make it unnecessary, because npm shows a TS icon on the package registry listing only if the package.json contains a "types" field.

包相对文件路径

¥Package-relative file paths

如果 package.json "exports"package.json "typesVersions" 都不适用,则根据适用的 相对路径 解析规则,裸包说明符的子路径将相对于包目录进行解析。在尊重 [package.json "exports"] 的模式中,即使导入无法通过 "exports" 解析(如 上面的例子 中所示),此行为也会因为包的 package.json 中存在 "exports" 字段而被阻止。另一方面,如果导入无法通过 "typesVersions" 解析,则会尝试使用包相对文件路径解析作为后备。

¥If neither package.json "exports" nor package.json "typesVersions" apply, subpaths of a bare package specifier resolve relative to the package directory, according to applicable relative path resolution rules. In modes that respect [package.json "exports"], this behavior is blocked by the mere presence of the "exports" field in the package’s package.json, even if the import fails to resolve through "exports", as demonstrated in an example above. On the other hand, if the import fails to resolve through "typesVersions", a package-relative file path resolution is attempted as a fallback.

当支持包相对路径时,考虑 moduleResolution 模式和上下文,它们按照与任何其他相对路径相同的规则进行解析。例如,在 --moduleResolution nodenext目录模块无扩展路径 中,仅在 require 调用中受支持,在 import 中不支持

¥When package-relative paths are supported, they resolve under the same rules as any other relative path considering the moduleResolution mode and context. For example, in --moduleResolution nodenext, directory modules and extensionless paths are only supported in require calls, not in imports:

ts
// @Filename: module.mts
import "pkg/dist/foo"; // ❌ import, needs `.js` extension
import "pkg/dist/foo.js"; // ✅
import foo = require("pkg/dist/foo"); // ✅ require, no extension needed

package.json "imports" 和自名导入

¥package.json "imports" and self-name imports

moduleResolution 设置为 node16nodenextbundler,并且 resolvePackageJsonImports 未被禁用时,TypeScript 将尝试通过导入文件的最近祖级 package.json 的 "imports" 字段来解析以 # 开头的导入路径。同样,当启用 package.json "exports" 查找 时,TypeScript 将尝试通过该包的 "exports" 字段解析以当前包名称(即导入文件的最近祖级 package.json 的 "name" 字段中的值)开头的导入路径。 json。这两个功能都允许包中的文件导入同一包中的其他文件,从而替换相对导入路径。

¥When moduleResolution is set to node16, nodenext, or bundler, and resolvePackageJsonImports is not disabled, TypeScript will attempt to resolve import paths beginning with # through the "imports" field of the nearest ancestor package.json of the importing file. Similarly, when package.json "exports" lookups are enabled, TypeScript will attempt to resolve import paths beginning with the current package name—that is, the value in the "name" field of the nearest ancestor package.json of the importing file—through the "exports" field of that package.json. Both of these features allow files in a package to import other files in the same package, replacing a relative import path.

TypeScript 完全遵循 Node.js 对 "imports"自我参考 的解析算法,直到解析文件路径。此时,TypeScript 的解析算法会根据包含正在解析的 "imports""exports" 的 package.json 是否属于 node_modules 依赖或正在编译的本地项目(即,其目录包含包含 "imports""exports" 的项目的 tsconfig.json 文件)进行分叉。 导入文件):

¥TypeScript follows Node.js’s resolution algorithm for "imports" and self references exactly up until a file path is resolved. At that point, TypeScript’s resolution algorithm forks based on whether the package.json containing the "imports" or "exports" being resolved belongs to a node_modules dependency or the local project being compiled (i.e., its directory contains the tsconfig.json file for the project that contains the importing file):

  • 如果 package.json 位于 node_modules 中,并且尚无可识别的 TypeScript 文件扩展名,TypeScript 会将 扩展替换 应用于该文件路径,并检查结果文件路径是否存在。

    ¥If the package.json is in node_modules, TypeScript will apply extension substitution to the file path if it doesn’t already have a recognized TypeScript file extension, and check for the existence of the resulting file paths.

  • 如果 package.json 是本地项目的一部分,则会执行额外的重新映射步骤,以便找到输入 TypeScript 实现文件,该文件最终将生成从 "imports" 解析的输出 JavaScript 或声明文件路径。如果没有此步骤,任何解析 "imports" 路径的编译都将引用先前编译的输出文件,而不是要包含在当前编译中的其他输入文件。此重新映射使用 tsconfig.json 中的 outDir/declarationDirrootDir,因此使用 "imports" 通常需要设置显式 rootDir

    ¥If the package.json is part of the local project, an additional remapping step is performed in order to find the input TypeScript implementation file that will eventually produce the output JavaScript or declaration file path that was resolved from "imports". Without this step, any compilation that resolves an "imports" path would be referencing output files from the previous compilation instead of other input files that are intended to be included in the current compilation. This remapping uses the outDir/declarationDir and rootDir from the tsconfig.json, so using "imports" usually requires an explicit rootDir to be set.

此变体允许包作者编写仅引用将发布到 npm 的编译输出的 "imports""exports" 字段,同时仍然允许本地开发使用原始 TypeScript 源文件。

¥This variation allows package authors to write "imports" and "exports" fields that reference only the compilation outputs that will be published to npm, while still allowing local development to use the original TypeScript source files.

示例:使用条件的本地项目

¥Example: local project with conditions

设想:"/src/main.mts" 在项目目录中导入带有条件 ["types", "node", "import"](由 moduleResolution 设置和触发模块解析请求的上下文确定)的 "#utils",其中包含 tsconfig.json 和 package.json:

¥Scenario: "/src/main.mts" imports "#utils" with conditions ["types", "node", "import"] (determined by moduleResolution setting and the context that triggered the module resolution request) in a project directory with a tsconfig.json and package.json:

json
// tsconfig.json
{
"compilerOptions": {
"moduleResolution": "node16",
"resolvePackageJsonImports": true,
"rootDir": "./src",
"outDir": "./dist"
}
}
json
// package.json
{
"name": "pkg",
"imports": {
"#utils": {
"import": "./dist/utils.d.mts",
"require": "./dist/utils.d.cts"
}
}
}

解决过程:

¥Resolution process:

  1. 导入路径以 # 开头,尝试通过 "imports" 解析。

    ¥Import path starts with #, try to resolve through "imports".

  2. 最近的祖级 package.json 中是否存在 "imports"?是的。

    ¥Does "imports" exist in the nearest ancestor package.json? Yes.

  3. "#utils" 是否存在于 "imports" 对象中?是的。

    ¥Does "#utils" exist in the "imports" object? Yes.

  4. imports["#utils"] 处的值是一个对象 - 它必须指定条件。

    ¥The value at imports["#utils"] is an object—it must be specifying conditions.

  5. 第一个条件 "import" 是否符合此请求?是的。

    ¥Does the first condition "import" match this request? Yes.

  6. 我们是否应该尝试将输出路径映射到输入路径?是的,因为:

    ¥Should we attempt to map the output path to an input path? Yes, because:

    • package.json 是 node_modules 中的吗?不,它在本地项目中。

      ¥Is the package.json in node_modules? No, it’s in the local project.

    • tsconfig.json 是否位于 package.json 目录中?是的。

      ¥Is the tsconfig.json within the package.json directory? Yes.

  7. ./dist/utils.d.mts 中,将 outDir 前缀替换为 rootDir./src/utils.d.mts

    ¥In ./dist/utils.d.mts, replace the outDir prefix with rootDir. ./src/utils.d.mts

  8. 将输出扩展 .d.mts 替换为相应的输入扩展 .mts./src/utils.mts

    ¥Replace the output extension .d.mts with the corresponding input extension .mts. ./src/utils.mts

  9. 如果文件存在,则返回路径 "./src/utils.mts"

    ¥Return the path "./src/utils.mts" if the file exists.

  10. 否则,如果文件存在,则返回路径 "./dist/utils.d.mts"

    ¥Otherwise, return the path "./dist/utils.d.mts" if the file exists.

示例:具有子路径模式的 node_modules 依赖

¥Example: node_modules dependency with subpath pattern

设想:"/node_modules/pkg/main.mts" 使用 package.json 导入带有条件 ["types", "node", "import"]"#internal/utils"(由 moduleResolution 设置和触发模块解析请求的上下文确定):

¥Scenario: "/node_modules/pkg/main.mts" imports "#internal/utils" with conditions ["types", "node", "import"] (determined by moduleResolution setting and the context that triggered the module resolution request) with the package.json:

json
// /node_modules/pkg/package.json
{
"name": "pkg",
"imports": {
"#internal/*": {
"import": "./dist/internal/*.mjs",
"require": "./dist/internal/*.cjs"
}
}
}

解决过程:

¥Resolution process:

  1. 导入路径以 # 开头,尝试通过 "imports" 解析。

    ¥Import path starts with #, try to resolve through "imports".

  2. 最近的祖级 package.json 中是否存在 "imports"?是的。

    ¥Does "imports" exist in the nearest ancestor package.json? Yes.

  3. "#internal/utils" 是否存在于 "imports" 对象中?不,检查模式匹配。

    ¥Does "#internal/utils" exist in the "imports" object? No, check for pattern matches.

  4. 任何带有 * 的键都与 "#internal/utils" 匹配吗?是的,"#internal/*" 匹配并将 utils 设置为替补。

    ¥Does any key with a * match "#internal/utils"? Yes, "#internal/*" matches and sets utils to be the substitution.

  5. imports["#internal/*"] 处的值是一个对象 - 它必须指定条件。

    ¥The value at imports["#internal/*"] is an object—it must be specifying conditions.

  6. 第一个条件 "import" 是否符合此请求?是的。

    ¥Does the first condition "import" match this request? Yes.

  7. 我们是否应该尝试将输出路径映射到输入路径?否,因为 package.json 位于 node_modules 中。

    ¥Should we attempt to map the output path to an input path? No, because the package.json is in node_modules.

  8. ./dist/internal/*.mjs 中,将 * 替换为替换 utils./dist/internal/utils.mjs

    ¥In ./dist/internal/*.mjs, replace * with the substitution utils. ./dist/internal/utils.mjs

  9. 路径 ./dist/internal/utils.mjs 是否具有可识别的 TypeScript 文件扩展名?不,请尝试扩展替换。

    ¥Does the path ./dist/internal/utils.mjs have a recognized TypeScript file extension? No, try extension substitution.

  10. 通过 扩展替换,尝试以下路径,返回第一个存在的路径,否则返回 undefined

    ¥Via extension substitution, try the following paths, returning the first one that exists, or undefined otherwise:

    1. ./dist/internal/utils.mts
    2. ./dist/internal/utils.d.mts
    3. ./dist/internal/utils.mjs

node16, nodenext

这些模式反映了 Node.js v12 及更高版本的模块解析行为。(node16nodenext 目前是相同的,但如果 Node.js 将来对其模块系统进行重大更改,node16 将被冻结,而 nodenext 将被更新以反映新行为。)在 Node.js 中,ECMAScript 导入的解析算法与 CommonJS require 调用的算法显着不同。对于正在解析的每个模块说明符,首先使用语法和 导入文件的模块格式 来确定模块说明符是在触发的 JavaScript 中的 import 还是 require 中。然后,该信息被传递到模块解析器中,以确定要使用哪种解析算法(以及是否对 package.json "exports""imports" 使用 "import""require" 条件)。

¥These modes reflect the module resolution behavior of Node.js v12 and later. (node16 and nodenext are currently identical, but if Node.js makes significant changes to its module system in the future, node16 will be frozen while nodenext will be updated to reflect the new behavior.) In Node.js, the resolution algorithm for ECMAScript imports is significantly different from the algorithm for CommonJS require calls. For each module specifier being resolved, the syntax and the module format of the importing file are first used to determine whether the module specifier will be in an import or require in the emitted JavaScript. That information is then passed into the module resolver to determine which resolution algorithm to use (and whether to use the "import" or "require" condition for package.json "exports" or "imports").

默认情况下,确定为 CommonJS 格式 的 TypeScript 文件可能仍使用 importexport 语法,但触发的 JavaScript 将使用 requiremodule.exports 代替。这意味着使用 require 算法解析 import 语句是很常见的。如果这导致混乱,可以启用 verbatimModuleSyntax 编译器选项,该选项禁止使用将作为 require 调用触发的 import 语句。

¥TypeScript files that are determined to be in CommonJS format may still use import and export syntax by default, but the emitted JavaScript will use require and module.exports instead. This means that it’s common to see import statements that are resolved using the require algorithm. If this causes confusion, the verbatimModuleSyntax compiler option can be enabled, which prohibits the use of import statements that would be emitted as require calls.

请注意,根据 Node.js 的行为,动态 import() 调用始终使用 import 算法进行解析。但是,import() 类型是根据导入文件的格式解析的(为了向后兼容现有的 CommonJS 格式类型声明):

¥Note that dynamic import() calls are always resolved using the import algorithm, according to Node.js’s behavior. However, import() types are resolved according to the format of the importing file (for backward compatibility with existing CommonJS-format type declarations):

ts
// @Filename: module.mts
import x from "./mod.js"; // `import` algorithm due to file format (emitted as-written)
import("./mod.js"); // `import` algorithm due to syntax (emitted as-written)
type Mod = typeof import("./mod.js"); // `import` algorithm due to file format
import mod = require("./mod"); // `require` algorithm due to syntax (emitted as `require`)
// @Filename: commonjs.cts
import x from "./mod"; // `require` algorithm due to file format (emitted as `require`)
import("./mod.js"); // `import` algorithm due to syntax (emitted as-written)
type Mod = typeof import("./mod"); // `require` algorithm due to file format
import mod = require("./mod"); // `require` algorithm due to syntax (emitted as `require`)

隐式和强制选项

¥Implied and enforced options

支持的功能

¥Supported features

功能按优先顺序列出。

¥Features are listed in order of precedence.

import require
paths
baseUrl
node_modules 包查找
package.json "exports" ✅ 匹配 typesnodeimport ✅ 匹配 typesnoderequire
package.json "imports" 和自名导入 ✅ 匹配 typesnodeimport ✅ 匹配 typesnoderequire
package.json "typesVersions"
包相对路径 ✅ 当 exports 不存在时 ✅ 当 exports 不存在时
完整相对路径
无扩展相对路径
目录模块

bundler

--moduleResolution bundler 尝试对大多数 JavaScript 打包程序常见的模块解析行为进行建模。简而言之,这意味着支持传统上与 Node.js CommonJS require 解析算法(如 node_modules 查找目录模块无扩展路径)相关的所有行为,同时还支持较新的 Node.js 解析功能(如 package.json "exports"package.json "imports")。

¥--moduleResolution bundler attempts to model the module resolution behavior common to most JavaScript bundlers. In short, this means supporting all the behaviors traditionally associated with Node.js’s CommonJS require resolution algorithm like node_modules lookups, directory modules, and extensionless paths, while also supporting newer Node.js resolution features like package.json "exports" and package.json "imports".

思考 --moduleResolution bundler--moduleResolution nodenext 之间的异同是有启发性的,特别是它们如何决定在解析 package.json "exports""imports" 时使用什么条件。考虑 .ts 文件中的导入语句:

¥It’s instructive to think about the similarities and differences between --moduleResolution bundler and --moduleResolution nodenext, particularly in how they decide what conditions to use when resolving package.json "exports" or "imports". Consider an import statement in a .ts file:

ts
// index.ts
import { foo } from "pkg";

回想一下,在 --module nodenext --moduleResolution nodenext 中,--module 首先设置 determines 是否将导入作为 importrequire 调用发送到 .js 文件,然后将该信息传递给 TypeScript 的模块解析器,该解析器决定是否匹配 "pkg" 的 package.json 中的 "import""require" 条件 "exports" 相应。假设该文件的范围内没有 package.json。文件扩展名是 .ts,因此输出文件扩展名是 .js,Node.js 将其解释为 CommonJS,因此 TypeScript 会将这个 import 作为 require 调用触发。因此,模块解析器在从 "pkg" 解析 "exports" 时将使用 require 条件。

¥Recall that in --module nodenext --moduleResolution nodenext, the --module setting first determines whether the import will be emitted to the .js file as an import or require call, then passes that information to TypeScript’s module resolver, which decides whether to match "import" or "require" conditions in "pkg"’s package.json "exports" accordingly. Let’s assume that there’s no package.json in scope of this file. The file extension is .ts, so the output file extension will be .js, which Node.js will interpret as CommonJS, so TypeScript will emit this import as a require call. So, the module resolver will use the require condition as it resolves "exports" from "pkg".

--moduleResolution bundler 中发生相同的过程,但决定是否为此 import 语句触发 importrequire 调用的规则将有所不同,因为 --moduleResolution bundler 需要使用 --module esnext--module preserve。在这两种模式下,ESM import 声明始终作为 ESM import 声明触发,因此 TypeScript 的模块解析器将接收该信息并在从 "pkg" 解析 "exports" 时使用 "import" 条件。

¥The same process happens in --moduleResolution bundler, but the rules for deciding whether to emit an import or require call for this import statement will be different, since --moduleResolution bundler necessitates using --module esnext or --module preserve. In both of those modes, ESM import declarations always emit as ESM import declarations, so TypeScript’s module resolver will receive that information and use the "import" condition as it resolves "exports" from "pkg".

这种解释可能有点不直观,因为 --moduleResolution bundler 通常与 --noEmit 结合使用 - 打包器通常处理原始 .ts 文件并在未转换的 importrequire 上执行模块解析。然而,为了保持一致性,TypeScript 仍然使用 module 决定的假设触发来通知模块解析和类型检查。这使得 --module preserve 成为运行时或打包程序在原始 .ts 文件上运行时的最佳选择,因为它意味着没有转换。在 --module preserve --moduleResolution bundler 下,你可以在同一个文件中写入导入和需求,分别解决 importrequire 条件:

¥This explanation may be somewhat unintuitive, since --moduleResolution bundler is usually used in combination with --noEmit—bundlers typically process raw .ts files and perform module resolution on untransformed imports or requires. However, for consistency, TypeScript still uses the hypothetical emit decided by module to inform module resolution and type checking. This makes --module preserve the best choice whenever a runtime or bundler is operating on raw .ts files, since it implies no transformation. Under --module preserve --moduleResolution bundler, you can write imports and requires in the same file that will resolve with the import and require conditions, respectively:

ts
// index.ts
import pkg1 from "pkg"; // Resolved with "import" condition
import pkg2 = require("pkg"); // Resolved with "require" condition

隐式和强制选项

¥Implied and enforced options

  • --moduleResolution bundler 必须与 --module esnext--module preserve 配对。

    ¥--moduleResolution bundler must be paired with --module esnext or --module preserve.

  • --moduleResolution bundler 意味着 --allowSyntheticDefaultImports

    ¥--moduleResolution bundler implies --allowSyntheticDefaultImports.

支持的功能

¥Supported features

node10(原名 node

¥node10 (formerly known as node)

在 TypeScript 5.0 中,--moduleResolution node 被重命名为 node10(保留 node 作为向后兼容的别名)。它反映了 CommonJS 模块解析算法,因为它存在于 v12 之前的 Node.js 版本中。不应再使用它。

¥--moduleResolution node was renamed to node10 (keeping node as an alias for backward compatibility) in TypeScript 5.0. It reflects the CommonJS module resolution algorithm as it existed in Node.js versions earlier than v12. It should no longer be used.

支持的功能

¥Supported features

classic

不要使用 classic

¥Do not use classic.