Node.js v25.3.0 文档


模块:ECMAScript 模块#>

【Modules: ECMAScript modules】

介绍#>

【Introduction】

ECMAScript 模块用于打包可重用的 JavaScript 代码。模块是使用各种 importexport 语句定义的。

【ECMAScript modules are the official standard format to package JavaScript code for reuse. Modules are defined using a variety of import and export statements.】

以下是 ES 模块导出函数的示例:

【The following example of an ES module exports a function:】

// addTwo.mjs
function addTwo(num) {
  return num + 2;
}

export { addTwo }; 

下面是一个 ES 模块的示例,它从 addTwo.mjs 导入函数:

【The following example of an ES module imports the function from addTwo.mjs:】

// app.mjs
import { addTwo } from './addTwo.mjs';

// Prints: 6
console.log(addTwo(4)); 

Node.js 完全支持当前规范的 ECMAScript 模块,并提供它们与其原始模块格式之间的互操作性,CommonJS

【Node.js fully supports ECMAScript modules as they are currently specified and provides interoperability between them and its original module format, CommonJS.】

启用#>

【Enabling】

Node.js 有两种模块系统:CommonJS 模块和 ECMAScript 模块。

【Node.js has two module systems: CommonJS modules and ECMAScript modules.】

作者可以通过 .mjs 文件扩展名、package.json 中值为 "module""type" 字段,或值为 "module"--input-type 标志来告诉 Node.js 将 JavaScript 解释为 ES 模块。这些都是明确标示代码被用于作为 ES 模块运行的标记。

【Authors can tell Node.js to interpret JavaScript as an ES module via the .mjs file extension, the package.json "type" field with a value "module", or the --input-type flag with a value of "module". These are explicit markers of code being intended to run as an ES module.】

反过来,作者也可以明确告诉Node.js将JavaScript解释为 通过“.cjs”文件扩展名和“package.json”"type"字段访问CommonJS 其值为“commonjs”,或--input-type标志,值为 “普通人”。

【Inversely, authors can explicitly tell Node.js to interpret JavaScript as CommonJS via the .cjs file extension, the package.json "type" field with a value "commonjs", or the --input-type flag with a value of "commonjs".】

当代码缺少显式标记来指示使用哪种模块系统时,Node.js 会检查模块的源代码以查找 ES 模块语法。如果发现此类语法,Node.js 会将代码作为 ES 模块运行;否则,它将作为 CommonJS 模块运行。更多详情请参见 确定模块系统

【When code lacks explicit markers for either module system, Node.js will inspect the source code of a module to look for ES module syntax. If such syntax is found, Node.js will run the code as an ES module; otherwise it will run the module as CommonJS. See Determining module system for more details.】

#>

【Packages】

本节已移至 Modules: Packages

【This section was moved to Modules: Packages.】

import 说明符#>

import Specifiers】

术语#>

【Terminology】

import 语句的 specifierfrom 关键字之后的字符串,例如 import { sep } from 'node:path' 中的 'node:path'。specifier 也用于 export from 语句,以及作为 import() 表达式的参数。

【The specifier of an import statement is the string after the from keyword, e.g. 'node:path' in import { sep } from 'node:path'. Specifiers are also used in export from statements, and as the argument to an import() expression.】

有三种类型的说明符:

【There are three types of specifiers:】

  • 相对规范,例如 './startup.js''../config.mjs'。它们指的是相对于导入文件位置的路径。这些路径始终需要文件扩展名。
  • 裸包说明符'some-package''some-package/shuffle'。它们可以通过包名引用包的主入口点,或者像示例中那样通过包名前缀引用包内的特定功能模块。对于没有 "exports" 字段的包,仅在必要时才需要包含文件扩展名。
  • 绝对指定符,例如 'file:///opt/nodejs/config.js'。它们直接并明确地引用完整路径。

裸说明符的解析由 Node.js 模块解析与加载算法 处理。所有其他说明符的解析总是仅通过标准相对 网址 解析语义来解决。

【Bare specifier resolutions are handled by the Node.js module resolution and loading algorithm. All other specifier resolutions are always only resolved with the standard relative URL resolution semantics.】

像在 CommonJS 中一样,包中的模块文件可以通过在包名后附加路径来访问,除非包的 package.json 包含 "exports" 字段,在这种情况下,包中的文件只能通过 "exports" 中定义的路径访问。

【Like in CommonJS, module files within packages can be accessed by appending a path to the package name unless the package's package.json contains an "exports" field, in which case files within packages can only be accessed via the paths defined in "exports".】

有关适用于 Node.js 模块解析中裸模块名的这些包解析规则的详细信息,请参阅 packages 文档

【For details on these package resolution rules that apply to bare specifiers in the Node.js module resolution, see the packages documentation.】

强制文件扩展名#>

【Mandatory file extensions】

使用 import 关键字来解析相对或绝对路径时,必须提供文件扩展名。目录索引(例如 './startup/index.js')也必须完整指定。

【A file extension must be provided when using the import keyword to resolve relative or absolute specifiers. Directory indexes (e.g. './startup/index.js') must also be fully specified.】

这种行为与在浏览器环境中 import 的表现相符,假设服务器配置是典型的。

【This behavior matches how import behaves in browser environments, assuming a typically configured server.】

URL#>

【URLs】

ES 模块会作为 URL 被解析和缓存。这意味着特殊字符必须被 百分比编码,例如将 # 替换为 %23,将 ? 替换为 %3F

【ES modules are resolved and cached as URLs. This means that special characters must be percent-encoded, such as # with %23 and ? with %3F.】

