主题
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 默认模块系统(文件扩展名
.js,package.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 的两种方式:
- 文件扩展名用
.mjs; - 或在
package.json中声明"type": "module",此时.js即按 ESM 解析(CJS 需改用.cjs)。
CJS 与 ESM 对比
| 维度 | CommonJS | ES Modules |
|---|---|---|
| 语法 | require / exports | import / 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' 的顺序(简化):
- 内置模块(如
fs、path)→ 直接返回。 - 以
./..//开头 → 按路径解析文件或目录。 - 裸模块名 → 从
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