模块:ECMAScript 模块#

稳定性:2 - 稳定

简介#

ECMAScript 模块是 JavaScript 代码打包重用的官方标准格式。模块使用各种 importexport 语句定义。

以下 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 用于 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: URL 作为一种替代方法支持加载 Node.js 内置模块。此 URL 方案允许通过有效的绝对 URL 字符串引用内置模块。

import fs from 'node:fs/promises'; 

导入属性#

稳定性:1.1 - 积极开发

此功能以前称为“导入断言”,并使用 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#

稳定性:1.2 - 发布候选

警告:仅在 file: 模块上存在。

import.meta.filename#

稳定性:1.2 - 发布候选

警告 仅本地模块支持此属性。不使用 file: 协议的模块将不提供它。

import.meta.url#

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

这使得一些有用的模式成为可能,例如相对文件加载

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

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 模块解析的所有功能。依赖关系解析受包内允许的导出解析的约束。

注意事项:

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

非标准 API:

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

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

与 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 之间的区别#

没有 requireexportsmodule.exports#

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

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

没有 `__filename` 或 `__dirname`#

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

可以通过 import.meta.filenameimport.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 模块#

稳定性:1 - 实验性

JSON 文件可以通过 `import` 引用。

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

with { type: 'json' } 语法是必须的;请参阅 导入属性

导入的 JSON 仅公开一个 `default` 导出。不支持命名导出。在 CommonJS 缓存中创建缓存条目以避免重复。如果 JSON 模块已从相同路径导入,则在 CommonJS 中返回相同对象。

Wasm 模块#

稳定性:1 - 实验性