file:node:data: URL 方案是受支持的。在 Node.js 中,像 'https://example.com/app.js' 这样的指定符默认不受支持,除非使用 自定义 HTTPS 加载器

file: URL#>

file: URLs】

如果用于解析模块的 import 指定符具有不同的查询或片段,模块会被多次加载。

【Modules are loaded multiple times if the import specifier used to resolve them has a different query or fragment.】

import './foo.mjs?query=1'; // loads ./foo.mjs with query of "?query=1"
import './foo.mjs?query=2'; // loads ./foo.mjs with query of "?query=2" 

卷宗根可以通过 ///file:/// 引用。鉴于 网址 与路径解析之间的差异(例如百分比编码的细节),建议在导入路径时使用 url.pathToFileURL

【The volume root may be referenced via /, //, or file:///. Given the differences between URL and path resolution (such as percent encoding details), it is recommended to use url.pathToFileURL when importing a path.】

data: 导入#>

data: imports】

data: URL 支持以下 MIME 类型的导入:

  • text/javascript 用于 ES 模块
  • application/json 用于 JSON
  • application/wasm 用于 Wasm
import 'data:text/javascript,console.log("hello!");';
import _ from 'data:application/json,"world!"' with { type: 'json' }; 

data: URL 仅解析内置模块的 裸指定符绝对说明符。解析 关系限定词 不起作用,因为 data: 不是 特别计划。例如,尝试从 data:text/javascript,import "./foo"; 加载 ./foo 会失败,因为 data: URL 没有相对解析的概念。

node: 导入#>

node: imports】

node: URL 被支持作为加载 Node.js 内置模块的替代方式。该 URL 方案允许通过有效的绝对 URL 字符串来引用内置模块。

import fs from 'node:fs/promises'; 

导入属性#>

【Import attributes】

导入属性 是一种用于模块导入语句的内联语法,用于在模块说明符旁传递更多信息。

import fooData from './foo.json' with { type: 'json' };

const { default: barData } =
  await import('./bar.json', { with: { type: 'json' } }); 

Node.js 仅支持 type 属性,并支持以下值:

【Node.js only supports the type attribute, for which it supports the following values:】

属性 type所需
'json'JSON 模块

在导入 JSON 模块时,type: 'json' 属性是必须的。

【The type: 'json' attribute is mandatory when importing JSON modules.】

内置模块#>

【Built-in modules】

内置模块 提供其公共 API 的命名导出。也提供了一个默认导出,它是 CommonJS 导出的值。默认导出可以用于包括修改命名导出在内的操作。内置模块的命名导出只有通过调用 module.syncBuiltinESMExports() 才会更新。

import EventEmitter from 'node:events';
const e = new EventEmitter(); 
import { readFile } from 'node:fs';
readFile('./foo.txt', (err, source) => {
  if (err) {
    console.error(err);
  } else {
    console.log(source);
  }
}); 
import fs, { readFileSync } from 'node:fs';
import { syncBuiltinESMExports } from 'node:module';
import { Buffer } from 'node:buffer';

fs.readFileSync = () => Buffer.from('Hello, ESM');
syncBuiltinESMExports();

fs.readFileSync === readFileSync; 

import() 表达式#>

import() expressions】

动态 import() 提供了一种异步导入模块的方式。它在 CommonJS 和 ES 模块中都受支持,并且可用于加载 CommonJS 和 ES 模块。

import.meta#>

import.meta 元属性是一个 Object,包含以下属性。它仅在 ES 模块中受支持。

【The import.meta meta property is an Object that contains the following properties. It is only supported in ES modules.】

import.meta.dirname#>

  • 类型:<string> 当前模块的目录名称。

这与import.meta.filenamepath.dirname()相同。

【This is the same as the path.dirname() of the import.meta.filename.】

注意:仅出现在 file: 模块中。

import.meta.filename#>

  • 类型:<string> 当前模块的完整绝对路径和文件名,符号链接已解析。

这与import.meta.urlurl.fileURLToPath()相同。

【This is the same as the url.fileURLToPath() of the import.meta.url.】

注意 只有本地模块支持此属性。未使用 file: 协议的模块将不提供该属性。

import.meta.url#>

  • 类型:<string> 模块的绝对 file: URL。

这与在浏览器中提供当前模块文件的 URL 时的定义完全相同。

【This is defined exactly the same as it is in browsers providing the URL of the current module file.】

这可以启用有用的模式,例如相对文件加载

【This enables useful patterns such as relative file loading:】

import { readFileSync } from 'node:fs';
const buffer = readFileSync(new URL('./data.proto', import.meta.url)); 

import.meta.main#>

稳定性: 1.0 - 早期开发

  • 类型:<boolean> 当当前模块是当前进程的入口时为 true;否则为 false

相当于 CommonJS 中的 require.main === module

【Equivalent to require.main === module in CommonJS.】

类似于Python的“name== ”main“”。

【Analogous to Python's __name__ == "__main__".】

export function foo() {
  return 'Hello, world';
}

function main() {
  const message = foo();
  console.log(message);
}

if (import.meta.main) main();
// `foo` can be imported from another module without possible side-effects from `main` 

import.meta.resolve(specifier)#>

稳定性: 1.2 - 发布候选版

  • specifier <string> 要相对于当前模块解析的模块说明符。
  • 返回值:<string> 指定符将解析到的绝对 URL 字符串。

import.meta.resolve 是一个模块相对的解析函数,其作用域限定在每个模块内,返回 URL 字符串。

const dependencyAsset = import.meta.resolve('component-lib/asset.css');
// file:///app/node_modules/component-lib/asset.css
import.meta.resolve('./dep.js');
// file:///app/dep.js 

