返回
创建于
状态
公开
简单来说:CommonJS (CJS) 基本上不支持 Tree-shaking,或者说效果非常有限。
Tree-shaking 的核心依赖于 ES Modules (ESM) 的“静态结构”特性,而 CommonJS 的“动态性”则是它的天然克星。
为什么 CommonJS 难以进行 Tree-shaking?
1. 动态导出(Dynamic Export)
在 CJS 中,你可以根据条件来决定导出什么。打包工具在不运行代码的情况下,很难确定 module.exports 最终会长什么样。
1if (process.env.NODE_ENV === 'production') {
2 module.exports = { a: 1 };
3} else {
4 module.exports = { b: 2 };
5}2. 动态导入(Dynamic Require)
CJS 的 require 是同步且动态的。路径甚至可以是一个变量,这让打包工具无法在构建阶段(Static Analysis)建立完整的依赖图。
1const moduleName = 'utils';
2const tools = require(`./${moduleName}.js`); // 工具无法预知加载了哪个文件3. 对象引用特性
CJS 导出的是一个对象。当你 require 一个模块时,你拿到的是这个对象的引用。现代打包工具很难追踪这个对象上的哪些属性没被用到,尤其是当对象被重新赋值或传递时。
现状:虽然难,但并非完全不可能
尽管原生不支持,但现代构建工具(如 Webpack, Rollup, Terser)做了一些尝试来优化 CJS:
- Webpack 的优化:Webpack 会尝试分析简单的 CJS 导出(例如静态定义的
module.exports = { ... }),并在某些情况下通过Terser这种压缩工具识别并删除未引用的代码。但这非常依赖代码的写法是否足够“规整”。 - 插件转换:有些开发者会使用 Babel 插件将 CJS 转换为 ESM,然后再进行 Tree-shaking。
- Package.json 的
sideEffects:通过在package.json中标记sideEffects: false,可以帮助工具更大胆地剔除未使用的模块,无论它是 CJS 还是 ESM。
对比总结
| 特性 | CommonJS (CJS) | ES Modules (ESM) |
|---|---|---|
| 加载方式 | 运行时加载(动态) | 编译时加载(静态) |
| 导出内容 | 值的拷贝(对象) | 值的动态绑定(只读引用) |
| Tree-shaking | 极难/不支持 | 天然支持 |
| 主要用途 | Node.js 服务端 | 浏览器端、现代前端项目 |
建议
如果你希望项目拥有更好的性能和更小的体积:
- 优先使用
import和export(ESM)。 - 在发布 npm 包时,提供
module字段(指向 ESM 版本)和main字段(指向 CJS 版本)。 - 如果必须使用 CJS 库,尽量寻找支持按路径引入的库(例如
import lodash/fp而不是import { fp } from 'lodash')。