- 构建依赖图:打包工具从入口文件开始,分析所有模块的导入导出关系
- 标记依赖:识别哪些导出被其他模块使用,哪些从未被引用
- 静态分析:基于 ES6 模块的静态结构(
import
/export
必须是顶层声明)
Tree Sharking
约 1303 字大约 4 分钟
2025-06-22
一、简介
Tree Shaking 是现代 JavaScript 打包工具中实现 死代码消除(Dead Code Elimination) 的一项关键优化技术。
Tree Shaking 可以 显著减小打包后的文件体积,提高网页加载速度。
Tree Shaking 通过静态分析移除代码中未被使用的部分(就像摇树让枯叶落下一样),能够在打包时安全地移除:
- 未被导入的模块
- 模块中未被使用的导出
- 不会产生副作用的冗余代码
二、工作流程
Tree Shaking 的大致过程如下:
依赖关系分析阶段
代码标记阶段
- 标记"活的"代码:从入口开始,标记所有被直接或间接引用的导出
- 识别副作用:通过
package.json
的sideEffects
属性或 魔法注释 标记有副作用的模块
代码消除阶段
- 移除未引用代码:删除所有未被标记的导出和相关的代码
- 保留副作用代码:即使某些代码未被直接使用,但如果它有副作用也会被保留
优化输出阶段
- 合并代码块:将多个小文件合并成一个大文件
- 压缩代码:移除不必要的空格、注释等
- 生成最终包:输出只包含必要代码的打包文件
End
三、如何开启
1、Webpack 4 及以上版本
从 Webpack 4 开始,Webpack 内置了 Terser
插件,用于压缩代码。Terser
插件集成了 Tree Shaking 功能,能够自动移除未使用的代码。
只要设置 mode
为 production
,Webpack 会自动启用该插件,无需手动配置。
module.exports = {
mode: "production",
// 其他配置...
};
2、Webpack 3 及以下版本
在老版本的 Webpack 中,要开启 Tree Shaking,需要手动配置插件。
// webpack.config.js
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
// 其他配置...
optimization: {
minimizer: [
new TerserPlugin({
// 开启 Tree Shaking
treeShaking: true,
}),
],
},
};
// webpack.config.js
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
module.exports = {
// 其他配置...
optimization: {
minimize: true,
minimizer: [
new UglifyJsPlugin({
cache: true,
parallel: true,
sourceMap: true, // 如果需要source map的话
}),
],
},
};
四、生效条件
要配置 Webpack 实现代码的无用代码剔除,需要确保一下几点:
1、开启生产模式
配置 mode
为 production
,Webpack 会自动启用相关优化。
module.exports = {
mode: "production",
// 其他配置...
};
2、确保使用 ES6 模块化
Webpack 的 Tree Shaking 功能主要依赖于 ES 模块的静态结构。
- 确保你的代码使用 ES 模块,而不是 CommonJS 模块。
- 确保你的代码中没有使用动态拼接的路径,主要针对
import()
动态导入。 - 如果你的代码使用了 CommonJS 模块,你需要使用 Babel 等工具将其转换为 ES6 模块。同时防止 Babel 把 ES6 模块转成 CommonJS 模块。
3、标记副作用
在 Webpack 的 Tree Shaking 过程中,副作用 指的是模块执行时除了导出值之外的其他行为。这些行为可能包括:
- 修改全局变量、在
window
上挂载对象 - 执行 I/O 操作
- 动态拼接路径引入的模块或者路由(路径包含变量)
- 特殊语法:
eval
、with
- 导入一个 CSS 文件(它会直接影响页面样式)
如果直接删除有副作用的代码,可能导致代码运行时报错。因此,我们需要进行相关的配置,来告诉 Webpack 哪些模块是有副作用的,哪些模块是无副作用的。
sideEffects
属性
在 package.json
中设置 sideEffects
为 false
,表示仓库下所有模块都没有副作用,Webpack 移除未使用的导出是安全的。如果存在有副作用的代码,我们可以配置一个数组来告诉 Webpack 保守处理。
{
// 标记所有的代码都没有副作用
"sideEffects": false
// 标记有副作用的文件
"sideEffects": [
"./src/some-side-effectful-file.js"
"./src/polyfill.js",
"*.css"
]
}
/*#__PURE__*/
注释
标记函数、模块调用为无副作用,等于告诉 Webpack,如果该函数、模块未被调用,可以被安全的移除。
const result = /*#__PURE__*/ someFunction();
class MyClass {
constructor() {
/*#__PURE__*/ initPolyfill();
}
}
const value = /*#__PURE__*/ a + b * c;
4、编写纯模块
尽量编写无副作用的模块。函数尽量是纯函数、尽量不导入其他的依赖、模块的导入不应该改变全局状态。
什么是纯函数?
一个函数的返回结果只依赖于它的参数,并且在执行过程中没有副作用,就称为纯函数。
五、示例
假设模块 math.js
如下:
export function square(x) {
return x * x;
}
export function cube(x) {
console.log("cube"); // 副作用
return x * x * x;
}
如果只使用 square
,cube
中的 console.log
是副作用,Webpack 可能会因此不会移除 cube
。
更新日志
e7112
-1于