支持 Node.js 模块解析的所有功能。依赖解析需遵循包内允许的导出解析规则。

【All features of the Node.js module resolution are supported. Dependency resolutions are subject to the permitted exports resolutions within the package.】

注意事项

  • 这可能导致同步文件系统操作,其性能影响与 require.resolve 类似。
  • 此功能在自定义加载器中不可用(会导致死锁)。

非标准 API

在使用 --experimental-import-meta-resolve 标志时,该函数接受第二个参数:

【When using the --experimental-import-meta-resolve flag, that function accepts a second argument:】

  • parent <string> | <URL> 可选的绝对父模块 URL,用于解析。 默认值: import.meta.url

与 CommonJS 的互操作性#>

【Interoperability with CommonJS】

import 语句#>

import statements】

import 语句可以引用 ES 模块或 CommonJS 模块。 import 语句只允许在 ES 模块中使用,但在 CommonJS 中支持动态 import() 表达式以加载 ES 模块。

【An import statement can reference an ES module or a CommonJS module. import statements are permitted only in ES modules, but dynamic import() expressions are supported in CommonJS for loading ES modules.】

在导入 CommonJS 模块 时,module.exports 对象会作为默认导出提供。命名导出可能也可用,通过静态分析提供,以便更好地与生态系统兼容。

【When importing CommonJS modules, the module.exports object is provided as the default export. Named exports may be available, provided by static analysis as a convenience for better ecosystem compatibility.】

require#>

CommonJS 模块 require 当前仅支持加载同步的 ES 模块(也就是说,不使用顶层 await 的 ES 模块)。

【The CommonJS module require currently only supports loading synchronous ES modules (that is, ES modules that do not use top-level await).】

详情请参见 使用 require() 加载 ECMAScript 模块

【See Loading ECMAScript modules using require() for details.】

CommonJS 命名空间#>

【CommonJS Namespaces】

CommonJS 模块由一个 module.exports 对象组成,该对象可以是任意类型。

【CommonJS modules consist of a module.exports object which can be of any type.】

为此,当从 ECMAScript 模块导入 CommonJS 时,会为 CommonJS 模块构建一个命名空间封装器,该封装器总是提供一个指向 CommonJS module.exports 值的 default 导出键。

【To support this, when importing CommonJS from an ECMAScript module, a namespace wrapper for the CommonJS module is constructed, which always provides a default export key pointing to the CommonJS module.exports value.】

此外,对 CommonJS 模块的源代码执行启发式静态分析,以尽最大努力生成一个静态导出列表,从 module.exports 的值中提供命名空间。这是必要的,因为这些命名空间必须在评估 CJS 模块之前构建。

【In addition, a heuristic static analysis is performed against the source text of the CommonJS module to get a best-effort static list of exports to provide on the namespace from values on module.exports. This is necessary since these namespaces must be constructed prior to the evaluation of the CJS module.】

这些 CommonJS 命名空间对象还提供 default 导出作为 'module.exports' 命名导出,以明确表示它们在 CommonJS 中的表示使用的是该值,而不是命名空间值。这与 require(esm) 互操作支持中 'module.exports' 导出名称的处理语义相对应。

【These CommonJS namespace objects also provide the default export as a 'module.exports' named export, in order to unambiguously indicate that their representation in CommonJS uses this value, and not the namespace value. This mirrors the semantics of the handling of the 'module.exports' export name in require(esm) interop support.】

在导入 CommonJS 模块时,可以使用 ES 模块的默认导入或其对应的简写语法来可靠地导入它:

【When importing a CommonJS module, it can be reliably imported using the ES module default import or its corresponding sugar syntax:】

import { default as cjs } from 'cjs';
// Identical to the above
import cjsSugar from 'cjs';

console.log(cjs);
console.log(cjs === cjsSugar);
// Prints:
//   <module.exports>
//   true 

当使用 import * as m from 'cjs' 或动态导入时,可以直接观察到这个模块命名空间的特殊对象:

【This Module Namespace Exotic Object can be directly observed either when using import * as m from 'cjs' or a dynamic import:】

import * as m from 'cjs';
console.log(m);
console.log(m === await import('cjs'));
// Prints:
//   [Module] { default: <module.exports>, 'module.exports': <module.exports> }
//   true 

为了更好地与 JS 生态系统中现有的使用兼容,Node.js 还尝试确定每个导入的 CommonJS 模块的命名导出,并通过静态分析过程将它们作为单独的 ES 模块导出。

【For better compatibility with existing usage in the JS ecosystem, Node.js in addition attempts to determine the CommonJS named exports of every imported CommonJS module to provide them as separate ES module exports using a static analysis process.】

例如,考虑编写的 CommonJS 模块:

【For example, consider a CommonJS module written:】

// cjs.cjs
exports.name = 'exported'; 

前面的模块支持 ES 模块中的命名导入:

【The preceding module supports named imports in ES modules:】

import { name } from './cjs.cjs';
console.log(name);
// Prints: 'exported'

import cjs from './cjs.cjs';
console.log(cjs);
// Prints: { name: 'exported' }

import * as m from './cjs.cjs';
console.log(m);
// Prints:
//   [Module] {
//     default: { name: 'exported' },
//     'module.exports': { name: 'exported' },
//     name: 'exported'
//   } 

从记录的模块命名空间特殊对象的最后一个示例可以看出,当模块被导入时,name 导出会从 module.exports 对象中复制出来,并直接设置在 ES 模块命名空间上。

【As can be seen from the last example of the Module Namespace Exotic Object being logged, the name export is copied off of the module.exports object and set directly on the ES module namespace when the module is imported.】

