判断模块类型需看文件扩展名和package.json的"type"字段:.cjs强制CommonJS,.mjs强制ESM,.js配合"type":"module"为ESM,否则为CommonJS。

CommonJS 和 ES 模块(ESM)不是同一套机制,不能混用 require() 和 import,Node.js 中默认启用 ESM 需靠文件扩展名或 package.json 显式声明。
如何判断当前代码走的是 CommonJS 还是 ESM?
看执行环境和文件标识:
- 文件以
.cjs结尾 → 强制 CommonJS - 文件以
.mjs结尾 → 强制 ESM - 文件为
.js,且所在项目package.json有"type": "module"→ 默认 ESM - 文件为
.js,且package.json无"type"字段或值为"commonjs"→ 默认 CommonJS -
require()在 ESM 文件中直接报错:ReferenceError: require is not defined
require() 和 import 的行为差异
CommonJS 是运行时同步加载,ESM 是编译时静态分析 —— 这导致两者根本不可互换:
-
require()可以写在条件语句里、函数内,甚至拼接路径:require('./' + name + '.js') -
import必须是顶层语句,路径必须是字符串字面量,不能动态;否则报错:Cannot use import statement outside a module或Dynamic imports must be of string literal -
require()返回的是模块的exports对象,可随意赋值:module.exports = class A {}或exports.foo = 1 -
import只能解构或命名导入,且绑定是“实时只读引用”(非拷贝),被导入模块内部改了值,导入方能感知(如导出一个对象,其属性被修改)
如何在 ESM 中兼容 CommonJS 模块?
Node.js 允许 ESM 用 import 加载 CommonJS 模块,但行为受限:
立即学习“Java免费学习笔记(深入)”;
- 如果 CommonJS 模块只做了
module.exports = xxx(单个值),ESM 中可用import xxx from 'pkg' - 如果 CommonJS 模块用了
exports.a = 1; exports.b = 2,ESM 中必须写成import * as mod from 'pkg',再通过mod.a访问 - 不能用
import { a } from 'pkg'直接解构 —— 因为 CommonJS 没有静态export声明,ESM 无法做静态分析 - 反过来不行:
require()无法加载原生 ESM 文件(会报错ERR_REQUIRE_ESM),除非用await import()动态导入
实际开发中最容易踩的坑
常见报错背后的真实原因:
TypeError: __dirname is not defined in ES module scope
→ ESM 中没有 __dirname 和 __filename,得用:
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const filename = fileURLToPath(import.meta.url);
const dirname = dirname(__filename);
另一个高频问题:
Must use import to load ES Module
→ 你用 require('./util.mjs') 了,但 .mjs 是强制 ESM,只能用 import 或 await import() 加载。
跨模块混合时,最麻烦的不是语法,而是循环依赖处理逻辑完全不同:CommonJS 返回当前已执行部分的 exports,ESM 则抛出 ReferenceError(未初始化完成前访问)。