在 `--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 导入#

稳定性:1 - 实验性

--experimental-network-imports 标志下,支持使用 https:http: 导入基于网络的模块。这允许类似 Web 浏览器的导入在 Node.js 中工作,但由于应用程序稳定性和安全问题,与在浏览器沙箱中运行相比,在特权环境中运行时存在一些差异。

导入仅限于 HTTP/1#

目前尚不支持 HTTP/2 和 HTTP/3 的自动协议协商。

HTTP 仅限于环回地址#

http: 容易受到中间人攻击,不允许用于 IPv4 地址 127.0.0.0/8127.0.0.1127.255.255.255)和 IPv6 地址 ::1 之外的地址。http: 的支持旨在用于本地开发。

身份验证永远不会发送到目标服务器。#

AuthorizationCookieProxy-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)

  1. resolved 为 **undefined**。
  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 为 **undefined**。
  7. 如果 resolved 是 *“file:”* URL,则
    1. 如果 resolved 包含任何 "/""\" 的百分比编码(分别为 "%2F""%5C"),则
      1. 抛出 无效的模块说明符 错误。
    2. 如果 resolved 处的文件是目录,则
      1. 抛出 不支持的目录导入 错误。
    3. 如果 resolved 处的文件不存在,则
      1. 抛出Module Not Found错误。
    4. resolved设置为resolved的真实路径,保持相同的URL查询字符串和片段组件。
    5. format设置为ESM_FILE_FORMAT(resolved)的结果。
  8. 否则,
    1. format设置为与URL resolved关联的Content-Type的模块格式。
  9. formatresolved返回到加载阶段。

PACKAGE_RESOLVE(packageSpecifier, parentURL)

  1. packageNameundefined
  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. 如果packageSubpath"/"结尾,则
    1. 抛出 无效的模块说明符 错误。
  9. selfUrlPACKAGE_SELF_RESOLVE(packageName, packageSubpath, parentURL)的结果。
  10. 如果selfUrl不是undefined,则返回selfUrl
  11. parentURL不是文件系统根目录时,
    1. packageURL"node_modules/"packageSpecifier的串联相对于parentURL的URL解析结果。
    2. parentURL设置为parentURL的父文件夹URL。
    3. 如果packageURL处的文件夹不存在,则
      1. 继续下一个循环迭代。
    4. pjsonREAD_PACKAGE_JSON(packageURL)的结果。
    5. 如果pjson不是nullpjson.exports不是nullundefined,则
      1. 返回PACKAGE_EXPORTS_RESOLVE(packageURL, packageSubpath, pjson.exports, defaultConditions)的结果。
    6. 否则,如果packageSubpath等于".",则
      1. 如果pjson.main是字符串,则
        1. 返回mainpackageURL中的URL解析结果。
    7. 否则,
      1. 返回packageURLpackageSubpath的 URL 解析结果。
  12. 抛出Module Not Found错误。

PACKAGE_SELF_RESOLVE(packageName, packageSubpath, parentURL)

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

PACKAGE_EXPORTS_RESOLVE(packageURL, subpath, exports, conditions)

  1. 如果exports是一个既包含以"."开头的键又包含不以"."开头的键的对象,则抛出Invalid Package Configuration错误。
  2. 如果subpath等于".",则
    1. mainExportundefined
    2. 如果exports是一个字符串或数组,或者是一个不包含以"."开头的键的对象,则
      1. mainExport设置为exports
    3. 否则,如果exports是一个包含"."属性的对象,则
      1. mainExport设置为exports["."]。
    4. 如果mainExport不为undefined,则
      1. resolvedPACKAGE_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(specifier, parentURL, conditions)

  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 Import Not Defined错误。

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

  1. 如果matchKeymatchObj的键,并且不包含"*",则
    1. targetmatchObj[matchKey]的值。
    2. 返回PACKAGE_TARGET_RESOLVE(packageURL, target, null, isImports, conditions)的结果。
  2. expansionKeysmatchObj 的键列表,其中仅包含单个"*",并按排序函数PATTERN_KEY_COMPARE 排序,该函数按特异性降序排序。
  3. 对于expansionKeys 中的每个键expansionKey,执行以下操作
    1. patternBaseexpansionKey 的子字符串,直到但不包括第一个"*" 字符。
    2. 如果matchKeypatternBase 开头但不等于patternBase,则
      1. patternTrailerexpansionKey 从第一个"*" 字符后的索引开始的子字符串。
      2. 如果patternTrailer 长度为零,或者matchKeypatternTrailer 结尾,并且matchKey 的长度大于或等于expansionKey 的长度,则
        1. targetmatchObj[expansionKey] 的值。
        2. patternMatchmatchKeypatternBase 长度索引开始到matchKey 长度减去patternTrailer 长度的子字符串。
        3. 返回PACKAGE_TARGET_RESOLVE(packageURL, target, patternMatch, isImports, conditions) 的结果。
  4. 返回null

PATTERN_KEY_COMPARE(keyA, keyB)

  1. 断言:keyA"/" 结尾或仅包含单个"*"
  2. 断言:keyB"/" 结尾或仅包含单个"*"
  3. baseLengthAkeyA"*" 的索引加一,如果keyA 包含"*",否则为keyA 的长度。
  4. baseLengthBkeyB"*" 的索引加一,如果keyB 包含"*",否则为keyB 的长度。
  5. 如果baseLengthA 大于baseLengthB,则返回 -1。
  6. 如果baseLengthB 大于baseLengthA,则返回 1。
  7. 如果keyA 不包含"*",则返回 1。
  8. 如果keyB 不包含"*",则返回 -1。
  9. 如果keyA 的长度大于keyB 的长度,则返回 -1。
  10. 如果keyB 的长度大于keyA 的长度,则返回 1。
  11. 返回 0。

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

  1. 如果target 是一个字符串,则
    1. 如果target 不以"./" 开头,则
      1. 如果isImportsfalse,或者target"../""/" 开头,或者target 是一个有效的 URL,则
        1. 抛出Invalid Package Target 错误。
      2. 如果 patternMatch 是一个字符串,则
        1. 返回 PACKAGE_RESOLVE(将 target 中所有 "*" 实例替换为 patternMatchpackageURL + "/")。
      3. 返回 PACKAGE_RESOLVE(target, packageURL + "/")。
    2. 如果 target"/""\" 分割后,在第一个 "." 段之后包含任何 """.""..""node_modules" 段,不区分大小写,包括百分比编码变体,则抛出 Invalid Package Target 错误。
    3. resolvedTargetpackageURLtarget 连接后的 URL 解析结果。
    4. 断言:packageURL 包含在 resolvedTarget 中。
    5. 如果 patternMatchnull,则
      1. 返回 resolvedTarget
    6. 如果 patternMatch"/""\" 分割后,包含任何 """.""..""node_modules" 段,不区分大小写,包括百分比编码变体,则抛出 Invalid Module Specifier 错误。
    7. 返回 resolvedTarget 中所有 "*" 实例替换为 patternMatch 后的 URL 解析结果。
  2. 否则,如果 target 是一个非空对象,则
    1. 如果 target 包含任何索引属性键,如 ECMA-262 6.1.7 数组索引 中所定义,则抛出 Invalid Package Configuration 错误。
    2. 对于 target 的每个属性 p,按对象插入顺序,
      1. 如果 p 等于 "default"conditions 包含 p 的条目,则
        1. targetValuetargetp 属性的值。
        2. resolvedPACKAGE_TARGET_RESOLVE( packageURL, targetValue, patternMatch, isImports, conditions) 的结果。
        3. 如果 resolved 等于 undefined,则继续循环。
        4. 返回 resolved
    3. 返回undefined
  3. 否则,如果 target 是一个数组,则
    1. 如果 target.length 为零,则返回 null
    2. 对于 target 中的每个项目 targetValue,执行以下操作
      1. resolvedPACKAGE_TARGET_RESOLVE( packageURL, targetValue, patternMatch, isImports, conditions) 的结果,在任何 Invalid Package Target 错误上继续循环。
      2. 如果 resolvedundefined,则继续循环。
      3. 返回 resolved
    3. 返回或抛出最后一个回退解析结果 null 返回或错误。
  4. 否则,如果targetnull,则返回null
  5. 否则抛出无效的包目标错误。

ESM_FILE_FORMAT(url)

  1. 断言:url对应于一个现有文件。
  2. 如果url".mjs"结尾,则
    1. 返回"module"
  3. 如果url".cjs"结尾,则
    1. 返回"commonjs"
  4. 如果url".json"结尾,则
    1. 返回"json"
  5. 如果启用了--experimental-wasm-modules并且url".wasm"结尾,则
    1. 返回"wasm"
  6. packageURLLOOKUP_PACKAGE_SCOPE(url)的结果。
  7. pjsonREAD_PACKAGE_JSON(packageURL)的结果。
  8. packageTypenull
  9. 如果pjson?.type"module""commonjs",则
    1. packageType设置为pjson.type
  10. 如果url".js"结尾,则
    1. 如果packageType不为null,则
      1. 返回packageType
    2. 如果启用了--experimental-detect-module并且模块的源代码包含静态导入或导出语法,则
      1. 返回"module"
    3. 返回"commonjs"
  11. 如果url没有任何扩展名,则
    1. 如果packageType"module"并且启用了--experimental-wasm-modules并且url处的文件包含 WebAssembly 模块的标头,则
      1. 返回"wasm"
    2. 如果packageType不为null,则
      1. 返回packageType
    3. 如果启用了--experimental-detect-module并且模块的源代码包含静态导入或导出语法,则
      1. 返回"module"
    4. 返回"commonjs"
  12. 返回undefined(将在加载阶段抛出错误)。

LOOKUP_PACKAGE_SCOPE(url)

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

READ_PACKAGE_JSON(packageURL)

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

自定义 ESM 指定符解析算法#

模块自定义钩子提供了一种自定义 ESM 指定符解析算法的机制。一个为 ESM 指定符提供 CommonJS 风格解析的示例是 commonjs-extension-resolution-loader