对于这些命名导出,无法检测到对 module.exports 的实时绑定更新或新增导出。

【Live binding updates or new exports added to module.exports are not detected for these named exports.】

命名导出的检测是基于常见的语法模式,但并不总是能正确检测命名导出。在这种情况下,使用上述描述的默认导入形式可能是更好的选择。

【The detection of named exports is based on common syntax patterns but does not always correctly detect named exports. In these cases, using the default import form described above can be a better option.】

命名导出检测涵盖了许多常见的导出模式、再导出模式以及构建工具和转译器输出。有关实现的确切语义,请参见 cjs 模块解析器

【Named exports detection covers many common export patterns, reexport patterns and build tool and transpiler outputs. See cjs-module-lexer for the exact semantics implemented.】

ES 模块和 CommonJS 的区别#>

【Differences between ES modules and CommonJS】

没有 requireexportsmodule.exports#>

【No require, exports, or module.exports

在大多数情况下,ES 模块的 import 可以用来加载 CommonJS 模块。

【In most cases, the ES module import can be used to load CommonJS modules.】

如果需要,可以在 ES 模块中使用 module.createRequire() 构建 require 函数。

【If needed, a require function can be constructed within an ES module using module.createRequire().】

没有 __filename__dirname#>

【No __filename or __dirname

这些 CommonJS 变量在 ES 模块中不可用。

【These CommonJS variables are not available in ES modules.】

__filename__dirname 的用例可以通过 import.meta.filenameimport.meta.dirname 复现。

没有插件加载#>

【No Addon Loading】

插件 目前不支持使用 ES 模块导入。

它们可以改为装载 module.createRequire()process.dlopen

【They can instead be loaded with module.createRequire() or process.dlopen.】

没有 require.main#>

【No require.main

要替换 require.main === module,可以使用 import.meta.main API。

【To replace require.main === module, there is the import.meta.main API.】

没有 require.resolve#>

【No require.resolve

相对解析可以通过 new URL('./local', import.meta.url) 来处理。

【Relative resolution can be handled via new URL('./local', import.meta.url).】

对于完整的 require.resolve 替代方案,可以使用 import.meta.resolve API。

【For a complete require.resolve replacement, there is the import.meta.resolve API.】

或者可以使用 module.createRequire()

【Alternatively module.createRequire() can be used.】

没有 NODE_PATH#>

【No NODE_PATH

NODE_PATH 不是解析 import 指定符的一部分。如果需要此行为,请使用符号链接。

没有 require.extensions#>

【No require.extensions

require.extensions 不会被 import 使用。模块自定义钩子可以提供替代方案。

没有 require.cache#>

【No require.cache

require.cache 不会被 import 使用,因为 ES 模块加载器有它自己的独立缓存。

JSON 模块#>

【JSON modules】

JSON 文件可以通过 import 引用:

【JSON files can be referenced by import:】

import packageConfig from './package.json' with { type: 'json' }; 

必须使用 with { type: 'json' } 语法;详见 导入属性

【The with { type: 'json' } syntax is mandatory; see Import Attributes.】

导入的 JSON 只暴露了 default 导出。不支持命名导出。为了避免重复,会在 CommonJS 缓存中创建一个缓存项。如果 JSON 模块已从相同路径导入,CommonJS 会返回同一个对象。

【The imported JSON only exposes a default export. There is no support for named exports. A cache entry is created in the CommonJS cache to avoid duplication. The same object is returned in CommonJS if the JSON module has already been imported from the same path.】

Wasm 模块#>

【Wasm modules】

支持导入 WebAssembly 模块实例和 WebAssembly 源阶段导入。

【Importing both WebAssembly module instances and WebAssembly source phase imports is supported.】

这两种集成都符合 WebAssembly 的 ES 模块集成提案

【Both of these integrations are in line with the ES Module Integration Proposal for WebAssembly.】

Wasm 源阶段导入#>

【Wasm Source Phase Imports】

稳定性: 1.2 - 发布候选版

源阶段导入 提案允许使用 import source 关键字组合直接导入 WebAssembly.Module 对象,而不是获取已经用其依赖实例化的模块实例。

【The Source Phase Imports proposal allows the import source keyword combination to import a WebAssembly.Module object directly, instead of getting a module instance already instantiated with its dependencies.】

当需要为 Wasm 进行自定义实例化时,这非常有用,同时仍可以通过 ES 模块集成来解析和加载它。

【This is useful when needing custom instantiations for Wasm, while still resolving and loading it through the ES module integration.】

例如,要创建一个模块的多个实例,或将自定义导入传入新的 library.wasm 实例中:

【For example, to create multiple instances of a module, or to pass custom imports into a new instance of library.wasm:】

import source libraryModule from './library.wasm';

const instance1 = await WebAssembly.instantiate(libraryModule, importObject1);

const instance2 = await WebAssembly.instantiate(libraryModule, importObject2); 

除了静态源阶段之外,还有一种源阶段的动态变体,通过 import.source 动态阶段导入语法实现:

【In addition to the static source phase, there is also a dynamic variant of the source phase via the import.source dynamic phase import syntax:】

const dynamicLibrary = await import.source('./library.wasm');

const instance = await WebAssembly.instantiate(dynamicLibrary, importObject); 

JavaScript 字符串内置函数#>

【JavaScript String Builtins】

稳定性: 1.2 - 发布候选版

在导入 WebAssembly 模块时,WebAssembly JS 字符串内置函数提案 会通过 ESM 集成自动启用。这允许 WebAssembly 模块直接使用来自 wasm:js-string 命名空间的高效编译时字符串内置功能。

