Rollup 简介

Tree Shaking:只打包用到的代码

理解 Tree Shaking 的原理,明白为什么它依赖 ES 模块,学会写出容易被摇掉无用代码的库。

🎯 引言

学完这篇文章,你将理解 Tree Shaking 的原理,知道为什么它必须依赖 ES 模块,并能写出“对 Tree Shaking 友好”的代码,让你的库保持小巧。


🧱 什么是 Tree Shaking

Tree Shaking(摇树)指的是打包时自动移除没有被使用的代码。

想象一棵挂满果子的树,你摇一摇,熟透的果子(用不到的代码)掉下来,树上只留下你需要的部分。

回顾第一篇的例子:

src/math.js
export function add(a, b) {
    return a + b;
}

export function subtract(a, b) {
    return a - b;
}
src/index.js
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 格式的产物。

如果你的库用 CommonJS 编写再发布,使用方即使用了 Vite / Rollup,也无法享受 Tree Shaking,没用到的代码会全部进入最终产物,白白增加体积。

🛠 动手验证 Tree Shaking

我们用一个更直观的例子感受它的威力。安装 lodash-es(版本 4.17.21):

npm install lodash-es@4.17.21

只使用其中一个函数:

src/index.js
import { debounce } from 'lodash-es';

export { debounce };

配合 node-resolve 插件打包:

rollup.config.mjs
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 又在意体积,请选 lodash-es 而不是 lodash。前者是 ES 模块写法,支持 Tree Shaking;后者是 CommonJS,只能整个打包。

💡 sideEffects:告诉工具哪些文件不能摇

有些文件“光看引用关系”像是没有被使用,但实际上有重要副作用,比如:

src/polyfill.js
// 没有导出任何东西,只是在全局打补丁
Array.prototype.myCustomMethod = function () {
    // ...
};

这类文件可能被误判为“无用代码”而摇掉。为了保险起见,发布库时可以在 package.json 里声明 sideEffects 字段:

package.json
{
    "name": "my-utils",
    "sideEffects": false
}
  • "sideEffects": false:明确表示所有文件都没有副作用,可以放心摇。
  • 如果个别文件有副作用,写成数组:"sideEffects": ["src/polyfill.js"]

这个字段是给使用方的打包工具看的提示,写上它能让你的库被更彻底地优化。


🧾 小节总结

  • Tree Shaking 指打包时自动移除未使用的代码,Rollup 是它的发源地。
  • Tree Shaking 依赖 ES 模块的静态结构,CommonJS 无法被静态分析。
  • 发布库时务必提供 es 格式产物,使用方才能享受 Tree Shaking。
  • 通过 package.jsonsideEffects 字段声明副作用,避免误删有副作用的代码。

❓ 知识问答

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 的效果:

  1. rollup-demo 中安装 lodash-es@4.17.21
  2. 分别用两种方式打包并对比产物体积:
    • 方式一:import { debounce } from 'lodash-es'
    • 方式二:import { debounce, throttle, cloneDeep } from 'lodash-es' 并全部导出
  3. 观察第二种方式的产物是否比第一种大。
src/index.js
// 请在这里编写引入语句,然后执行 npm run build 对比产物

🎉 恭喜你已经理解了 Tree Shaking 的原理!下一篇我们将综合运用前面所有知识,完整打包并发布一个工具库。