Node.js中.mjs和.cjs混合使用需遵循扩展名优先、单向兼容原则:.mjs恒为ESM,.cjs恒为CJS,.js依package.json的"type"字段;ESM可导入CJS但仅支持默认导出,CJS不可同步require ESM,须用动态import;混用易致循环依赖与执行序紊乱,建议以ESM为入口统一类型。

Node.js 对 .mjs(ES 模块)和 .cjs(CommonJS 模块)混合使用有明确但需注意的规则:文件扩展名优先决定模块类型,而非内容或 package.json 的 "type" 字段;跨类型导入必须遵循单向兼容约束。
扩展名决定模块类型,且不可绕过
Node.js 严格按文件后缀判断模块系统:
-
.mjs文件总是被当作 ES 模块(无论package.json中"type"是什么) -
.cjs文件总是被当作 CommonJS 模块(同样无视"type") -
.js文件的行为由最近的package.json中"type": "module"或"type": "commonjs"决定;未声明时默认为 CommonJS
ES 模块可导入 CommonJS,但有局限
.mjs 可以用 import 加载 .cjs 文件,但仅能获取其 module.exports 对象(即默认导出),不能按名解构导入(除非该 CJS 模块显式设置 exports.xxx 并在 package.json 中配置 "exports" 映射):
// utils.cjs
module.exports = { foo: () => 'bar' };
exports.baz = 'qux';
// main.mjs
import utils from './utils.cjs'; // ✅ 可行:得到 { foo: ..., baz: ... }
import { foo } from './utils.cjs'; // ❌ 报错:Named export 'foo' not found
CommonJS 不能直接 import ES 模块
.cjs 文件不支持 import 语法,也不能用 require('./mod.mjs') 同步加载 ES 模块(会抛错 ERR_REQUIRE_ESM)。如需在 CJS 中使用 ESM 导出,必须:
立即学习“Java免费学习笔记(深入)”;
- 改用
await import('./mod.mjs')(动态导入,返回 Promise) - 确保调用处在 async 函数内,或顶层使用
await(仅限 ES 模块顶层) - 注意:
require()永远无法加载.mjs,这是硬性限制
避免循环依赖与顶层 await 的陷阱
混合使用时,ES 模块的静态分析 + CommonJS 的动态执行特性容易引发隐蔽问题:
- 若
A.mjs→B.cjs→A.mjs,Node.js 允许但会返回A.mjs的空命名空间({}),直到其执行完成 -
.mjs中的await顶层语句会阻塞整个模块图初始化,而.cjs无此机制,混用可能打乱执行顺序 - 建议:核心逻辑统一模块类型;若必须混合,将 ESM 作为入口,CJS 作为底层工具封装