【When importing WebAssembly modules, the WebAssembly JS String Builtins Proposal is automatically enabled through the ESM Integration. This allows WebAssembly modules to directly use efficient compile-time string builtins from the wasm:js-string namespace.】

例如,下面的 Wasm 模块使用 wasm:js-stringlength 内置函数导出一个字符串 getLength 函数:

【For example, the following Wasm module exports a string getLength function using the wasm:js-string length builtin:】

(module
  ;; Compile-time import of the string length builtin.
  (import "wasm:js-string" "length" (func $string_length (param externref) (result i32)))

  ;; Define getLength, taking a JS value parameter assumed to be a string,
  ;; calling string length on it and returning the result.
  (func $getLength (param $str externref) (result i32)
    local.get $str
    call $string_length
  )

  ;; Export the getLength function.
  (export "getLength" (func $get_length))
) 
import { getLength } from './string-len.wasm';
getLength('foo'); // Returns 3. 

Wasm 内建函数是在编译模块时进行链接的编译时导入,而不是在实例化期间。它们的行为不同于普通的模块图导入,也无法通过 WebAssembly.Module.imports(mod) 进行检查,除非使用禁用字符串内建函数的直接 WebAssembly.compile API 重新编译模块,否则无法对其进行虚拟化。

【Wasm builtins are compile-time imports that are linked during module compilation rather than during instantiation. They do not behave like normal module graph imports and they cannot be inspected via WebAssembly.Module.imports(mod) or virtualized unless recompiling the module using the direct WebAssembly.compile API with string builtins disabled.】

在模块被实例化之前在源阶段导入模块,也会自动使用编译时内置功能:

【Importing a module in the source phase before it has been instantiated will also use the compile-time builtins automatically:】

import source mod from './string-len.wasm';
const { exports: { getLength } } = await WebAssembly.instantiate(mod, {});
getLength('foo'); // Also returns 3. 

Wasm 实例阶段导入#>

【Wasm Instance Phase Imports】

稳定性: 1.1 - 处于活跃开发中

实例导入允许将任何 .wasm 文件像普通模块一样导入,从而支持它们的模块导入。

【Instance imports allow any .wasm files to be imported as normal modules, supporting their module imports in turn.】

例如,一个包含以下内容的 index.js

【For example, an index.js containing:】

import * as M from './library.wasm';
console.log(M); 

在以下条件下执行:

【executed under:】

node index.mjs 

将为 library.wasm 的实例化提供导出接口。

【would provide the exports interface for the instantiation of library.wasm.】

保留的 Wasm 命名空间#>

【Reserved Wasm Namespaces】

在导入 WebAssembly 模块实例时,不能使用以保留前缀开头的导入模块名称或导入/导出名称:

【When importing WebAssembly module instances, they cannot use import module names or import/export names that start with reserved prefixes:】

  • wasm-js: - 在所有模块导入名称、模块名称和导出名称中被保留。
  • wasm: - 在模块导入名称和导出名称中保留(为了支持未来内置填充,允许使用导入的模块名称)。

使用上述保留名称导入模块会抛出 WebAssembly.LinkError

【Importing a module using the above reserved names will throw a WebAssembly.LinkError.】

顶层 await#>

【Top-level await

await 关键字可以在 ECMAScript 模块的顶层主体中使用。

【The await keyword may be used in the top level body of an ECMAScript module.】

假设有一个 a.mjs 文件,其中包含

【Assuming an a.mjs with】

export const five = await Promise.resolve(5); 

还有一个 b.mjs,内容是

【And a b.mjs with】

import { five } from './a.mjs';

console.log(five); // Logs `5` 
node b.mjs # works 

如果顶层的 await 表达式永远不会被解析,node 进程将会以 13 状态码 退出。

【If a top level await expression never resolves, the node process will exit with a 13 status code.】

import { spawn } from 'node:child_process';
import { execPath } from 'node:process';

spawn(execPath, [
  '--input-type=module',
  '--eval',
  // Never-resolving Promise:
  'await new Promise(() => {})',
]).once('exit', (code) => {
  console.log(code); // Logs `13`
}); 

加载器#>

【Loaders】

以前的 Loaders 文档现在位于 模块:自定义钩子

【The former Loaders documentation is now at Modules: Customization hooks.】

解析和加载算法#>

【Resolution and loading algorithm】

特性#>

【Features】

默认解析器具有以下属性:

【The default resolver has the following properties:】

  • 基于文件 URL 的解析,如 ES 模块所使用
  • 相对和绝对 URL 解析
  • 没有默认扩展
  • 没有主文件夹
  • 通过 node_modules 进行裸指定符包解析查找
  • 不会因未知的扩展或协议而失败
  • 可以选择在加载阶段提供格式提示

默认加载器具有以下属性

