- assert 断言
- async_hooks 异步钩子
- async_hooks/context 异步上下文
- buffer 缓冲区
- C++插件
- C/C++插件(使用 Node-API)
- C++嵌入器
- child_process 子进程
- cluster 集群
- CLI 命令行
- console 控制台
- Corepack 核心包
- crypto 加密
- crypto/webcrypto 网络加密
- debugger 调试器
- deprecation 弃用
- dgram 数据报
- diagnostics_channel 诊断通道
- dns 域名服务器
- domain 域
- Error 错误
- events 事件触发器
- fs 文件系统
- global 全局变量
- http 超文本传输协议
- http2 超文本传输协议 2.0
- https 安全超文本传输协议
- inspector 检查器
- Intl 国际化
- module 模块
- module/cjs CommonJS 模块
- module/esm ECMAScript 模块
- module/package 包模块
- net 网络
- os 操作系统
- path 路径
- perf_hooks 性能钩子
- permission 权限
- process 进程
- punycode 域名代码
- querystring 查询字符串
- readline 逐行读取
- repl 交互式解释器
- report 诊断报告
- stream 流
- stream/web 网络流
- string_decoder 字符串解码器
- test 测试
- timers 定时器
- tls 安全传输层
- trace_events 跟踪事件
- tty 终端
- url 网址
- util 实用工具
- v8 引擎
- vm 虚拟机
- wasi 网络汇编系统接口
- worker_threads 工作线程
- zlib 压缩
Node.js v16.20.2 文档
- Node.js v16.20.2
- 目录
-
导航
- assert 断言
- async_hooks 异步钩子
- async_hooks/context 异步上下文
- buffer 缓冲区
- C++插件
- C/C++插件(使用 Node-API)
- C++嵌入器
- child_process 子进程
- cluster 集群
- CLI 命令行
- console 控制台
- Corepack 核心包
- crypto 加密
- crypto/webcrypto 网络加密
- debugger 调试器
- deprecation 弃用
- dgram 数据报
- diagnostics_channel 诊断通道
- dns 域名服务器
- domain 域
- Error 错误
- events 事件触发器
- fs 文件系统
- global 全局变量
- http 超文本传输协议
- http2 超文本传输协议 2.0
- https 安全超文本传输协议
- inspector 检查器
- Intl 国际化
- module 模块
- module/cjs CommonJS 模块
- module/esm ECMAScript 模块
- module/package 包模块
- net 网络
- os 操作系统
- path 路径
- perf_hooks 性能钩子
- permission 权限
- process 进程
- punycode 域名代码
- querystring 查询字符串
- readline 逐行读取
- repl 交互式解释器
- report 诊断报告
- stream 流
- stream/web 网络流
- string_decoder 字符串解码器
- test 测试
- timers 定时器
- tls 安全传输层
- trace_events 跟踪事件
- tty 终端
- url 网址
- util 实用工具
- v8 引擎
- vm 虚拟机
- wasi 网络汇编系统接口
- worker_threads 工作线程
- zlib 压缩
- 其他版本
模块:ECMAScript 模块#>
【Modules: ECMAScript modules】
介绍#>
【Introduction】
ECMAScript 模块用于打包可重用的 JavaScript 代码。模块是使用各种 import 和 export 语句定义的。
【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 的 "type" 字段或 --input-type 标志告诉 Node.js 使用 ECMAScript 模块加载器。在这些情况之外,Node.js 将使用 CommonJS 模块加载器。更多详情请参见 确定模块系统。
【Authors can tell Node.js to use the ECMAScript modules loader
via the .mjs file extension, the package.json "type" field, or the
--input-type flag. Outside of those cases, Node.js will use the CommonJS
module loader. See Determining module system for more details.】
包#>
【Packages】
本节已移至 Modules: Packages。
【This section was moved to Modules: Packages.】
import 说明符#>
【import Specifiers】
术语#>
【Terminology】
import 语句的 specifier 是 from 关键字之后的字符串,例如 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 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用于 JSONapplication/wasm用于 Wasm
import 'data:text/javascript,console.log("hello!");';
import _ from 'data:application/json,"world!"' assert { 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 assertions】
导入断言提案 为模块导入语句添加了内联语法,以便在模块说明符旁传递更多信息。
【The Import Assertions proposal adds an inline syntax for module import statements to pass on more information alongside the module specifier.】
import fooData from './foo.json' assert { type: 'json' };
const { default: barData } =
await import('./bar.json', { assert: { type: 'json' } });
Node.js 支持以下 type 值,对于这些值,断言是强制性的:
【Node.js supports the following type values, for which the assertion is
mandatory:】
断言 type | 所需 |
|---|---|
'json' | JSON 模块 |
内置模块#>
【Builtin 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,包含以下属性。
【The import.meta meta property is an Object that contains the following
properties.】
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.resolve(specifier[, parent])#>
此功能仅在启用 --experimental-import-meta-resolve 命令标志时可用。
【This feature is only available with the --experimental-import-meta-resolve
command flag enabled.】
specifier<string> 相对于parent需要解析的模块说明符。parent<string> | <URL> 用于解析的绝对父模块 URL。如果未指定,则默认使用import.meta.url的值。- 返回: <Promise>
提供一个相对于模块的解析函数,作用域限定在每个模块内,返回 URL 字符串。
【Provides a module-relative resolution function scoped to each module, returning the URL string.】
const dependencyAsset = await import.meta.resolve('component-lib/asset.css');
import.meta.resolve 也接受第二个参数,它是用来指定解析来源的父模块:
await import.meta.resolve('./dep', import.meta.url);
这个函数是异步的,因为 Node.js 中的 ES 模块解析器允许异步操作。
【This function is asynchronous because the ES module resolver in Node.js is allowed to be asynchronous.】
与 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 总是将它引用的文件视为 CommonJS 模块。
【The CommonJS module require always treats the files it references as CommonJS.】
使用 require 加载 ES 模块不被支持,因为 ES 模块是异步执行的。相反,应使用 import() 从 CommonJS 模块加载 ES 模块。
【Using require to load an ES module is not supported because ES modules have
asynchronous execution. Instead, use import() to load an ES module
from a CommonJS module.】
CommonJS 命名空间#>
【CommonJS Namespaces】
CommonJS 模块由一个 module.exports 对象组成,该对象可以是任意类型。
【CommonJS modules consist of a module.exports object which can be of any type.】
在导入 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';
// The following import statement is "syntax sugar" (equivalent but sweeter)
// for `{ default as cjsSugar }` in the above import statement:
import cjsSugar from 'cjs';
console.log(cjs);
console.log(cjs === cjsSugar);
// Prints:
// <module.exports>
// true
CommonJS 模块的 ECMAScript 模块命名空间表示始终是一个带有 default 导出键的命名空间,该键指向 CommonJS 的 module.exports 值。
【The ECMAScript Module Namespace representation of a CommonJS module is always
a namespace with a default export key pointing to the CommonJS
module.exports value.】
当使用 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> }
// 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' }, 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】
没有 require、exports 或 module.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.url 进行复制。
没有原生模块加载#>
【No Native Module Loading】
ES 模块导入当前不支持原生模块。
【Native modules are not currently supported with ES module imports.】
它们可以改为装载 module.createRequire() 或 process.dlopen。
【They can instead be loaded with module.createRequire() or
process.dlopen.】
没有 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 a flagged experimental
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' assert { type: 'json' };
assert { type: 'json' } 语法是必须的;见 导入断言。
【The assert { type: 'json' } syntax is mandatory; see Import Assertions.】
导入的 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】
在 --experimental-wasm-modules 标志下支持导入 WebAssembly 模块,允许将任何 .wasm 文件作为普通模块导入,同时也支持它们的模块导入。
【Importing WebAssembly modules is supported under the
--experimental-wasm-modules flag, allowing any .wasm files to be
imported as normal modules while also supporting their module imports.】
此整合符合 WebAssembly 的 ES 模块集成提案。
【This integration is in line with the ES Module Integration Proposal for WebAssembly.】
例如,一个包含以下内容的 index.mjs:
【For example, an index.mjs containing:】
import * as M from './module.wasm';
console.log(M);
在以下条件下执行:
【executed under:】
node --experimental-wasm-modules index.mjs
将为 module.wasm 的实例化提供导出接口。
【would provide the exports interface for the instantiation of module.wasm.】
顶层 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`
});
HTTPS 和 HTTP 导入#>
【HTTPS and HTTP imports】
在启用 --experimental-network-imports 标志时,支持使用 https: 和 http: 导入基于网络的模块。这允许在 Node.js 中进行类似 Web 浏览器的导入,但由于在受信环境中运行而非浏览器沙箱时的应用稳定性和安全性考虑,会有一些差异。
【Importing network based modules using https: and http: is supported under
the --experimental-network-imports flag. This allows web browser-like imports
to work in Node.js with a few differences due to application stability and
security concerns that are different when running in a privileged environment
instead of a browser sandbox.】
导入仅限于 HTTP/1#>
【Imports are limited to HTTP/1】
尚不支持 HTTP/2 和 HTTP/3 的自动协议协商。
【Automatic protocol negotiation for HTTP/2 and HTTP/3 is not yet supported.】
HTTP 仅限于环回地址#>
【HTTP is limited to loopback addresses】
http: 容易受到中间人攻击,不允许用于 IPv4 地址 127.0.0.0/8(127.0.0.1 到 127.255.255.255)和 IPv6 地址 ::1 之外的地址。对 http: 的支持旨在用于本地开发。
身份验证永远不会发送到目标服务器。#>
【Authentication is never sent to the destination server.】
Authorization、Cookie 和 Proxy-Authorization 头信息不会被发送到服务器。避免在导入的 URL 的部分中包含用户信息。正在开发一种在服务器上安全使用这些的安全模型。
永远不会在目标服务器上检查 CORS#>
【CORS is never checked on the destination server】
CORS 的设计目的是允许服务器将 API 的使用者限制在特定的一组主机上。这不被支持,因为对于基于服务器的实现来说,这没有意义。
【CORS is designed to allow a server to limit the consumers of an API to a specific set of hosts. This is not supported as it does not make sense for a server-based implementation.】
无法加载非网络依赖#>
【Cannot load non-network dependencies】
这些模块无法访问不是通过 http: 或 https: 的其他模块。要在避免安全问题的同时仍然访问本地模块,请传入对本地依赖的引用:
【These modules cannot access other modules that are not over http: or https:.
To still access local modules while avoiding the security concern, pass in
references to the local dependencies:】
// file.mjs
import worker_threads from 'node:worker_threads';
import { configure, resize } from 'https://example.com/imagelib.mjs';
configure({ worker_threads });
// https://example.com/imagelib.mjs
let worker_threads;
export function configure(opts) {
worker_threads = opts.worker_threads;
}
export function resize(img, size) {
// Perform resizing in worker_thread to avoid main thread blocking
}
默认情况下不启用基于网络的加载#>
【Network-based loading is not enabled by default】
目前,需要使用 --experimental-network-imports 标志来启用通过 http: 或 https: 加载资源。将来,将使用不同的机制来强制执行这一点。需要选择加入,以防止传递性依赖意外使用可能影响 Node.js 应用可靠性的可变状态。
【For now, the --experimental-network-imports flag is required to enable loading
resources over http: or https:. In the future, a different mechanism will be
used to enforce this. Opt-in is required to prevent transitive dependencies
inadvertently using potentially mutable state that could affect reliability
of Node.js applications.】
加载器#>
【Loaders】
该 API 目前正在重新设计,仍可能会有所变动。
要自定义默认模块解析,可以通过向 Node.js 提供 --experimental-loader ./loader-name.mjs 参数来可选地提供加载器钩子。
【To customize the default module resolution, loader hooks can optionally be
provided via a --experimental-loader ./loader-name.mjs argument to Node.js.】
当使用 hooks 时,它们会应用于入口点和所有 import 调用。它们不会应用于 require 调用;这些仍然遵循 CommonJS 规则。
【When hooks are used they apply to the entry point and all import calls. They
won't apply to require calls; those still follow CommonJS rules.】
加载器遵循 --require 的模式:
【Loaders follow the pattern of --require:】
node \
--experimental-loader unpkg \
--experimental-loader http-to-https \
--experimental-loader cache-buster
这些按以下顺序调用:cache-buster 调用 http-to-https,然后调用 unpkg。
【These are called in the following sequence: cache-buster calls
http-to-https which calls unpkg.】
钩子#>
【Hooks】
钩子是链的一部分,即使该链只包含一个自定义(用户提供的)钩子和始终存在的默认钩子。钩子函数是嵌套的:每个钩子函数必须总是返回一个普通对象,而链式调用的发生是因为每个函数都会调用 next<hookName>(),它是对后续加载器钩子的引用。
返回缺少必需属性的值的 hook 会触发异常。返回时既未调用 next<hookName>() 又未返回 shortCircuit: true 的 hook 也会触发异常。这些错误是为了帮助防止链条中意外的中断。
resolve(specifier, context, nextResolve)#>
加载器 API 正在重新设计。此钩子可能会消失或其签名可能会更改。请不要依赖下面描述的 API。
specifier<string>context<Object>conditions<string[]> 相关package.json的导出条件importAssertions<Object>parentURL<string> | <undefined> 导入此模块的模块,如果这是 Node.js 入口点,则为 undefined
nextResolve<Function> 链中后续的resolve钩子,或者在最后一个用户提供的resolve钩子之后的 Node.js 默认resolve钩子- 返回值: <Object>
format<string> | <null> | <undefined> 对 load 钩子的提示(可能会被忽略)'builtin' | 'commonjs' | 'json' | 'module' | 'wasm'shortCircuit<undefined> | <boolean> 一个信号,表示此钩子打算终止resolve钩子链。默认:falseurl<string> 该输入解析到的绝对 URL
resolve 钩子链负责为给定的模块说明符和父 URL 解析文件 URL,并可选择提供其格式(例如 'module')作为 load 钩子的提示。如果指定了格式,load 钩子最终负责提供最终的 format 值(并且可以选择忽略 resolve 提供的提示);如果 resolve 提供了 format,即使只是为了将该值传递给 Node.js 默认的 load 钩子,也需要自定义 load 钩子。
【The resolve hook chain is responsible for resolving file URL for a given
module specifier and parent URL, and optionally its format (such as 'module')
as a hint to the load hook. If a format is specified, the load hook is
ultimately responsible for providing the final format value (and it is free to
ignore the hint provided by resolve); if resolve provides a format, a
custom load hook is required even if only to pass the value to the Node.js
default load hook.】
模块指定符是在 import 语句或 import() 表达式中的字符串。
【The module specifier is the string in an import statement or
import() expression.】
父 URL 是导入此模块的模块的 URL,如果这是应用的主入口点,则为 undefined。
【The parent URL is the URL of the module that imported this one, or undefined
if this is the main entry point for the application.】
context 中的 conditions 属性是一个适用于此解析请求的 包导出条件 条件数组。它们可以用于在其他地方查找条件映射,或者在调用默认解析逻辑时修改列表。
【The conditions property in context is an array of conditions for
package exports conditions that apply to this resolution
request. They can be used for looking up conditional mappings elsewhere or to
modify the list when calling the default resolution logic.】
当前的 包导出条件 总是包含在传入 hook 的 context.conditions 数组中。为了在调用 defaultResolve 时保证 默认的 Node.js 模块指定符解析行为,传递给它的 context.conditions 数组 必须 包含最初传入 resolve hook 的 context.conditions 数组的 所有 元素。
【The current package exports conditions are always in
the context.conditions array passed into the hook. To guarantee default
Node.js module specifier resolution behavior when calling defaultResolve, the
context.conditions array passed to it must include all elements of the
context.conditions array originally passed into the resolve hook.】
export async function resolve(specifier, context, nextResolve) {
const { parentURL = null } = context;
if (Math.random() > 0.5) { // Some condition.
// For some or all specifiers, do some custom logic for resolving.
// Always return an object of the form {url: <string>}.
return {
shortCircuit: true,
url: parentURL ?
new URL(specifier, parentURL).href :
new URL(specifier).href,
};
}
if (Math.random() < 0.5) { // Another condition.
// When calling `defaultResolve`, the arguments can be modified. In this
// case it's adding another value for matching conditional exports.
return nextResolve(specifier, {
...context,
conditions: [...context.conditions, 'another-condition'],
});
}
// Defer to the next hook in the chain, which would be the
// Node.js default resolve if this is the last user-specified loader.
return nextResolve(specifier);
}
load(url, context, nextLoad)#>
加载器 API 正在重新设计。此钩子可能会消失或其签名可能会更改。请不要依赖下面描述的 API。
在此 API 的早期版本中,这被拆分成三个独立的、现已弃用的钩子(
getFormat、getSource和transformSource)。
url<string>resolve链返回的 URLcontext<Object>conditions<string[]> 相关package.json的导出条件format<string> | <null> | <undefined> 可由resolve钩子链可选提供的格式importAssertions<Object>
nextLoad<Function> 链中随后的load钩子,或最后一个用户提供的load钩子之后的 Node.js 默认load钩子- 返回值: <Object>
format<string>shortCircuit<undefined> | <boolean> 表示该钩子打算终止resolve钩子链的信号。默认值:falsesource<string> | <ArrayBuffer> | <TypedArray> Node.js 用于评估的源代码
load 钩子提供了一种定义自定义方法的方法,用于确定 URL 应该如何被解释、获取和解析。它还负责验证导入断言。
【The load hook provides a way to define a custom method of determining how
a URL should be interpreted, retrieved, and parsed. It is also in charge of
validating the import assertion.】
format 的最终值必须是以下之一:
【The final value of format must be one of the following:】
format | 描述 | load 返回的 source 可接受类型 |
|---|---|---|
'builtin' | 加载 Node.js 内置模块 | 不适用 |
'commonjs' | 加载 Node.js CommonJS 模块 | 不适用 |
'json' | 加载 JSON 文件 | { string, ArrayBuffer, TypedArray } |
'module' | 加载 ES 模块 | { string, ArrayBuffer, TypedArray } |
'wasm' | 加载 WebAssembly 模块 | { ArrayBuffer, TypedArray } |
source 的值在类型 'builtin' 下会被忽略,因为目前无法替换 Node.js 内置(核心)模块的值。source 的值在类型 'commonjs' 下也会被忽略,因为 CommonJS 模块加载器没有为 ES 模块加载器提供覆盖 CommonJS 模块返回值 的机制。这个限制在未来可能会被克服。
【The value of source is ignored for type 'builtin' because currently it is
not possible to replace the value of a Node.js builtin (core) module. The value
of source is ignored for type 'commonjs' because the CommonJS module loader
does not provide a mechanism for the ES module loader to override the
CommonJS module return value. This limitation might be
overcome in the future.】
注意:ESM 的
load钩子与来自 CommonJS 模块的命名空间导出不兼容。尝试将它们一起使用会导致导入结果为空对象。未来可能会对此进行处理。
这些类型都对应于 ECMAScript 中定义的类。
- 特定的
ArrayBuffer对象是SharedArrayBuffer。 - 特定的
TypedArray对象是Uint8Array。
如果基于文本的格式(即 'json'、'module')的源值不是字符串,它将使用 util.TextDecoder 转换为字符串。
【If the source value of a text-based format (i.e., 'json', 'module')
is not a string, it is converted to a string using util.TextDecoder.】
load 钩子提供了一种定义自定义方法以获取 ES 模块标识符的源代码的方式。这允许加载器有可能避免从磁盘读取文件。它也可以用于将未识别的格式映射到受支持的格式,例如将 yaml 映射为 module。
【The load hook provides a way to define a custom method for retrieving the
source code of an ES module specifier. This would allow a loader to potentially
avoid reading files from disk. It could also be used to map an unrecognized
format to a supported one, for example yaml to module.】
export async function load(url, context, nextLoad) {
const { format } = context;
if (Math.random() > 0.5) { // Some condition
/*
For some or all URLs, do some custom logic for retrieving the source.
Always return an object of the form {
format: <string>,
source: <string|buffer>,
}.
*/
return {
format,
shortCircuit: true,
source: '...',
};
}
// Defer to the next hook in the chain.
return nextLoad(url);
}
在更高级的场景中,这也可以用来将不受支持的源转换为受支持的源(见下方示例)。
【In a more advanced scenario, this can also be used to transform an unsupported source to a supported one (see Examples below).】
globalPreload()#>
加载器 API 正在重新设计。此钩子可能会消失或其签名可能会更改。请不要依赖下面描述的 API。
在该 API 的早期版本中,此钩子被命名为
getGlobalPreloadCode。
context<Object> 用于辅助预加载代码的信息port<MessagePort>
- 返回: <string> 应用启动前运行的代码
有时可能需要在应用运行的同一全局作用域中运行一些代码。这个钩子允许返回一个字符串,该字符串将在启动时作为宽松模式脚本运行。
【Sometimes it might be necessary to run some code inside of the same global scope that the application runs in. This hook allows the return of a string that is run as a sloppy-mode script on startup.】
与 CommonJS 封装器的工作方式类似,代码在一个隐式函数作用域中运行。唯一的参数是一个类似 require 的函数,可用于加载内置模块,如 “fs”:getBuiltin(request: string)。
【Similar to how CommonJS wrappers work, the code runs in an implicit function
scope. The only argument is a require-like function that can be used to load
builtins like "fs": getBuiltin(request: string).】
如果代码需要更高级的 require 功能,它必须使用 module.createRequire() 自行构建 require。
【If the code needs more advanced require features, it has to construct
its own require using module.createRequire().】
export function globalPreload(context) {
return `\
globalThis.someInjectedProperty = 42;
console.log('I just set some globals!');
const { createRequire } = getBuiltin('module');
const { cwd } = getBuiltin('process');
const require = createRequire(cwd() + '/<preload>');
// [...]
`;
}
为了允许应用与加载器之间的通信,预加载代码提供了另一个参数:port。这个参数可以作为加载器钩子的参数使用,也可以在钩子返回的源代码中使用。为了正确调用 port.ref() 和 port.unref(),需要小心,以防止进程处于无法正常关闭的状态。
【In order to allow communication between the application and the loader, another
argument is provided to the preload code: port. This is available as a
parameter to the loader hook and inside of the source text returned by the hook.
Some care must be taken in order to properly call port.ref() and
port.unref() to prevent a process from being in a state where it won't
close normally.】
/**
* This example has the application context send a message to the loader
* and sends the message back to the application context
*/
export function globalPreload({ port }) {
port.onmessage = (evt) => {
port.postMessage(evt.data);
};
return `\
port.postMessage('console.log("I went to the Loader and back");');
port.onmessage = (evt) => {
eval(evt.data);
};
`;
}
示例#>
【Examples】
各种加载器钩子可以一起使用,以实现对 Node.js 代码加载和执行行为的广泛自定义。
【The various loader hooks can be used together to accomplish wide-ranging customizations of the Node.js code loading and evaluation behaviors.】
HTTPS 加载器#>
【HTTPS loader】
在当前的 Node.js 中,以 https:// 开头的说明符是实验性的(参见 HTTPS 和 HTTP 导入)。
【In current Node.js, specifiers starting with https:// are experimental (see
HTTPS and HTTP imports).】
下面的加载器注册了钩子,以支持此类说明符的基本功能。虽然这看起来像是对 Node.js 核心功能的显著改进,但实际上使用此加载器有很大的缺点:性能比从磁盘加载文件慢得多,没有缓存,也没有安全性。
【The loader below registers hooks to enable rudimentary support for such specifiers. While this may seem like a significant improvement to Node.js core functionality, there are substantial downsides to actually using this loader: performance is much slower than loading files from disk, there is no caching, and there is no security.】
// https-loader.mjs
import { get } from 'node:https';
export function resolve(specifier, context, nextResolve) {
const { parentURL = null } = context;
// Normally Node.js would error on specifiers starting with 'https://', so
// this hook intercepts them and converts them into absolute URLs to be
// passed along to the later hooks below.
if (specifier.startsWith('https://')) {
return {
shortCircuit: true,
url: specifier
};
} else if (parentURL && parentURL.startsWith('https://')) {
return {
shortCircuit: true,
url: new URL(specifier, parentURL).href,
};
}
// Let Node.js handle all other specifiers.
return nextResolve(specifier);
}
export function load(url, context, nextLoad) {
// For JavaScript to be loaded over the network, we need to fetch and
// return it.
if (url.startsWith('https://')) {
return new Promise((resolve, reject) => {
get(url, (res) => {
let data = '';
res.on('data', (chunk) => data += chunk);
res.on('end', () => resolve({
// This example assumes all network-provided JavaScript is ES module
// code.
format: 'module',
shortCircuit: true,
source: data,
}));
}).on('error', (err) => reject(err));
});
}
// Let Node.js handle all other URLs.
return nextLoad(url);
}
// main.mjs
import { VERSION } from 'https://coffeescript.org/browser-compiler-modern/coffeescript.js';
console.log(VERSION);
使用前面的加载器,运行
node --experimental-loader ./https-loader.mjs ./main.mjs
会打印出 main.mjs 中 URL 对应模块的当前 CoffeeScript 版本。
【With the preceding loader, running
node --experimental-loader ./https-loader.mjs ./main.mjs
prints the current version of CoffeeScript per the module at the URL in
main.mjs.】
转译加载器#>
【Transpiler loader】
Node.js 无法理解的格式来源可以使用 load 钩子 转换为 JavaScript。然而,在调用该钩子之前,resolve 钩子 需要告诉 Node.js 不要对未知的文件类型抛出错误。
【Sources that are in formats Node.js doesn't understand can be converted into
JavaScript using the load hook. Before that hook gets called,
however, a resolve hook needs to tell Node.js not to
throw an error on unknown file types.】
这比在运行 Node.js 之前先转换源文件的性能要低;转换器加载器应该仅用于开发和测试目的。
【This is less performant than transpiling source files before running Node.js; a transpiler loader should only be used for development and testing purposes.】
// coffeescript-loader.mjs
import { readFile } from 'node:fs/promises';
import { dirname, extname, resolve as resolvePath } from 'node:path';
import { cwd } from 'node:process';
import { fileURLToPath, pathToFileURL } from 'node:url';
import CoffeeScript from 'coffeescript';
const baseURL = pathToFileURL(`${cwd()}/`).href;
// CoffeeScript files end in .coffee, .litcoffee, or .coffee.md.
const extensionsRegex = /\.coffee$|\.litcoffee$|\.coffee\.md$/;
export async function resolve(specifier, context, nextResolve) {
if (extensionsRegex.test(specifier)) {
const { parentURL = baseURL } = context;
// Node.js normally errors on unknown file extensions, so return a URL for
// specifiers ending in the CoffeeScript file extensions.
return {
shortCircuit: true,
url: new URL(specifier, parentURL).href
};
}
// Let Node.js handle all other specifiers.
return nextResolve(specifier);
}
export async function load(url, context, nextLoad) {
if (extensionsRegex.test(url)) {
// Now that we patched resolve to let CoffeeScript URLs through, we need to
// tell Node.js what format such URLs should be interpreted as. Because
// CoffeeScript transpiles into JavaScript, it should be one of the two
// JavaScript formats: 'commonjs' or 'module'.
// CoffeeScript files can be either CommonJS or ES modules, so we want any
// CoffeeScript file to be treated by Node.js the same as a .js file at the
// same location. To determine how Node.js would interpret an arbitrary .js
// file, search up the file system for the nearest parent package.json file
// and read its "type" field.
const format = await getPackageType(url);
// When a hook returns a format of 'commonjs', `source` is be ignored.
// To handle CommonJS files, a handler needs to be registered with
// `require.extensions` in order to process the files with the CommonJS
// loader. Avoiding the need for a separate CommonJS handler is a future
// enhancement planned for ES module loaders.
if (format === 'commonjs') {
return {
format,
shortCircuit: true,
};
}
const { source: rawSource } = await nextLoad(url, { ...context, format });
// This hook converts CoffeeScript source code into JavaScript source code
// for all imported CoffeeScript files.
const transformedSource = coffeeCompile(rawSource.toString(), url);
return {
format,
shortCircuit: true,
source: transformedSource,
};
}
// Let Node.js handle all other URLs.
return nextLoad(url);
}
async function getPackageType(url) {
// `url` is only a file path during the first iteration when passed the
// resolved url from the load() hook
// an actual file path from load() will contain a file extension as it's
// required by the spec
// this simple truthy check for whether `url` contains a file extension will
// work for most projects but does not cover some edge-cases (such as
// extensionless files or a url ending in a trailing space)
const isFilePath = !!extname(url);
// If it is a file path, get the directory it's in
const dir = isFilePath ?
dirname(fileURLToPath(url)) :
url;
// Compose a file path to a package.json in the same directory,
// which may or may not exist
const packagePath = resolvePath(dir, 'package.json');
// Try to read the possibly nonexistent package.json
const type = await readFile(packagePath, { encoding: 'utf8' })
.then((filestring) => JSON.parse(filestring).type)
.catch((err) => {
if (err?.code !== 'ENOENT') console.error(err);
});
// Ff package.json existed and contained a `type` field with a value, voila
if (type) return type;
// Otherwise, (if not at the root) continue checking the next directory up
// If at the root, stop and return false
return dir.length > 1 && getPackageType(resolvePath(dir, '..'));
}
# main.coffee
import { scream } from './scream.coffee'
console.log scream 'hello, world'
import { version } from 'node:process'
console.log "Brought to you by Node.js version #{version}"
# scream.coffee
export scream = (str) -> str.toUpperCase()
使用前述的加载器,运行
node --experimental-loader ./coffeescript-loader.mjs main.coffee
会在从磁盘加载源代码后但在 Node.js 执行前将 main.coffee 转换为 JavaScript;对于通过任何已加载文件的 import 语句引用的任何 .coffee、.litcoffee 或 .coffee.md 文件也是如此。
【With the preceding loader, running
node --experimental-loader ./coffeescript-loader.mjs main.coffee
causes main.coffee to be turned into JavaScript after its source code is
loaded from disk but before Node.js executes it; and so on for any .coffee,
.litcoffee or .coffee.md files referenced via import statements of any
loaded file.】
解析算法#>
【Resolution algorithm】
特性#>
【Features】
解析器具有以下属性:
【The resolver has the following properties:】
- 使用文件 URL 的解析方式,如同 ES 模块
- 支持内置模块加载
- 相对和绝对 URL 解析
- 无默认扩展名
- 无文件夹主入口
- 通过 node_modules 查找裸模块标识符的包解析
解析器算法#>
【Resolver 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 的模块格式的算法由 ESM_FORMAT 提供,它会返回任何文件的唯一模块格式。对于 ECMAScript 模块,返回的是 "module" 格式,而 "commonjs" 格式则用于表示通过传统 CommonJS 加载器加载。未来更新中还可以扩展其他格式,例如 "addon"。
【The algorithm to determine the module format of a resolved URL is provided by ESM_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 配置无效或包含无效的配置。
- 无效的包目标: 包的 exports 或 imports 定义了一个无效类型或字符串目标的模块。
- 包路径未导出: 包的 exports 未定义或不允许针对给定模块的目标子路径。
- 未定义包导入: 包的 imports 未定义该指定符。
- 模块未找到: 请求的包或模块不存在。
- 不支持的目录导入: 解析的路径对应一个目录,该目录不是模块导入支持的目标。
解析器算法规范#>
【Resolver Algorithm Specification】
ESM_RESOLVE(specifier,parentURL)
- 令 resolved 为 未定义。
- 如果 specifier 是一个有效的 URL,那么
- 将 resolved 设置为解析并重新序列化 specifier 为 URL 的结果。
- 否则,如果 specifier 以 "/"、"./" 或 "../" 开头,那么
- 将 resolved 设置为相对于 parentURL 的 specifier 的 URL 解析结果。
- 否则,如果 specifier 以 "#" 开头,则
- 将 resolved 设置为 PACKAGE_IMPORTS_RESOLVE(specifier,parentURL,defaultConditions) 结果的解构值。
- 否则,
- 注意:specifier 现在是一个裸规范符。
- 将 resolved 设置为 PACKAGE_RESOLVE(specifier, parentURL) 的结果。
- 让 format 为 未定义。
- 如果 resolved 是一个 "file:" URL,那么
- 如果 resolved 包含任何 "/" 或 "" 的百分比编码(分别为 "%2F" 和 "%5C"),那么
- 抛出一个 无效模块说明符 错误。
- 如果 resolved 处的文件是一个目录,则
- 抛出“不支持的目录导入”错误。
- 如果 resolved 处的文件不存在,则
- 抛出 模块未找到 错误。
- 将 resolved 设置为 resolved 的真实路径,同时保持相同的 URL 查询字符串和片段组件。
- 将 format 设置为 ESM_FILE_FORMAT(resolved) 的结果。
- 否则,
- 设置 format 为与 URL resolved 关联的内容类型的模块格式。
- 将 resolved 作为模块格式加载,format。
PACKAGE_RESOLVE(packageSpecifier,parentURL)
- 让 packageName 为 未定义。
- 如果 packageSpecifier 是空字符串,则
- 抛出一个 无效模块说明符 错误。 3.如果 packageSpecifier 是 Node.js 内置模块名称,则
- 返回字符串 "node:" 与 packageSpecifier 连接后的结果。 4.如果 packageSpecifier 不以 "@" 开头,则
- 将 packageName 设置为 packageSpecifier 的子字符串,直到第一个 "/" 分隔符或字符串末尾。
- 否则,
- 如果 packageSpecifier 不包含 "/" 分隔符,则
- 抛出一个 无效模块说明符 错误。
- 将 packageName 设置为 packageSpecifier 的子字符串直到第二个 "/" 分隔符或字符串末尾。
- 如果 packageName 以 "." 开头或包含 "" 或 "%",那么
- 抛出一个 无效模块说明符 错误。
- 设 packageSubpath 为 "." 与 packageSpecifier 从 packageName 长度位置开始的子字符串连接而成。
- 如果 packageSubpath 以 "/" 结尾,那么
- 抛出一个 无效模块说明符 错误。
- 设 selfUrl 为 PACKAGE_SELF_RESOLVE(packageName, packageSubpath, parentURL) 的结果。
- 如果 selfUrl 不是 undefined,则返回 selfUrl。
- 当 parentURL 不是文件系统根目录时,
- 设 packageURL 为以 parentURL 为基准、将 "node_modules/" 与 packageSpecifier 连接后的 URL 解析结果。
- 将 parentURL 设置为 parentURL 的父文件夹 URL。
- 如果 packageURL 处的文件夹不存在,则
- 继续下一次循环迭代。
- 设 pjson 为 READ_PACKAGE_JSON(packageURL) 的结果。
- 如果 pjson 不是 null,并且 pjson.exports 不是 null 或 undefined,那么
- 返回 PACKAGE_EXPORTS_RESOLVE(packageURL, packageSubpath, pjson.exports, defaultConditions) 的结果。
- 否则,如果 packageSubpath 等于 ".",那么
- 如果 pjson.main 是一个字符串,那么
- 返回 packageURL 中 main 的 URL 解析。
- 否则,n> 1. 返回 packageURL 中 packageSubpath 的 URL 解析。
- 抛出 模块未找到 错误。
PACKAGE_SELF_RESOLVE(packageName, packageSubpath, parentURL)
- 设 packageURL 为 LOOKUP_PACKAGE_SCOPE(parentURL) 的结果。
- 如果 packageURL 为 null,则
- 返回 undefined。
- 设 pjson 为 READ_PACKAGE_JSON(packageURL) 的结果。
- 如果 pjson 为 null 或者 pjson.exports 为 null 或 undefined,则
- 返回 undefined。
- 如果 pjson.name 等于 packageName,则
- 返回 PACKAGE_EXPORTS_RESOLVE(packageURL, packageSubpath, pjson.exports, defaultConditions) 的结果的 已解析 解构值。
- 否则,返回 undefined。
PACKAGE_EXPORTS_RESOLVE(packageURL,subpath,exports,conditions)
- 如果 exports 是一个对象,同时包含一个以 "." 开头的键和一个不以 "." 开头的键,则抛出 Invalid Package Configuration 错误。
- 如果 subpath 等于 ".",那么
- 让 mainExport 为 未定义。
- 如果 exports 是字符串或数组,或者是一个不包含任何以 "." 开头的键的对象,那么
- 将 mainExport 设置为 exports。
- 否则,如果 exports 是一个包含 "." 属性的对象,则
- 将 mainExport 设置为 exports["."]。
- 如果 mainExport 不是 undefined,那么
- 令 resolved 为 PACKAGE_TARGET_RESOLVE(packageURL, mainExport, "", false, false, conditions) 的结果。
- 如果 resolved 不是 null 或 undefined,那么
- 返回 resolved。
- 否则,如果 exports 是一个对象,并且 exports 的所有键都以 "." 开头,那么 1.让 matchKey 等于字符串 "./" 与 subpath 的连接。
- 令 resolvedMatch 为 PACKAGE_IMPORTS_EXPORTS_RESOLVE(matchKey, exports, packageURL, false, conditions) 的结果。
- 如果 resolvedMatch.resolve 不是 null 或 undefined,那么
- 返回 resolvedMatch。
- 抛出 Package Path Not Exported 错误。
PACKAGE_IMPORTS_RESOLVE(specifier,parentURL,conditions)
- 断言:specifier 以 "#" 开头。
- 如果 specifier 恰好等于 "#" 或以 "#/" 开头,则
- 抛出 无效模块说明符 错误。
- 令 packageURL 为 LOOKUP_PACKAGE_SCOPE(parentURL) 的结果。
- 如果 packageURL 不为 null,则
- 令 pjson 为 READ_PACKAGE_JSON(packageURL) 的结果。
- 如果 pjson.imports 是非空对象,则
- 令 resolvedMatch 为 PACKAGE_IMPORTS_EXPORTS_RESOLVE(specifier, pjson.imports, packageURL, true, conditions) 的结果。
- 如果 resolvedMatch.resolve 不为 null 或 undefined,则
- 返回 resolvedMatch。
- 抛出 未定义的包导入 错误。
PACKAGE_IMPORTS_EXPORTS_RESOLVE(matchKey, matchObj, packageURL, isImports, conditions)
- If matchKey is a key of matchObj and does not end in "/" or contain "*", then
- Let target be the value of matchObj[matchKey].
- Let resolved be the result of PACKAGE_TARGET_RESOLVE( packageURL, target, "", false, isImports, conditions).
- Return the object { resolved, exact: true }.
- Let expansionKeys be the list of keys of matchObj either ending in "/" or containing only a single "*", sorted by the sorting function PATTERN_KEY_COMPARE which orders in descending order of specificity.
- For each key expansionKey in expansionKeys, do
- Let patternBase be null.
- If expansionKey contains "*", set patternBase to the substring of expansionKey up to but excluding the first "*" character.
- If patternBase is not null and matchKey starts with but is not equal to patternBase, then
- If matchKey ends with "/", throw an Invalid Module Specifier error.
- Let patternTrailer be the substring of expansionKey from the index after the first "*" character.
- 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
- Let target be the value of matchObj[expansionKey].
- Let subpath 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.
- Let resolved be the result of PACKAGE_TARGET_RESOLVE( packageURL, target, subpath, true, isImports, conditions).
- Return the object { resolved, exact: true }.
- Otherwise if patternBase is null and matchKey starts with expansionKey, then
- Let target be the value of matchObj[expansionKey].
- Let subpath be the substring of matchKey starting at the index of the length of expansionKey.
- Let resolved be the result of PACKAGE_TARGET_RESOLVE( packageURL, target, subpath, false, isImports, conditions).
- Return the object { resolved, exact: false }.
- Return the object { resolved: null, exact: true }.
模式_键_比较(keyA, keyB)
- 断言:keyA 以 "/" 结尾或仅包含一个 "*"。
- 断言:keyB 以 "/" 结尾或仅包含一个 "*"。
- 令 baseLengthA 为 keyA 中 "*" 的索引加一(如果 keyA 包含 "*"),否则为 keyA 的长度。
- 令 baseLengthB 为 keyB 中 "*" 的索引加一(如果 keyB 包含 "*"),否则为 keyB 的长度。
- 如果 baseLengthA 大于 baseLengthB,返回 -1。
- 如果 baseLengthB 大于 baseLengthA,返回 1。
- 如果 keyA 不包含 "*",返回 1。
- 如果 keyB 不包含 "*",返回 -1。
- 如果 keyA 的长度大于 keyB,返回 -1。
- 如果 keyB 的长度大于 keyA,返回 1。
- 返回 0。
PACKAGE_TARGET_RESOLVE(packageURL, target, subpath, pattern, internal, conditions)
- If target is a String, then
- If pattern is false, subpath has non-zero length and target does not end with "/", throw an Invalid Module Specifier error.
- If target does not start with "./", then
- If internal is true and target does not start with "../" or "/" and is not a valid URL, then
- If pattern is true, then
- Return PACKAGE_RESOLVE(target with every instance of "*" replaced by subpath, packageURL + "/").
- Return PACKAGE_RESOLVE(target + subpath, packageURL + "/").
- Otherwise, throw an Invalid Package Target error.
- 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.
- Let resolvedTarget be the URL resolution of the concatenation of packageURL and target.
- Assert: resolvedTarget is contained in packageURL.
- If subpath split on "/" or "\" contains any ".", "..", or "node_modules" segments, case insensitive and including percent encoded variants, throw an Invalid Module Specifier error.
- If pattern is true, then
- Return the URL resolution of resolvedTarget with every instance of "*" replaced with subpath.
- Otherwise,
- Return the URL resolution of the concatenation of subpath and resolvedTarget.
- Otherwise, if target is a non-null Object, then
- If exports contains any index property keys, as defined in ECMA-262 6.1.7 Array Index, throw an Invalid Package Configuration error.
- For each property p of target, in object insertion order as,
- If p equals "default" or conditions contains an entry for p, then
- Let targetValue be the value of the p property in target.
- Let resolved be the result of PACKAGE_TARGET_RESOLVE( packageURL, targetValue, subpath, pattern, internal, conditions).
- If resolved is equal to undefined, continue the loop.
- Return resolved.
- Return undefined.
- Otherwise, if target is an Array, then
- If _target.length is zero, return null.
- For each item targetValue in target, do
- Let resolved be the result of PACKAGE_TARGET_RESOLVE( packageURL, targetValue, subpath, pattern, internal, conditions), continuing the loop on any Invalid Package Target error.
- If resolved is undefined, continue the loop.
- Return resolved.
- Return or throw the last fallback resolution null return or error.
- Otherwise, if target is null, return null.
- Otherwise throw an Invalid Package Target error.
ESM_文件_格式(url)
- 断言:url 对应一个存在的文件。
- 如果 url 以 ".mjs" 结尾,
- 返回 "module"。
- 如果 url 以 ".cjs" 结尾,
- 返回 "commonjs"。
- 如果 url 以 ".json" 结尾,
- 返回 "json"。
- 令 packageURL 为 LOOKUP_PACKAGE_SCOPE(url) 的结果。
- 令 pjson 为 READ_PACKAGE_JSON(packageURL) 的结果。
- 如果 pjson?.type 存在并且为 "module",
- 如果 url 以 ".js" 结尾,
- 返回 "module"。
- 抛出 不支持的文件扩展名 错误。
- 否则,
- 抛出 不支持的文件扩展名 错误。
**查找_PACKAGE_SCOPE(url)
- 令 scopeURL 为 url。
- 当 scopeURL 不是文件系统根目录时,
- 将 scopeURL 设置为 scopeURL 的父级 URL。
- 如果 scopeURL 以 "node_modules" 路径段结束,则返回 null。
- 令 pjsonURL 为在 scopeURL 内解析出的 "package.json"。
- 如果 pjsonURL 对应的文件存在,则
- 返回 scopeURL。
- 返回 null。
读取_包_JSON(packageURL)
- 令 pjsonURL 为 packageURL 中 "package.json" 的解析结果。
- 如果 pjsonURL 处的文件不存在,则
- 返回 null。
- 如果 packageURL 处的文件无法解析为有效的 JSON,则
- 抛出 无效的包配置 错误。
- 返回 pjsonURL 处文件的解析后的 JSON 源。
自定义 ESM 说明符解析算法#>
【Customizing ESM specifier resolution algorithm】
不要依赖此标志。一旦 加载器 API 发展到可以通过自定义加载器实现等效功能的阶段,我们计划移除它。
当前的指定符解析不支持 CommonJS 加载器的所有默认行为。其中一个行为差异是文件扩展名的自动解析以及导入具有索引文件的目录的能力。
【The current specifier resolution does not support all default behavior of the CommonJS loader. One of the behavior differences is automatic resolution of file extensions and the ability to import directories that have an index file.】
--experimental-specifier-resolution=[mode] 标志可用于自定义扩展名解析算法。默认模式是 explicit,这要求必须向加载器提供模块的完整路径。要启用自动扩展名解析并从包含索引文件的目录导入,请使用 node 模式。
【The --experimental-specifier-resolution=[mode] flag can be used to customize
the extension resolution algorithm. The default mode is explicit, which
requires the full path to a module be provided to the loader. To enable the
automatic extension resolution and importing from directories that include an
index file use the node mode.】
$ node index.mjs
success!
$ node index # Failure!
Error: Cannot find module
$ node --experimental-specifier-resolution=node index
success!