Node.js v21.7.2 文档
- Node.js v21.7.2
- ► 目录
-
► 索引
- 断言测试
- 异步上下文跟踪
- 异步钩子
- 缓冲区
- C++ 附加模块
- 使用 Node-API 的 C/C++ 附加模块
- C++ 嵌入器 API
- 子进程
- 集群
- 命令行选项
- 控制台
- Corepack
- 加密
- 调试器
- 已弃用的 API
- 诊断通道
- DNS
- 域
- 错误
- 事件
- 文件系统
- 全局对象
- HTTP
- HTTP/2
- HTTPS
- 检查器
- 国际化
- 模块:CommonJS 模块
- 模块:ECMAScript 模块
- 模块:
node:module
API - 模块:包
- 网络
- 操作系统
- 路径
- 性能钩子
- 权限
- 进程
- Punycode
- 查询字符串
- 读取行
- REPL
- 报告
- 单一可执行应用程序
- 流
- 字符串解码器
- 测试运行器
- 计时器
- TLS/SSL
- 跟踪事件
- TTY
- UDP/数据报
- URL
- 实用程序
- V8
- VM
- WASI
- Web Crypto API
- Web Streams API
- 工作线程
- Zlib
- ► 其他版本
- ► 选项
模块:ECMAScript 模块#
简介#
ECMAScript 模块是 JavaScript 代码打包重用的官方标准格式。模块使用各种 import
和 export
语句定义。
以下 ES 模块示例导出一个函数
// addTwo.mjs
function addTwo(num) {
return num + 2;
}
export { addTwo };
以下 ES 模块示例从 addTwo.mjs
导入函数
// app.mjs
import { addTwo } from './addTwo.mjs';
// Prints: 6
console.log(addTwo(4));
Node.js 完全支持当前指定的 ECMAScript 模块,并提供它们与其原始模块格式 CommonJS 之间的互操作性。
启用#
Node.js 具有两个模块系统:CommonJS 模块和 ECMAScript 模块。
作者可以通过 .mjs
文件扩展名、package.json
"type"
字段(值为 "module"
)、--input-type
标志(值为 "module"
)或 --experimental-default-type
标志(值为 "module"
)来告诉 Node.js 将 JavaScript 解释为 ES 模块。这些是代码被明确标记为要作为 ES 模块运行。
反之,作者可以通过 .cjs
文件扩展名、package.json
"type"
字段(值为 "commonjs"
)、--input-type
标志(值为 "commonjs"
)或 --experimental-default-type
标志(值为 "commonjs"
)来告诉 Node.js 将 JavaScript 解释为 CommonJS。
当代码缺乏对任一模块系统的明确标记时,Node.js 将检查模块的源代码以查找 ES 模块语法。如果找到此类语法,Node.js 将以 ES 模块的形式运行代码;否则,它将以 CommonJS 的形式运行模块。有关更多详细信息,请参见 确定模块系统。
包#
本节已移至 模块:包。
import
说明符#
术语#
import
语句的说明符是 from
关键字后的字符串,例如 'node:path'
在 import { sep } from 'node:path'
中。说明符也用于 export from
语句,以及作为 import()
表达式的参数。
说明符有三种类型
-
相对说明符,例如
'./startup.js'
或'../config.mjs'
。它们指的是相对于导入文件位置的路径。这些说明符始终需要文件扩展名。 -
裸说明符,例如
'some-package'
或'some-package/shuffle'
。它们可以指代包的名称,或指代包中特定功能模块(以包名称为前缀,如示例所示)。只有当包没有"exports"
字段时,才需要包含文件扩展名。 -
绝对说明符,例如
'file:///opt/nodejs/config.js'
。它们直接且明确地指向完整路径。
裸说明符的解析由 Node.js 模块解析和加载算法 处理。所有其他说明符的解析始终仅使用标准的相对 URL 解析语义。
与 CommonJS 一样,包中的模块文件可以通过在包名称后附加路径来访问,除非包的 package.json
包含 "exports"
字段,在这种情况下,包中的文件只能通过 "exports"
中定义的路径访问。
有关适用于 Node.js 模块解析中裸说明符的这些包解析规则的详细信息,请参阅 包文档。
强制文件扩展名#
使用 import
关键字解析相对或绝对说明符时,必须提供文件扩展名。目录索引(例如 './startup/index.js'
)也必须完全指定。
此行为与浏览器环境中 import
的行为一致,假设服务器配置典型。
URL#
ES 模块以 URL 形式解析和缓存。这意味着特殊字符必须进行百分比编码,例如 #
编码为 %23
,?
编码为 %3F
。
支持 file:
、node:
和 data:
URL 方案。除非使用自定义 HTTPS 加载器,否则 Node.js 本身不支持像 'https://example.com/app.js'
这样的规范。
file:
URL#
如果用于解析模块的 import
规范具有不同的查询或片段,则模块将被加载多次。
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 和路径解析(如百分比编码细节)之间的差异,建议在导入路径时使用url.pathToFileURL。
data:
导入#
data:
URL 支持以下 MIME 类型进行导入
text/javascript
用于 ES 模块application/json
用于 JSONapplication/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:
URL 作为一种替代方法支持加载 Node.js 内置模块。此 URL 方案允许通过有效的绝对 URL 字符串引用内置模块。
import fs from 'node:fs/promises';
导入属性#
此功能以前称为“导入断言”,并使用
assert
关键字而不是with
。代码中对先前assert
关键字的任何使用都应更新为使用with
。
导入属性提案 为模块导入语句添加了内联语法,以便在模块规范旁边传递更多信息。
import fooData from './foo.json' with { type: 'json' };
const { default: barData } =
await import('./bar.json', { with: { type: 'json' } });
Node.js 支持以下 type
值,该属性是必需的
属性 type | 需要用于 |
---|---|
'json' | JSON 模块 |
内置模块#
核心模块 提供其公共 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()
在 CommonJS 和 ES 模块中都受支持。在 CommonJS 模块中,它可用于加载 ES 模块。
import.meta
#
import.meta
元属性是一个 Object
,它包含以下属性。
import.meta.dirname
#
- <string> 当前模块的目录名称。这与
import.meta.filename
的path.dirname()
相同。
警告:仅在
file:
模块上存在。
import.meta.filename
#
- <string> 当前模块的完整绝对路径和文件名,其中
- 符号链接已解析。
- 这与
url.fileURLToPath()
相同 import.meta.url
.
警告 仅本地模块支持此属性。不使用
file:
协议的模块将不提供它。
import.meta.url
#
- <string> 模块的绝对
file:
URL。
这与浏览器中定义的完全相同,提供当前模块文件的 URL。
这使得一些有用的模式成为可能,例如相对文件加载
import { readFileSync } from 'node:fs';
const buffer = readFileSync(new URL('./data.proto', import.meta.url));
import.meta.resolve(specifier)
#
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 模块解析的所有功能。依赖关系解析受包内允许的导出解析的约束。
注意事项:
- 这会导致同步文件系统操作,这会影响性能,类似于
require.resolve
。 - 此功能在自定义加载器中不可用(会导致死锁)。
非标准 API:
使用 --experimental-import-meta-resolve
标志时,该函数接受第二个参数
与 CommonJS 的互操作性#
import
语句#
import
语句可以引用 ES 模块或 CommonJS 模块。import
语句仅在 ES 模块中允许,但动态 import()
表达式在 CommonJS 中支持加载 ES 模块。
当导入 CommonJS 模块 时,module.exports
对象将作为默认导出提供。命名导出可能可用,由静态分析提供,以方便更好地与生态系统兼容。
require
#
CommonJS 模块 require
始终将它引用的文件视为 CommonJS。
使用 require
加载 ES 模块不受支持,因为 ES 模块具有异步执行。相反,请使用 import()
从 CommonJS 模块加载 ES 模块。
CommonJS 命名空间#
CommonJS 模块包含一个 module.exports
对象,它可以是任何类型。
当导入 CommonJS 模块时,可以使用 ES 模块默认导入或其相应的语法糖可靠地导入它
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
值。
此模块命名空间奇异对象可以直接观察,无论是在使用 import * as m from 'cjs'
还是动态导入时。
import * as m from 'cjs';
console.log(m);
console.log(m === await import('cjs'));
// Prints:
// [Module] { default: <module.exports> }
// true
为了更好地与 JS 生态系统中现有的用法兼容,Node.js 还会尝试确定每个导入的 CommonJS 模块的 CommonJS 命名导出,以便使用静态分析过程将它们作为单独的 ES 模块导出提供。
例如,考虑一个用以下方式编写的 CommonJS 模块
// cjs.cjs
exports.name = 'exported';
前面的模块支持 ES 模块中的命名导入
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 模块命名空间上。
这些命名导出不会检测到实时绑定更新或添加到 module.exports
的新导出。
命名导出的检测基于常见的语法模式,但并不总是能正确检测到命名导出。在这些情况下,使用上面描述的默认导入形式可能是一个更好的选择。
命名导出检测涵盖了许多常见的导出模式、重新导出模式以及构建工具和转译器输出。有关实现的精确语义,请参阅 cjs-module-lexer。
ES 模块和 CommonJS 之间的区别#
没有 require
、exports
或 module.exports
#
在大多数情况下,ES 模块的 `import` 可以用来加载 CommonJS 模块。
如果需要,可以使用 module.createRequire()
在 ES 模块中构建 `require` 函数。
没有 `__filename` 或 `__dirname`#
这些 CommonJS 变量在 ES 模块中不可用。
可以通过 import.meta.filename
和 import.meta.dirname
来复制 `__filename` 和 `__dirname` 的用例。
没有 Addon 加载#
目前不支持使用 ES 模块导入 Addon。
它们可以使用 module.createRequire()
或 process.dlopen
加载。
没有 `require.resolve`#
可以使用 `new URL('./local', import.meta.url)` 处理相对解析。
对于完整的 `require.resolve` 替换,可以使用 import.meta.resolve API。
或者可以使用 `module.createRequire()`。
没有 `NODE_PATH`#
NODE_PATH
不是解析 `import` 说明符的一部分。如果需要此行为,请使用符号链接。
没有 `require.extensions`#
import
不使用 `require.extensions`。模块自定义钩子可以提供替代方案。
没有 `require.cache`#
import
不使用 `require.cache`,因为 ES 模块加载器有自己的独立缓存。
JSON 模块#
JSON 文件可以通过 `import` 引用。
import packageConfig from './package.json' with { type: 'json' };
with { type: 'json' }
语法是必须的;请参阅 导入属性。
导入的 JSON 仅公开一个 `default` 导出。不支持命名导出。在 CommonJS 缓存中创建缓存条目以避免重复。如果 JSON 模块已从相同路径导入,则在 CommonJS 中返回相同对象。
Wasm 模块#
在 `--experimental-wasm-modules` 标志下支持导入 WebAssembly 模块,允许将任何 `.wasm` 文件作为普通模块导入,同时还支持它们的模块导入。
此集成符合 WebAssembly 的 ES 模块集成提案。
例如,一个包含以下内容的 index.mjs
文件:
import * as M from './module.wasm';
console.log(M);
在以下命令下执行:
node --experimental-wasm-modules index.mjs
将为 module.wasm
的实例化提供导出接口。
顶层 await
#
await
关键字可以在 ECMAScript 模块的顶层主体中使用。
假设一个包含以下内容的 a.mjs
文件:
export const five = await Promise.resolve(5);
以及一个包含以下内容的 b.mjs
文件:
import { five } from './a.mjs';
console.log(five); // Logs `5`
node b.mjs # works
如果顶层 await
表达式永远无法解析,node
进程将以 13
的 状态码 退出。
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 导入#
在 --experimental-network-imports
标志下,支持使用 https:
和 http:
导入基于网络的模块。这允许类似 Web 浏览器的导入在 Node.js 中工作,但由于应用程序稳定性和安全问题,与在浏览器沙箱中运行相比,在特权环境中运行时存在一些差异。
导入仅限于 HTTP/1#
目前尚不支持 HTTP/2 和 HTTP/3 的自动协议协商。
HTTP 仅限于环回地址#
http:
容易受到中间人攻击,不允许用于 IPv4 地址 127.0.0.0/8
(127.0.0.1
到 127.255.255.255
)和 IPv6 地址 ::1
之外的地址。http:
的支持旨在用于本地开发。
身份验证永远不会发送到目标服务器。#
Authorization
、Cookie
和 Proxy-Authorization
标头不会发送到服务器。避免在导入 URL 的部分中包含用户信息。正在开发一种安全模型,用于在服务器上安全地使用这些信息。
CORS 永远不会在目标服务器上检查#
CORS 旨在允许服务器将 API 的使用者限制为一组特定的主机。这不受支持,因为它对基于服务器的实现没有意义。
无法加载非网络依赖项#
这些模块无法访问不在 http:
或 https:
上的其他模块。为了在避免安全问题的同时仍然访问本地模块,请传入对本地依赖项的引用。
// 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
}
基于网络的加载默认情况下未启用#
目前,需要 --experimental-network-imports
标志才能启用通过 http:
或 https:
加载资源。将来,将使用不同的机制来强制执行此操作。需要选择加入以防止传递依赖项无意中使用可能的可变状态,这可能会影响 Node.js 应用程序的可靠性。
加载器#
以前的加载器文档现在位于 模块:自定义钩子。
解析和加载算法#
功能#
默认解析器具有以下属性
- 基于 FileURL 的解析,如 ES 模块所用
- 相对和绝对 URL 解析
- 没有默认扩展名
- 没有文件夹主文件
- 通过 node_modules 进行裸规范包解析查找
- 不会因未知扩展名或协议而失败
- 可以选择向加载阶段提供格式提示
默认加载器具有以下属性
- 支持通过
node:
URL 加载内置模块 - 支持通过
data:
URL 加载“内联”模块 - 支持
file:
模块加载 - 对任何其他 URL 协议失败
- 对
file:
加载的未知扩展名失败(仅支持.cjs
、.js
和.mjs
)
解析算法#
加载 ES 模块规范的算法通过下面的 ESM_RESOLVE 方法给出。它返回相对于 parentURL 的模块规范的已解析 URL。
解析算法确定模块加载的完整已解析 URL 及其建议的模块格式。解析算法不确定已解析 URL 协议是否可以加载,也不确定文件扩展名是否允许,而是这些验证在加载阶段由 Node.js 应用(例如,如果它被要求加载具有非 file:
、data:
、node:
的协议的 URL,或者如果 --experimental-network-imports
已启用,则为 https:
)。
该算法还尝试根据扩展名确定文件的格式(参见下面的 ESM_FILE_FORMAT
算法)。如果它无法识别文件扩展名(例如,如果它不是 .mjs
、.cjs
或 .json
),则返回 undefined
格式,这将在加载阶段抛出错误。
确定已解析 URL 的模块格式的算法由 **ESM_FILE_FORMAT** 提供,该算法返回任何文件的唯一模块格式。对于 ECMAScript 模块,返回 *“module”* 格式,而 *“commonjs”* 格式用于指示通过传统 CommonJS 加载器加载。在将来的更新中,可以扩展其他格式,例如 *“addon”*。
在以下算法中,除非另有说明,否则所有子程序错误都将作为这些顶级例程的错误传播。
defaultConditions 是条件环境名称数组,["node", "import"]
。
解析器可能会抛出以下错误
- 无效的模块说明符:模块说明符是无效的 URL、包名称或包子路径说明符。
- 无效的包配置:package.json 配置无效或包含无效配置。
- 无效的包目标:包导出或导入定义了包的目标模块,该模块是无效类型或字符串目标。
- 未导出包路径:包导出未定义或不允许包中给定模块的目标子路径。
- 未定义包导入:包导入未定义说明符。
- 模块未找到:请求的包或模块不存在。
- 不支持的目录导入:解析的路径对应于目录,该目录不是模块导入支持的目标。
解析算法规范#
ESM_RESOLVE(specifier, parentURL)
- 令 resolved 为 **undefined**。
- 如果 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 为 **undefined**。
- 如果 resolved 是 *“file:”* URL,则
- 如果 resolved 包含任何 "/" 或 "\" 的百分比编码(分别为 "%2F" 和 "%5C"),则
- 抛出 无效的模块说明符 错误。
- 如果 resolved 处的文件是目录,则
- 抛出 不支持的目录导入 错误。
- 如果 resolved 处的文件不存在,则
- 抛出Module Not Found错误。
- 将resolved设置为resolved的真实路径,保持相同的URL查询字符串和片段组件。
- 将format设置为ESM_FILE_FORMAT(resolved)的结果。
- 否则,
- 将format设置为与URL resolved关联的Content-Type的模块格式。
- 将format和resolved返回到加载阶段。
PACKAGE_RESOLVE(packageSpecifier, parentURL)
- 令packageName为undefined。
- 如果packageSpecifier为空字符串,则
- 抛出 无效的模块说明符 错误。
- 如果packageSpecifier是Node.js内置模块名称,则
- 返回字符串"node:"与packageSpecifier的串联。
- 如果packageSpecifier不以"@"开头,则
- 将packageName设置为packageSpecifier的子字符串,直到第一个"/"分隔符或字符串的结尾。
- 否则,
- 如果packageSpecifier不包含"/"分隔符,则
- 抛出 无效的模块说明符 错误。
- 将packageName设置为packageSpecifier的子字符串,直到第二个"/"分隔符或字符串的结尾。
- 如果packageName以"."开头或包含"\"或"%",则
- 抛出 无效的模块说明符 错误。
- 令packageSubpath为"."与packageSpecifier从packageName长度位置开始的子字符串的串联。
- 如果packageSubpath以"/"结尾,则
- 抛出 无效的模块说明符 错误。
- 令selfUrl为PACKAGE_SELF_RESOLVE(packageName, packageSubpath, parentURL)的结果。
- 如果selfUrl不是undefined,则返回selfUrl。
- 当parentURL不是文件系统根目录时,
- 令packageURL为"node_modules/"与packageSpecifier的串联相对于parentURL的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是字符串,则
- 返回main在packageURL中的URL解析结果。
- 否则,
- 返回packageURL中packageSubpath的 URL 解析结果。
- 抛出Module Not Found错误。
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为undefined。
- 如果exports是一个字符串或数组,或者是一个不包含以"."开头的键的对象,则
- 将mainExport设置为exports。
- 否则,如果exports是一个包含"."属性的对象,则
- 将mainExport设置为exports["."]。
- 如果mainExport不为undefined,则
- 令resolved为PACKAGE_TARGET_RESOLVE( packageURL, mainExport, null, false, conditions)的结果。
- 如果resolved不为null或undefined,则返回resolved。
- 否则,如果exports是一个对象,并且exports的所有键都以"."开头,则
- 断言:subpath以"./"开头。
- 令resolved为PACKAGE_IMPORTS_EXPORTS_RESOLVE( subpath, exports, packageURL, false, conditions)的结果。
- 如果resolved不为null或undefined,则返回resolved。
- 抛出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是一个非空对象,则
- 令resolved为PACKAGE_IMPORTS_EXPORTS_RESOLVE( specifier, pjson.imports, packageURL, true, conditions)的结果。
- 如果resolved不为null或undefined,则返回resolved。
- 抛出Package Import Not Defined错误。
PACKAGE_IMPORTS_EXPORTS_RESOLVE(matchKey, matchObj, packageURL, isImports, conditions)
- 如果matchKey是matchObj的键,并且不包含"*",则
- 令target为matchObj[matchKey]的值。
- 返回PACKAGE_TARGET_RESOLVE(packageURL, target, null, isImports, conditions)的结果。
- 令expansionKeys 为matchObj 的键列表,其中仅包含单个"*",并按排序函数PATTERN_KEY_COMPARE 排序,该函数按特异性降序排序。
- 对于expansionKeys 中的每个键expansionKey,执行以下操作
- 令patternBase 为expansionKey 的子字符串,直到但不包括第一个"*" 字符。
- 如果matchKey 以patternBase 开头但不等于patternBase,则
- 令patternTrailer 为expansionKey 从第一个"*" 字符后的索引开始的子字符串。
- 如果patternTrailer 长度为零,或者matchKey 以patternTrailer 结尾,并且matchKey 的长度大于或等于expansionKey 的长度,则
- 令target 为matchObj[expansionKey] 的值。
- 令patternMatch 为matchKey 从patternBase 长度索引开始到matchKey 长度减去patternTrailer 长度的子字符串。
- 返回PACKAGE_TARGET_RESOLVE(packageURL, target, patternMatch, isImports, conditions) 的结果。
- 返回null。
PATTERN_KEY_COMPARE(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, patternMatch, isImports, conditions)
- 如果target 是一个字符串,则
- 如果target 不以"./" 开头,则
- 如果isImports 为false,或者target 以"../" 或"/" 开头,或者target 是一个有效的 URL,则
- 抛出Invalid Package Target 错误。
- 如果 patternMatch 是一个字符串,则
- 返回 PACKAGE_RESOLVE(将 target 中所有 "*" 实例替换为 patternMatch,packageURL + "/")。
- 返回 PACKAGE_RESOLVE(target, packageURL + "/")。
- 如果 target 按 "/" 或 "\" 分割后,在第一个 "." 段之后包含任何 ""、"."、".." 或 "node_modules" 段,不区分大小写,包括百分比编码变体,则抛出 Invalid Package Target 错误。
- 令 resolvedTarget 为 packageURL 和 target 连接后的 URL 解析结果。
- 断言:packageURL 包含在 resolvedTarget 中。
- 如果 patternMatch 为 null,则
- 返回 resolvedTarget。
- 如果 patternMatch 按 "/" 或 "\" 分割后,包含任何 ""、"."、".." 或 "node_modules" 段,不区分大小写,包括百分比编码变体,则抛出 Invalid Module Specifier 错误。
- 返回 resolvedTarget 中所有 "*" 实例替换为 patternMatch 后的 URL 解析结果。
- 否则,如果 target 是一个非空对象,则
- 如果 target 包含任何索引属性键,如 ECMA-262 6.1.7 数组索引 中所定义,则抛出 Invalid Package Configuration 错误。
- 对于 target 的每个属性 p,按对象插入顺序,
- 如果 p 等于 "default" 或 conditions 包含 p 的条目,则
- 令 targetValue 为 target 中 p 属性的值。
- 令 resolved 为 PACKAGE_TARGET_RESOLVE( packageURL, targetValue, patternMatch, isImports, conditions) 的结果。
- 如果 resolved 等于 undefined,则继续循环。
- 返回 resolved。
- 返回undefined。
- 否则,如果 target 是一个数组,则
- 如果 target.length 为零,则返回 null。
- 对于 target 中的每个项目 targetValue,执行以下操作
- 令 resolved 为 PACKAGE_TARGET_RESOLVE( packageURL, targetValue, patternMatch, isImports, conditions) 的结果,在任何 Invalid Package Target 错误上继续循环。
- 如果 resolved 为 undefined,则继续循环。
- 返回 resolved。
- 返回或抛出最后一个回退解析结果 null 返回或错误。
- 否则,如果target为null,则返回null。
- 否则抛出无效的包目标错误。
ESM_FILE_FORMAT(url)
- 断言:url对应于一个现有文件。
- 如果url以".mjs"结尾,则
- 返回"module"。
- 如果url以".cjs"结尾,则
- 返回"commonjs"。
- 如果url以".json"结尾,则
- 返回"json"。
- 如果启用了
--experimental-wasm-modules
并且url以".wasm"结尾,则
- 返回"wasm"。
- 令packageURL为LOOKUP_PACKAGE_SCOPE(url)的结果。
- 令pjson为READ_PACKAGE_JSON(packageURL)的结果。
- 令packageType为null。
- 如果pjson?.type为"module"或"commonjs",则
- 将packageType设置为pjson.type。
- 如果url以".js"结尾,则
- 如果packageType不为null,则
- 返回packageType。
- 如果启用了
--experimental-detect-module
并且模块的源代码包含静态导入或导出语法,则
- 返回"module"。
- 返回"commonjs"。
- 如果url没有任何扩展名,则
- 如果packageType为"module"并且启用了
--experimental-wasm-modules
并且url处的文件包含 WebAssembly 模块的标头,则
- 返回"wasm"。
- 如果packageType不为null,则
- 返回packageType。
- 如果启用了
--experimental-detect-module
并且模块的源代码包含静态导入或导出语法,则
- 返回"module"。
- 返回"commonjs"。
- 返回undefined(将在加载阶段抛出错误)。
LOOKUP_PACKAGE_SCOPE(url)
- 令scopeURL为url。
- 当scopeURL不是文件系统根目录时,
- 将scopeURL设置为scopeURL的父 URL。
- 如果scopeURL以"node_modules"路径段结尾,则返回null。
- 令pjsonURL为scopeURL中"package.json"的解析结果。
- 如果pjsonURL处的文件存在,则
- 返回scopeURL。
- 返回null。
READ_PACKAGE_JSON(packageURL)
- 令pjsonURL为packageURL中"package.json"的解析结果。
- 如果pjsonURL处的文件不存在,则
- 返回null。
- 如果packageURL处的文件无法解析为有效的 JSON,则
- 抛出无效的包配置错误。
- 返回pjsonURL处的文件解析后的 JSON 源代码。
自定义 ESM 指定符解析算法#
模块自定义钩子提供了一种自定义 ESM 指定符解析算法的机制。一个为 ESM 指定符提供 CommonJS 风格解析的示例是 commonjs-extension-resolution-loader。