【The default loader has the following properties】

  • 通过 node: URL 支持内置模块加载
  • 通过 data: URL 支持“内联”模块加载
  • 支持 file: 模块加载
  • 在任何其他 URL 协议上都会失败
  • 在加载 file: 时对未知扩展名失败(仅支持 .cjs.js.mjs

解析算法#>

【Resolution algorithm】

加载 ES 模块说明符的算法如下所示,通过下面的 ESM_RESOLVE 方法实现。它返回相对于 parentURL 的模块说明符的解析 URL。

【The algorithm to load an ES module specifier is given through the ESM_RESOLVE method below. It returns the resolved URL for a module specifier relative to a parentURL.】

解析算法确定模块加载的完整解析 URL,以及其建议的模块格式。解析算法并不决定解析后的 URL 协议是否可以加载,或者文件扩展名是否被允许,这些验证是在 Node.js 的加载阶段进行的(例如,如果要求加载一个协议不是 file:data:node: 的 URL)。

【The resolution algorithm determines the full resolved URL for a module load, along with its suggested module format. The resolution algorithm does not determine whether the resolved URL protocol can be loaded, or whether the file extensions are permitted, instead these validations are applied by Node.js during the load phase (for example, if it was asked to load a URL that has a protocol that is not file:, data: or node:.】

该算法还尝试根据文件扩展名确定文件格式(见下方的 ESM_FILE_FORMAT 算法)。如果无法识别文件扩展名(例如,如果不是 .mjs.cjs.json),则返回 undefined 格式,这将在加载阶段抛出错误。

【The algorithm also tries to determine the format of the file based on the extension (see ESM_FILE_FORMAT algorithm below). If it does not recognize the file extension (eg if it is not .mjs, .cjs, or .json), then a format of undefined is returned, which will throw during the load phase.】

确定已解析 URL 的模块格式的算法由 ESM_FILE_FORMAT 提供,它会返回任何文件的唯一模块格式。对于 ECMAScript 模块,会返回 "module" 格式,而 "commonjs" 格式用于表示通过传统 CommonJS 加载器加载。未来更新中可以扩展其他格式,例如 "addon"

【The algorithm to determine the module format of a resolved URL is provided by ESM_FILE_FORMAT, which returns the unique module format for any file. The "module" format is returned for an ECMAScript Module, while the "commonjs" format is used to indicate loading through the legacy CommonJS loader. Additional formats such as "addon" can be extended in future updates.】

在以下算法中,除非另有说明,所有子程序错误都将被作为这些顶层例程的错误传播。

【In the following algorithms, all subroutine errors are propagated as errors of these top-level routines unless stated otherwise.】

defaultConditions 是条件环境名称数组,["node", "import"]

defaultConditions is the conditional environment name array, ["node", "import"].】

解析器可能会抛出以下错误:

【The resolver can throw the following errors:】

  • 无效的模块说明符:模块说明符是无效的 URL、包名或包子路径说明符。
  • 无效的包配置:package.json 配置无效或包含无效的配置。
  • 无效的包目标: 包的导出或导入定义了一个无效类型或字符串目标的包模块。
  • 包路径未导出: 包导出未定义或不允许在给定模块的包中使用目标子路径。
  • 未定义的包导入: 包导入未定义说明符。
  • 模块未找到:请求的包或模块不存在。
  • 不支持的目录导入:解析的路径对应于一个目录,该目录不是模块导入的支持目标。

解析算法规范#>

【Resolution Algorithm Specification】

ESM_RESOLVE(specifierparentURL)

  1. resolved未定义
  2. 如果 specifier 是一个有效的 URL,那么
    1. resolved 设置为解析并重新序列化 specifier 为 URL 的结果。
  3. 否则,如果 specifier"/""./""../" 开头,那么
    1. resolved 设置为相对于 parentURLspecifier 的 URL 解析结果。
  4. 否则,如果 specifier"#" 开头,则
    1. resolved 设置为 PACKAGE_IMPORTS_RESOLVE(specifier, parentURL, defaultConditions) 的结果。
  5. 否则,
    1. 注意:specifier 现在是一个裸规范符。
    2. resolved 设置为> PACKAGE_RESOLVE(specifier, parentURL) 的结果。
  6. format未定义
  7. 如果 resolved 是一个 "file:" URL,那么
    1. 如果 resolved 包含任何 "/""" 的百分比编码(分别为 "%2F""%5C"),那么
      1. 抛出一个 无效模块说明符 错误。
    2. 如果 resolved 处的文件是一个目录,则
      1. 抛出“不支持的目录导入”错误。
    3. 如果 resolved 处的文件不存在,则
      1. 抛出 模块未找到 错误。
    4. resolved 设置为 resolved 的真实路径,同时保持相同的 URL 查询字符串和片段组件。
    5. format 设置为 ESM_FILE_FORMAT(resolved) 的结果。
  8. 否则,
    1. 设置 format 为与 URL resolved 关联的内容类型的模块格式。
  9. formatresolved 返回到加载阶段

PACKAGE_RESOLVE(packageSpecifierparentURL)

  1. packageName未定义.
  2. 如果 packageSpecifier 是空字符串,则
    1. 抛出一个 无效模块说明符 错误。
  3. 如果 packageSpecifier 是 Node.js 内置模块名称,则
    1. 返回字符串 "node:"packageSpecifier 连接后的结果。
  4. 如果 packageSpecifier 不以 "@" 开头,则
    1. packageName 设置为 packageSpecifier 的子字符串,直到第一个 "/" 分隔符或字符串末尾。
  5. 否则,
    1. 如果 packageSpecifier 不包含 "/" 分隔符,则
      1. 抛出一个 无效模块说明符 错误。
    2. packageName 设置为 packageSpecifier 的子字符串直到第二个 "/" 分隔符或字符串末尾。
  6. 如果 packageName"." 开头或包含 """%",那么
    1. 抛出一个 无效的模块说明符 错误。
  7. packageSubpath"."packageSpecifierpackageName 长度位置开始的子字符串连接而成。
  8. selfUrlPACKAGE_SELF_RESOLVE(packageName, packageSubpath, parentURL) 的结果。
  9. 如果 selfUrl 不是 undefined,则返回 selfUrl
  10. 虽然 parentURL 不是文件系统根目录,
    1. packageURL 为以 parentURL 为基准、将 "node_modules/"packageName 连接后的 URL 解析结果。
    2. parentURL 设置为 parentURL 的父文件夹 URL。
    3. 如果 packageURL 处的文件夹不存在,则
      1. 继续下一次循环迭代。
    4. pjsonREAD_PACKAGE_JSON(packageURL) 的结果。
    5. 如果 pjson 不是 null,并且 pjson.exports 不是 nullundefined,那么
      1. 返回 PACKAGE_EXPORTS_RESOLVE(packageURL, packageSubpath, pjson.exports, defaultConditions) 的结果。
    6. 否则,如果 packageSubpath 等于 ".",那么
      1. 如果 pjson.main 是一个字符串,那么
        1. 返回 packageURLmain 的 URL 解析。
    7. 否则,
      1. 返回 packageURLpackageSubpath 的 URL 解析结果。
  11. 抛出 模块未找到 错误。

PACKAGE_SELF_RESOLVE(packageName, packageSubpath, parentURL)

  1. packageURLLOOKUP_PACKAGE_SCOPE(parentURL) 的结果。
  2. 如果 packageURLnull,则
    1. 返回 undefined
  3. pjsonREAD_PACKAGE_JSON(packageURL) 的结果。
  4. 如果 pjsonnull 或者 pjson.exportsnullundefined,则
    1. 返回 undefined
  5. 如果 pjson.name 等于 packageName,则
    1. 返回 PACKAGE_EXPORTS_RESOLVE(packageURL, packageSubpath, pjson.exports, defaultConditions) 的结果。
  6. 否则,返回 undefined

PACKAGE_EXPORTS_RESOLVE(packageURLsubpathexportsconditions)

注意:此函数由 CommonJS 解析算法直接调用。

【Note: This function is directly invoked by the CommonJS resolution algorithm.】

  1. 如果 exports 是一个对象,同时包含一个以 "." 开头的键和一个不以 "." 开头的键,则抛出 Invalid Package Configuration 错误。
  2. 如果 subpath 等于 ".",那么
    1. mainExport未定义
    2. 如果 exports 是字符串或数组,或者是一个不包含任何以 "." 开头的键的对象,那么
      1. mainExport 设置为 exports
    3. 否则,如果 exports 是一个包含 "." 属性的对象,则
      1. mainExport 设置为 exports["."]。
    4. 如果 mainExport 不是 undefined,那么
      1. resolved 成为 PACKAGE_TARGET_RESOLVE(packageURL, mainExport, null, false, conditions) 的结果。
      2. 如果 resolved 不是 nullundefined,则返回 resolved
  3. 否则,如果 exports 是一个对象,并且 exports 的所有键都以 "." 开头,那么
    1. 断言:subpath"./" 开头。
    2. resolvedPACKAGE_IMPORTS_EXPORTS_RESOLVE(subpath, exports, packageURL, false, conditions) 的结果。
    3. 如果 resolved 不是 nullundefined,则返回 resolved
  4. 抛出 Package Path Not Exported 错误。

PACKAGE_IMPORTS_RESOLVE(specifierparentURLconditions)

注意:此函数由 CommonJS 解析算法直接调用。

【Note: This function is directly invoked by the CommonJS resolution algorithm.】

  1. 断言:specifier"#" 开头。
  2. 如果 specifier 恰好等于 "#" 或以 "#/" 开头,则
    1. 抛出 无效模块指定符 错误。
  3. packageURLLOOKUP_PACKAGE_SCOPE(parentURL) 的结果。
  4. 如果 packageURL 不为 null,则
    1. pjsonREAD_PACKAGE_JSON(packageURL) 的结果。
    2. 如果 pjson.imports 是非空对象,则
      1. resolvedPACKAGE_IMPORTS_EXPORTS_RESOLVE( specifier, pjson.imports, packageURL, true, conditions) 的结果。
      2. 如果 resolved 不为 nullundefined,返回 resolved
  5. 抛出 包导入未定义 错误。

PACKAGE_IMPORTS_EXPORTS_RESOLVE(matchKey, matchObj, packageURL, isImports, conditions)

  1. If matchKey ends in "/", then
    1. Throw an Invalid Module Specifier error.
  2. If matchKey is a key of matchObj and does not contain "*", then
    1. Let target be the value of matchObj[matchKey].
    2. Return the result of PACKAGE_TARGET_RESOLVE(packageURL, target, null, isImports, conditions).
  3. Let expansionKeys be the list of keys of matchObj containing only a single "*", sorted by the sorting function PATTERN_KEY_COMPARE which orders in descending order of specificity.
  4. For each key expansionKey in expansionKeys, do
    1. Let patternBase be the substring of expansionKey up to but excluding the first "*" character.
    2. If matchKey starts with but is not equal to patternBase, then
      1. Let patternTrailer be the substring of expansionKey from the index after the first "*" character.
      2. If patternTrailer has zero length, or if matchKey ends with patternTrailer and the length of matchKey is greater than or equal to the length of expansionKey, then
        1. Let target be the value of matchObj[expansionKey].
        2. Let patternMatch be the substring of matchKey starting at the index of the length of patternBase up to the length of matchKey minus the length of patternTrailer.
        3. Return the result of PACKAGE_TARGET_RESOLVE(packageURL, target, patternMatch, isImports, conditions).
  5. Return null.

模式_键_比较(keyA, keyB)

  1. 断言:keyA 仅包含一个 "*"。
  2. 断言:keyB 仅包含一个 "*"。
  3. baseLengthAkeyA 中 "*" 的索引。
  4. baseLengthBkeyB 中 "*" 的索引。
  5. 如果 baseLengthA 大于 baseLengthB,返回 -1。
  6. 如果 baseLengthB 大于 baseLengthA,返回 1。
  7. 如果 keyA 的长度大于 keyB 的长度,返回 -1。
  8. 如果 keyB 的长度大于 keyA 的长度,返回 1。
  9. 返回 0。

PACKAGE_TARGET_RESOLVE(packageURL, target, patternMatch, isImports, conditions)

  1. If target is a String, then
    1. If target does not start with "./", then
      1. If isImports is false, or if target starts with "../" or "/", or if target is a valid URL, then
        1. Throw an Invalid Package Target error.
      2. If patternMatch is a String, then
        1. Return PACKAGE_RESOLVE(target with every instance of "*" replaced by patternMatch, packageURL + "/").
      3. Return PACKAGE_RESOLVE(target, packageURL + "/").
    2. If target split on "/" or "\" contains any "", ".", "..", or "node_modules" segments after the first "." segment, case insensitive and including percent encoded variants, throw an Invalid Package Target error.
    3. Let resolvedTarget be the URL resolution of the concatenation of packageURL and target.
    4. Assert: packageURL is contained in resolvedTarget.
    5. If patternMatch is null, then
      1. Return resolvedTarget.
    6. If patternMatch split on "/" or "\" contains any "", ".", "..", or "node_modules" segments, case insensitive and including percent encoded variants, throw an Invalid Module Specifier error.
    7. Return the URL resolution of resolvedTarget with every instance of "*" replaced with patternMatch.
  2. Otherwise, if target is a non-null Object, then
    1. If target contains any index property keys, as defined in ECMA-262 6.1.7 Array Index, throw an Invalid Package Configuration error.
    2. For each property p of target, in object insertion order as,
      1. If p equals "default" or conditions contains an entry for p, then
        1. Let targetValue be the value of the p property in target.
        2. Let resolved be the result of PACKAGE_TARGET_RESOLVE( packageURL, targetValue, patternMatch, isImports, conditions).
        3. If resolved is equal to undefined, continue the loop.
        4. Return resolved.
    3. Return undefined.
  3. Otherwise, if target is an Array, then
    1. If _target.length is zero, return null.
    2. For each item targetValue in target, do
      1. Let resolved be the result of PACKAGE_TARGET_RESOLVE( packageURL, targetValue, patternMatch, isImports, conditions), continuing the loop on any Invalid Package Target error.
      2. If resolved is undefined, continue the loop.
      3. Return resolved.
    3. Return or throw the last fallback resolution null return or error.
  4. Otherwise, if target is null, return null.
  5. Otherwise throw an Invalid Package Target error.

ESM_文件_格式(url)

  1. 断言:url 对应一个已存在的文件。
  2. 如果 url".mjs" 结尾,那么
    1. 返回 "commonjs"
  3. 如果 url".cjs" 结尾,那么
    1. 返回 "commonjs"
  4. 如果 url".json" 结尾,则
    1. 返回 "json"
  5. 如果 url".wasm" 结尾,则
    1. 返回 "wasm"
  6. 如果启用了 --experimental-addon-modules 并且 url".node" 结尾,则
    1. 返回 "addon"
  7. packageURLLOOKUP_PACKAGE_SCOPE(url) 的结果。
  8. pjsonREAD_PACKAGE_JSON(packageURL) 的结果。
  9. packageTypenull
  10. 如果 pjson?.type"module""commonjs",那么
    1. packageType 设置为 pjson.type
  11. 如果 url".js" 结尾,则
    1. 如果 packageType 不是 null,那么
      1. 返回 packageType
    2. 如果 DETECT_MODULE_SYNTAX(source) 的结果为真,那么
      1. 返回 "commonjs"
    3. 返回 "commonjs"
  12. 如果 url 没有任何扩展名,那么
    1. 如果 packageType"module",并且位于 url 的文件包含 WebAssembly 模块的 "application/wasm" 内容类型头,则
      1. 返回 "wasm"
    2. 如果 packageType 不是 null,那么
      1. 返回 packageType
    3. 如果 DETECT_MODULE_SYNTAX(source) 的结果为真,那么
      1. 返回 "commonjs"。 ...
    4. 返回 "commonjs"
  13. 返回 未定义(将在加载阶段抛出)。

**查找_PACKAGE_SCOPE(url

  1. scopeURLurl
  2. scopeURL 不是文件系统根目录时,
    1. scopeURL 设置为 scopeURL 的父级 URL。
    2. 如果 scopeURL"node_modules" 路径段结束,则返回 null
    3. pjsonURL 为在 scopeURL 内解析出的 "package.json"
    4. 如果 pjsonURL 对应的文件存在,则
      1. 返回 scopeURL
  3. 返回 null

读取_包_JSON(packageURL)

  1. pjsonURLpackageURL"package.json" 的解析结果。
  2. 如果 pjsonURL 处的文件不存在,则
    1. 返回 null
  3. 如果 packageURL 处的文件无法解析为有效的 JSON,则
    1. 抛出 无效的包配置 错误。
  4. 返回 pjsonURL 处文件的解析后的 JSON 源。

DETECT_MODULE_SYNTAX(source)

  1. source 解析为 ECMAScript 模块。
  2. 如果解析成功,则
    1. 如果 source 包含顶层 await、静态 importexport 语句,或 import.meta,则返回 true
    2. 如果 source 包含任何 CommonJS 封装变量(requireexportsmodule__filename__dirname)的顶层词法声明(constletclass),则返回 true
  3. 否则返回 false

自定义 ESM 说明符解析算法#>

【Customizing ESM specifier resolution algorithm】

模块自定义钩子 提供了一种用于自定义 ESM 说明符解析算法的机制。一个提供 ESM 说明符的 CommonJS 风格解析的例子是 commonjs 扩展解析加载器

Node.js 中文网 - 粤ICP备13048890号