Tree Shaking原理
一、Tree Shaking 是什么?
Tree Shaking
是一个比喻,想象你的代码是一棵树,其中有绿色的、充满生机的树叶(被使用的代码),也有枯黄的、死亡的树叶(未使用的代码)。Tree Shaking 就是“摇动”这棵树,让那些死代码掉下来,最终只将活的代码打包到最终的 bundle 中
。
它的核心目标
是减小最终输出文件的体积(bundle size)
。
二、前置核心条件:ES Module
Tree Shaking
之所以能实现,完全依赖于 ES2015(ES6)
模块系统的静态结构特性。
正是这种静态结构,使得打包工具(Webpack、Rollup等)在代码运行之前,就能像编译器一样,准确地分析出:
- 哪些模块被导出了。
- 导出的哪些成员被其他模块导入了。
- 哪些导出成员从未被其他模块使用。
结论:Tree Shaking
只对 ES Module
语法 (import/export)
有效,对 CommonJS (require)
无效
三、Webpack Tree Shaking 的工作原理
Webpack
的 Tree Shaking
过程可以分为两个核心阶段:
- 阶段一:
标记未使用代码(Marking)
- 依赖收集:Webpack 从入口文件开始,根据 import和 export语句构建整个应用的依赖关系图(Dependency Graph)。这个过程就像画出一张地图,清晰地标明了每个模块从哪里来(被谁导入),到哪里去(导出了什么,又导入了什么)。
- 标记使用状态:Webpack 会遍历这张依赖图,并标记所有被引用和使用的导出。这个过程被称为 “活代码”分析。
- 识别未使用代码:那些在依赖图中被导出,但没有任何一条 import语句引用它的代码,就会被标记为 “未使用代码(unused harmony export)”。
- 阶段二:
清除未使用代码(Shaking/Elimination)
-
仅仅标记出来还不够,必须将其从最终的 bundle 中移除。这个清除工作主要由 压缩工具 完成,最常用的是 TerserWebpackPlugin(Webpack 4+ 默认使用,用于压缩 JS)。
-
传递信息:Webpack 在生成的代码中,会为那些未使用的导出添加特殊的注释(如 /* unused harmony export [export_name] */)。
-
Terser 进行清除:Terser 在压缩和混淆代码时,会识别这些特殊注释,并安全地将这些未被标记为“活代码”的导出语句彻底删除。
-
简单概括:Webpack 负责分析并标记哪些代码是“死的”,Terser 这样的压缩工具负责动手清除这些死代码。
-
四、项目配置与最佳实践
要让 Tree Shaking 高效工作,需要满足一些配置条件。
-
使用生产模式 (production)
在 webpack.config.js中:module.exports = {mode: 'production', // 生产模式会自动开启 tree shaking 等优化// ... };
或者通过命令行:webpack --mode=production
为什么? 因为生产模式会默认:
- 启用各种优化(包括 optimization.usedExports: true)。
- 启用代码压缩(使用 TerserWebpackPlugin)。
-
避免副作用声明(关键!)
-
副作用:一个模块在执行时,除了导出成员,还会做其他事情,例如修改全局变量、进行 polyfill等。
Webpack 无法 100% 确定一个文件是否有副作用。为了安全起见,如果一个文件有副作用,即使其导出未被使用,Webpack 也可能不敢将其 tree-shake 掉。
解决方法:在 package.json中声明你的模块没有副作用。
{"name": "your-project","sideEffects": false // 声明整个项目都没有副作用}
如果你的项目或某些文件确实有副作用,可以提供一个数组:
{"sideEffects": ["./src/some-side-effectful-file.js","*.css" // 通常需要排除 CSS 文件,因为导入 CSS 也是副作用] }
这个配置极大地帮助 Webpack 更大胆地进行 Tree Shaking。
-
-
使用 optimization.usedExports
(在生产模式下默认开启,无需手动设置)module.exports = {// ...optimization: {usedExports: true, // 标记哪些导出被使用了}, };