Skip to content

Node.js 模块系统

Node.js 支持两套模块规范:CommonJS(CJS)ES Modules(ESM)。理解两者的差异与兼容方式,是组织和维护 Node 项目的基础。

作者:yanshaodong

CommonJS(CJS)

Node.js 传统的模块系统,使用 require / module.exports

javascript
// math.js
function add(a, b) {
  return a + b
}
module.exports = { add }

// index.js
const { add } = require('./math')
console.log(add(1, 2))

特点:

  • 同步加载,运行时解析。
  • 模块作用域隔离,不会污染全局。
  • Node 默认模块系统(文件扩展名 .jspackage.json"type": "module" 时)。

ES Modules(ESM)

现代 JavaScript 标准模块,使用 import / export

javascript
// math.mjs
export function add(a, b) {
  return a + b
}

// index.mjs
import { add } from './math.mjs'
console.log(add(1, 2))

启用 ESM 的两种方式:

  1. 文件扩展名用 .mjs
  2. 或在 package.json 中声明 "type": "module",此时 .js 即按 ESM 解析(CJS 需改用 .cjs)。

CJS 与 ESM 对比

维度CommonJSES Modules
语法require / exportsimport / export
加载运行时、同步静态分析、可异步
是否支持 Tree-Shaking
浏览器兼容是(原生支持)
动态导入require() 任意处import() 返回 Promise

互相引用

在 ESM 中引入 CJS 模块:

javascript
// ESM 中
import cjsModule from './legacy.cjs'   // 默认导入
import { named } from './legacy.cjs'   // Node 12+ 支持具名导入(部分)

在 CJS 中引入 ESM 模块(必须使用动态 import):

javascript
// CJS 中
async function main() {
  const esm = await import('./modern.mjs')
  esm.doSomething()
}

package.json 的 type 字段

json
{
  "name": "my-app",
  "type": "module"
}
  • 省略或 "commonjs".js 视为 CJS。
  • "module".js 视为 ESM,CJS 文件需 .cjs 后缀。

模块解析机制

Node 解析 require('x') / import 'x' 的顺序(简化):

  1. 内置模块(如 fspath)→ 直接返回。
  2. ./ ../ / 开头 → 按路径解析文件或目录。
  3. 裸模块名 → 从 node_modules 向上逐级查找。

目录模块:若引入的是一个文件夹,Node 会读取其 package.json"main"(CJS)或 "exports"(ESM)字段确定入口。

最佳实践

  • 新项目优先使用 ESM"type": "module"),语法现代、利于 Tree-Shaking。
  • 维护旧项目或发布库时,可同时提供 CJS 与 ESM 产物(通过 exports 字段映射)。
  • 避免在 ESM 顶层使用 __dirname / __filename(不存在),改用:
javascript
import { fileURLToPath } from 'url'
import { dirname } from 'path'

const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)

作者:yanshaodong