本页面解释了 Node.js 如何实现其两种模块系统:CommonJS (CJS) 和 ECMAScript 模块 (ESM) 之间的互操作性。有关每个模块系统独立工作方式的详细信息,请参阅 CommonJS 模块 和 ECMAScript 模块。
Node.js 提供了机制,允许用一种格式编写的模块导入并与用另一种格式编写的模块进行交互。这种互操作性对于从旧版 CommonJS 系统向标准化 ESM 格式的过渡至关重要,它允许渐进式采用,同时保持与现有生态系统的兼容性。
来源:doc/api/esm.md459-468 doc/api/esm.md487-521 lib/internal/modules/esm/loader.js361-391
ECMAScript 模块可以使用标准的 import 语法导入 CommonJS 模块。CommonJS 模块的 module.exports 作为默认导出提供,并可方便地进行命名导出。
当从 ECMAScript 模块导入 CommonJS 模块时,Node.js 会构建一个包装 CommonJS module.exports 的命名空间对象。此命名空间
module.exports 值的 default 导出'module.exports' 命名导出,以明确指示 CommonJS 导出值来源:doc/api/esm.md479-521 doc/api/esm.md522-575
Node.js 会自动检测 CommonJS 模块中的命名导出,以便在 ESM 导入中使用。此检测涵盖了常见的导出模式
但是,此检测存在限制
如有疑问,请使用默认导入,它始终可用且可靠。
来源:doc/api/esm.md576-585 lib/internal/modules/esm/translators.js80-82
CommonJS 模块无法直接同步 require() ESM 模块。这是因为 ESM 模块可能包含异步操作(例如顶层 await),这与 require() 的同步特性不兼容。
从 CommonJS 使用 ESM 的推荐方法是通过动态 import() 函数,该函数在两种模块系统中都有效
来源:doc/api/esm.md334-339 doc/api/modules.md176-205
Node.js 已添加对直接 require 符合特定条件的 ESM 模块的实验性支持
await).mjs 文件扩展名.js 扩展名,并在最近的 package.json 中设置 type: "module"此功能在 Node.js 22 和 20.19.0 中已稳定。
来源:doc/api/modules.md171-209 lib/internal/modules/esm/loader.js361-391
为了同时支持 ESM 和 CommonJS 消费者,包可以使用几种方法
可以在 package.json 中使用 exports 字段为不同的模块系统定义不同的入口点
这使得包可以根据是使用 ESM 的 import 还是 CommonJS 的 require() 来提供不同的文件。
可以定义更复杂的导出映射来处理不同的条件
来源:doc/api/packages.md (引用但未在提供的文件中)
某些特性在一个系统中有,而在另一个系统中没有
| CommonJS | ECMAScript 模块 |
|---|---|
require() | 不可用(使用 import 或 createRequire) |
module.exports | 不可用(使用 export 语句) |
__filename, __dirname | 不可用(使用 import.meta.filename, import.meta.dirname) |
| 同步加载 | 顶层代码是同步的,但可以包含顶层 await |
| specifier 没有 URL 解析 | specifier 的基于 URL 的解析 |
require.cache | 独立的 ESM 模块缓存 |
误用模块系统时,可能会遇到以下错误
ERR_REQUIRE_ESM:尝试 require() ESM 模块ERR_REQUIRE_ASYNC_MODULE:尝试 require() 带有顶层 await 的 ESM 模块ERR_UNKNOWN_MODULE_FORMAT:无法确定模块的格式ERR_NETWORK_IMPORT_DISALLOWED:尝试从不支持的 URL 方案导入来源:lib/internal/modules/esm/loader.js30-29 lib/internal/errors.js183-190
createRequire在 ES 模块中,module.createRequire() 函数允许您构造一个 CommonJS 风格的 require 函数
来源:doc/api/module.md50-67 doc/api/esm.md594-595
ESM 模块系统使用 URL 而不是文件路径。为了保持路径的一致性
或者,较新的 Node.js 版本提供了快捷方式
Node.js 通过创建包装器对象和执行两种系统之间的翻译来实现模块互操作性。
importSyncForRequireESM 加载器中的此内部方法用于 CommonJS 模块 require ES 模块时
来源:lib/internal/modules/esm/loader.js361-391 lib/internal/modules/esm/translators.js120-161
当 ESM 模块导入 CommonJS 模块时,转换层会创建一个“模块命名空间奇异对象”
来源:lib/internal/modules/esm/translators.js72-82 doc/api/esm.md487-521
尽可能在单个文件或包中使用单一模块格式。
从 ESM 导入 CommonJS 时:
module.exports 对象从 CommonJS 导入 ESM 时:
import() 函数异步加载 ESM 模块import() 返回的 Promise对于包作者:
对于应用程序开发者:
package.json 中的 "type" 字段设置createRequire