Node 支持两种模块系统:CommonJS (CJS) — 原始系统,使用 require/module.exports — 和 ES Modules (ESM) — 标准系统,使用 import/export。它们在语法、加载行为和 interop 方面有所不同。
Node 支持两种模块系统:CommonJS (CJS) — 原始系统,使用 require/module.exports — 和 ES Modules (ESM) — 标准系统,使用 import/export。它们在语法、加载行为和 interop 方面有所不同。
// math.js
function add(a, b) { return a + b; }
module.exports = { add };
// app.js
const { add } = require("./math"); // synchronous, runtime
// math.mjs (or .js with "type": "module")
export function add(a, b) { return a + b; }
export default something;
// app.mjs
import { add } from "./math.mjs"; // static, hoisted
CommonJS ES Modules
Syntax require/module.exports import/export
Loading synchronous, runtime static, hoisted
Top-level await no yes
Tree-shaking hard yes (statically analyzable)
File extension .cjs / .js (default) .mjs / .js with "type":"module"
__dirname/__filename available not available (use import.meta.url)
// package.json
{ "type": "module" } // now .js files are treated as ESM
或显式使用 .mjs 扩展名。如果没有这样做,Node 会将 .js 默认为 CommonJS。
// ESM can import CommonJS:
import pkg from "some-cjs-lib"; // works (default import)
// CommonJS CANNOT require() a pure-ESM package (it's async):
const esm = require("esm-only-pkg"); // ❌ ERR_REQUIRE_ESM
// must use dynamic import: const esm = await import("esm-only-pkg");
粗糙的地方:CJS 无法 require() ESM-only 包 — 一个常见的迁移难点。另请注意 ESM 缺少 __dirname(使用 import.meta.url)。
了解这两个系统对于真实的 Node 工作至关重要:ESM 是未来(标准、可摇树、顶层 await、浏览器对齐),也是新项目的正确选择,但 npm 生态系统和现有代码的大部分仍然是 CommonJS。
理解语法差异、如何启用 ESM("type": "module")以及 interop 限制(尤其是 CJS 无法 require ESM)可以防止混合使用时经常出现的混淆和错误。