Rollup 是怎么实现 Tree-shaking 的

作者:Administrator 发布时间: 2025-12-16 阅读量:10 评论数:0

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)。

  • 优势:

    1. 代码体积更小(少了大量的函数包装壳)。

    2. 更容易分析变量的引用链(因为都在一个作用域里)。

第四步:标记 (Mark) - 只有用到的才会被标记

Rollup 会遍历 AST,进行“可达性分析”:

  1. 从入口文件(Entry)出发,寻找所有被使用的变量。

  2. 如果发现变量 A 被使用了,就去定义 A 的地方,把它标记为“Included”。

  3. 如果 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 的处理逻辑:

  1. 分析 index.js,发现用到了 add

  2. utils.jsadd,标记为“保留”。

  3. 扫描结束。

  4. 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 的解决方案:

  1. 静态分析: 尝试分析语句是否纯净(Pure)。

  2. package.json 配置: 允许开发者在 package.json 中配置 "sideEffects": false,显式告诉 Rollup:“我的包是纯净的,如果没有被用到,请直接把整个文件删掉,不用担心副作用。”

  3. /* #__PURE__ */ 注释: 开发者可以在函数调用前加这个注释,告诉 Rollup 这个函数调用是无副作用的,如果返回值没被用,可以直接删掉该调用。


总结

Rollup 实现 Tree-shaking 的公式:

TreeShaking = ESM(静态结构) + AST(语法分析) + ScopeHoisting(平铺作用域) + Inclusion(只纳投名状)

它不是在做“减法”(删代码),而是在做“加法”(只把有用的代码加进来)。

评论