Rollup 实现 Tree-shaking 的核心在于它利用了 ES Modules (ESM) 的静态结构特性,通过静态分析 (Static Analysis) 来判断哪些代码被真正使用了。
简单来说,Rollup 不是“移除没用的代码”,而是**“只打包用到的代码”**(Whitelist 白名单机制)。
以下是 Rollup 实现 Tree-shaking 的底层步骤和核心原理:
1. 前置条件:必须是 ESM
Rollup 能够实现高效 Tree-shaking 的根本原因在于 ES6 模块(ESM)的设计:
CommonJS (
require) 是动态的:你可以在if语句里引用模块,路径甚至可以是变量。这导致打包工具很难在不运行代码的情况下知道你需要什么。ESM (
import/export) 是静态的:import必须在顶层,不能动态改变。这使得 Rollup 可以在编译阶段(代码运行前)就确定依赖关系。
2. 核心流程:从 AST 到产物
Rollup 的处理流程主要分为三个阶段:解析 (Parse) -> 绑定 (Bind) -> 打包 (Bundle/Generate)。
第一步:构建 AST (Abstract Syntax Tree)
Rollup 使用轻量级的解析器 Acorn 将你的代码转换成 AST(抽象语法树)。AST 是代码的树状结构表示,让机器能读懂代码的语法含义。
第二步:构建模块图 (Module Graph)
Rollup 从入口文件(如 index.js)开始,分析 AST 中的 import 语句,递归地加载所有依赖文件,建立一个完整的模块依赖图。
第三步:作用域分析与 Scope Hoisting (关键)
这是 Rollup 与早期 Webpack 最大的不同,也是它 Tree-shaking 高效的原因。
Scope Hoisting (作用域提升/模块拼接):
Rollup 不会像 Webpack 那样把每个模块包裹在一个 function(module, exports) { ... } 中。相反,它尝试把所有模块的代码平铺到同一个作用域中(Module Concatenation)。
优势:
代码体积更小(少了大量的函数包装壳)。
更容易分析变量的引用链(因为都在一个作用域里)。
第四步:标记 (Mark) - 只有用到的才会被标记
Rollup 会遍历 AST,进行“可达性分析”:
从入口文件(Entry)出发,寻找所有被使用的变量。
如果发现变量
A被使用了,就去定义A的地方,把它标记为“Included”。如果
A依赖了B,就顺藤摸瓜去把B也标记为“Included”。
第五步:生成 (Generate) - 丢弃未标记的代码
最后生成代码时,Rollup 只会把标记为 "Included" 的语句写入最终文件。那些定义了但从未被追踪到的 AST 节点,直接被忽略,不会出现在产物中。
3. 一个具体的代码示例
假设有两个文件:
JavaScript
// utils.js
export const add = (a, b) => a + b;
export const sub = (a, b) => a - b; // 这个函数从未被使用
// index.js
import { add } from './utils.js';
console.log(add(1, 2));
Rollup 的处理逻辑:
分析
index.js,发现用到了add。去
utils.js找add,标记为“保留”。扫描结束。
sub函数虽然被 export 了,但从未被 index.js 引用链触达,因此在生成最终代码时,sub直接被丢弃。
4. 最大的难题:副作用 (Side Effects)
这是 Tree-shaking 最大的绊脚石。
如果一个函数在导入时执行了某些操作,修改了全局环境(例如修改了 window 对象,或者给 Array 原型添加了方法),这被称为副作用。
JavaScript
// effect.js
export const a = 1;
window.isLoaded = true; // 这是一个副作用!
即使你只引入了 a (import { a } from './effect.js'),Rollup 也不敢轻易把 window.isLoaded = true 这行代码删掉,因为删掉可能会导致程序逻辑错误(比如别的代码依赖 window.isLoaded)。
Rollup 的解决方案:
静态分析: 尝试分析语句是否纯净(Pure)。
package.json配置: 允许开发者在package.json中配置"sideEffects": false,显式告诉 Rollup:“我的包是纯净的,如果没有被用到,请直接把整个文件删掉,不用担心副作用。”/* #__PURE__ */注释: 开发者可以在函数调用前加这个注释,告诉 Rollup 这个函数调用是无副作用的,如果返回值没被用,可以直接删掉该调用。
总结
Rollup 实现 Tree-shaking 的公式:
TreeShaking = ESM(静态结构) + AST(语法分析) + ScopeHoisting(平铺作用域) + Inclusion(只纳投名状)
它不是在做“减法”(删代码),而是在做“加法”(只把有用的代码加进来)。