Tree Shaking:只打包用到的代码
🎯 引言
学完这篇文章,你将理解 Tree Shaking 的原理,知道为什么它必须依赖 ES 模块,并能写出“对 Tree Shaking 友好”的代码,让你的库保持小巧。
🧱 什么是 Tree Shaking
Tree Shaking(摇树)指的是打包时自动移除没有被使用的代码。
想象一棵挂满果子的树,你摇一摇,熟透的果子(用不到的代码)掉下来,树上只留下你需要的部分。
回顾第一篇的例子:
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
import { add } from './math.js';
console.log(add(1, 2));
打包产物里只有 add,没有 subtract。因为入口只引用了 add,Rollup 分析后发现 subtract 没人用,就直接把它丢掉了。
Tree Shaking 是 Rollup 最先提出并发扬光大的能力,如今已成为所有现代打包工具的标配。
✨ 为什么 Tree Shaking 依赖 ES 模块
这是理解 Tree Shaking 的关键。Tree Shaking 的前提是:打包工具要在“不运行代码”的情况下,静态分析出哪些代码被用到了。
ES 模块恰好满足这个条件:
// import / export 写在文件顶层,是固定不变的
import { add } from './math.js';
export { add };
而 CommonJS 的导出是“动态”的,可以写在任何逻辑里:
// CommonJS 可以这样写,打包工具不运行代码根本无法判断导出了什么
if (process.env.NODE_ENV === 'production') {
module.exports = { add };
} else {
module.exports = { add, subtract };
}
所以记住结论:只有基于 ES 模块(import / export),Tree Shaking 才能发挥作用。这也是为什么发布库时一定要提供 es 格式的产物。
🛠 动手验证 Tree Shaking
我们用一个更直观的例子感受它的威力。安装 lodash-es(版本 4.17.21):
npm install lodash-es@4.17.21
只使用其中一个函数:
import { debounce } from 'lodash-es';
export { debounce };
配合 node-resolve 插件打包:
import resolve from '@rollup/plugin-node-resolve';
export default {
input: 'src/index.js',
output: {
file: 'dist/bundle.js',
format: 'es'
},
plugins: [resolve()]
};
lodash-es 完整源码有上万个函数,但打包后的 bundle.js 只有几百行——因为 Rollup 只把 debounce 及其内部依赖打了进来,其余函数全部被摇掉了。
lodash-es 而不是 lodash。前者是 ES 模块写法,支持 Tree Shaking;后者是 CommonJS,只能整个打包。💡 sideEffects:告诉工具哪些文件不能摇
有些文件“光看引用关系”像是没有被使用,但实际上有重要副作用,比如:
// 没有导出任何东西,只是在全局打补丁
Array.prototype.myCustomMethod = function () {
// ...
};
这类文件可能被误判为“无用代码”而摇掉。为了保险起见,发布库时可以在 package.json 里声明 sideEffects 字段:
{
"name": "my-utils",
"sideEffects": false
}
"sideEffects": false:明确表示所有文件都没有副作用,可以放心摇。- 如果个别文件有副作用,写成数组:
"sideEffects": ["src/polyfill.js"]。
这个字段是给使用方的打包工具看的提示,写上它能让你的库被更彻底地优化。
🧾 小节总结
- Tree Shaking 指打包时自动移除未使用的代码,Rollup 是它的发源地。
- Tree Shaking 依赖 ES 模块的静态结构,CommonJS 无法被静态分析。
- 发布库时务必提供
es格式产物,使用方才能享受 Tree Shaking。 - 通过
package.json的sideEffects字段声明副作用,避免误删有副作用的代码。
❓ 知识问答
Q1:Tree Shaking 需要单独开启吗?
A:不需要。Rollup 默认就会做 Tree Shaking,你不用写任何配置。
Q2:引入了整个模块,但只用其中一个方法,能摇掉其他方法吗?
A:要看引入方式。import { debounce } from 'lodash-es' 这种具名导入可以精确摇掉未用部分;如果写成 import _ from 'lodash'(CommonJS 整包引入),就无法摇掉了。
Q3:TypeScript 写的库还能 Tree Shaking 吗?
A:可以,只要最终发布的是 ES 模块格式的 JavaScript。注意编译配置中 module 要设为 ESNext,不要编译成 CommonJS。
Q4:Webpack 和 Vite 也有 Tree Shaking 吗?
A:有。Tree Shaking 由 Rollup 发扬光大后,Webpack 和 Vite(生产构建基于 Rollup)也都支持了,前提同样是基于 ES 模块。
🧪 小练习
验证 Tree Shaking 的效果:
- 在
rollup-demo中安装lodash-es@4.17.21。 - 分别用两种方式打包并对比产物体积:
- 方式一:
import { debounce } from 'lodash-es' - 方式二:
import { debounce, throttle, cloneDeep } from 'lodash-es'并全部导出
- 方式一:
- 观察第二种方式的产物是否比第一种大。
// 请在这里编写引入语句,然后执行 npm run build 对比产物
🎉 恭喜你已经理解了 Tree Shaking 的原理!下一篇我们将综合运用前面所有知识,完整打包并发布一个工具库。
