CommonJS vs ESM(JavaScript 模块化详解)
JavaScript 代码规模增大后,需要一种机制来拆分文件、管理依赖、复用代码,这就是 模块化(Module System)。
目前 JavaScript 主要有两种模块规范:
- CommonJS(CJS)
- ECMAScript Modules(ESM)
一、CommonJS(CJS)
CommonJS 是 Node.js 早期使用的模块规范。
在 ES6 模块化出现之前,Node.js 使用 CommonJS 解决模块化问题。
1 导出模块
1 | module.exports = { |
或
1 | exports.add = add |
tips:
-
module.exports是真正的导出对象 -
exports只是module.exports的引用
2 导入模块
1 | const math = require('./math') |
tips:require() 会加载模块并返回 module.exports。
3 CommonJS 特点
1️⃣ 同步加载
1 | const module = require('./module') |
加载流程:
读取文件
↓
执行文件
↓
返回 exports
特点:
- 同步执行
- 适合服务器环境
2️⃣ 运行时加载
require() 本质是函数,因此可以动态执行。
1 | if (condition) { |
3️⃣ 值拷贝
CommonJS 导出的是 值的拷贝。
1 | // a.js |
1 | // b.js |
如果 a.js 里的 count 改变:
b.js 不会自动更新
4️⃣ 模块缓存
Node.js 会缓存加载过的模块。
1 | require('./a') |
模块只会执行一次。
缓存机制:
Module Cache
二、ESM(ECMAScript Modules)
ESM 是 ES6(2015)官方模块化规范。
现在浏览器和 Node.js 都支持。
1 导出模块
命名导出
1 | export const add = () => {} |
默认导出
1 | export default function add() {} |
2 导入模块
命名导入
1 | import { add } from './math.js' |
默认导入
1 | import add from './math.js' |
全部导入
1 | import * as math from './math.js' |
3 ESM 特点
1️⃣ 静态分析(编译时解析)
ESM 的依赖关系在 编译阶段就能确定。
1 | import { add } from './math' |
限制:
- 必须写在顶层
- 不能写在 if / for / function 中
错误示例:
1 | if (true) { |
2️⃣ 异步加载
浏览器加载模块时,并行下载模块,适合前端环境。
3️⃣ 实时绑定(引用)
ESM 导出的是 引用(Live Binding)。
1 | // a.js |
1 | // b.js |
count 会随着 a.js 的变化而变化。
4️⃣ 支持 Tree Shaking
由于 ESM 是 静态结构,打包工具可以删除未使用代码。
1 | export const add = () => {} |
如果只使用:
1 | import { add } from './math' |
打包时,sub 会被删除。
三、CJS vs ESM 对比
| 特性 | CommonJS | ESM |
|---|---|---|
| 出现时间 | Node.js | ES6 |
| 导入 | require | import |
| 导出 | module.exports | export |
| 加载方式 | 同步 | 异步 |
| 执行时机 | 运行时 | 编译时 |
| 值类型 | 值拷贝 | 引用 |
| Tree Shaking | 不支持 | 支持 |
| 浏览器支持 | 不支持 | 支持 |
四、Node.js 中使用 ESM
Node.js 默认使用 CommonJS。如果要使用 ESM,有两种方式:
方式一:package.json
1 | { |
之后 .js 文件默认使用 ESM。
方式二:使用 .mjs
index.mjs
五、CJS 和 ESM 互相导入
在 Node.js 中,两种模块系统可能共存。
1 ESM 导入 CJS
1 | import pkg from './cjs.js' |
默认导出为 module.exports。
2 CJS 导入 ESM
不能直接使用 require(),需要:
1 | import('./esm.js') |
动态导入。
六、ESM 对前端工程化的意义
现代打包工具更偏向使用 ESM,例如:
- Vite
- Rollup
- ESBuild
- Next.js
原因如下:
1 Tree Shaking
删除未使用代码。
2 静态依赖分析
可以在编译阶段构建依赖图。
3 更好的代码分割
1 | import('./module') |
七、总结
CommonJS 和 ESM 的区别:
CommonJS 是 Node.js 早期使用的模块规范,使用 require 和module.exports,模块是同步加载,运行时解析,导出的是值拷贝。
ESM 是 ES6 官方模块规范,使用 import 和export,模块在编译阶段进行静态分析,支持异步加载,导出的是引用,并且支持Tree Shaking。
现代前端构建工具(如 Vite、Rollup)通常优先使用ESM,因为它可以在构建阶段进行更好的优化。