Webpack 与 Rollup 中 Tree-shaking 的实现原理与效果

Tree-shaking 是现代前端构建工具中用于消除死代码(未被使用的代码)的重要优化手段,核心原理是利用 ES6 模块的静态特性(import/export)分析代码依赖,剔除未被引用的导出内容。

一、Tree-shaking 核心前提

Tree-shaking 仅对 ES6 模块(ESM) 有效,因为 ESM 具有以下静态特性:

  • 模块依赖关系在编译时确定(而非运行时)。
  • import/export 语句只能出现在模块顶层,无法动态修改。

CommonJS 模块(require/module.exports)因依赖关系动态化,无法被 Tree-shaking 优化。

二、Rollup 中的 Tree-shaking

Rollup 是专注于 ES 模块打包的工具,Tree-shaking 是其原生核心功能,实现简洁且高效。

1. 实现原理

Rollup 通过以下步骤实现 Tree-shaking:

  1. 依赖解析 :遍历模块的 import/export 语句,构建模块依赖图。
  2. 标记未使用代码 :从入口模块出发,追踪所有被引用的导出(export),未被引用的导出被标记为 "死代码"。
  3. 删除死代码:在打包阶段直接剔除标记的死代码,不生成冗余内容。

Rollup 对代码的静态分析更彻底,且默认输出未被混淆的代码,因此 Tree-shaking 效果直观可见。

2. 代码示例与效果

源文件
javascript 复制代码
// utils.js(ES 模块)
export const add = (a, b) => a + b;
export const minus = (a, b) => a - b; // 未被使用的函数

// index.js(入口文件)
import { add } from './utils.js';
console.log(add(1, 2)); // 仅使用 add
Rollup 配置(rollup.config.js
arduino 复制代码
export default {
  input: 'src/index.js',
  output: {
    file: 'dist/bundle.js',
    format: 'es' // 输出 ES 模块(保留 import/export,便于观察)
  }
};
打包结果(dist/bundle.js
css 复制代码
const add = (a, b) => a + b;
console.log(add(1, 2));
  • 效果 :未被使用的 minus 函数被完全剔除,输出代码简洁。

三、Webpack 中的 Tree-shaking

Webpack 从 v2 开始支持 Tree-shaking,但实现逻辑更复杂,受多种因素影响(如模式、压缩工具等)。

1. 实现原理

Webpack 的 Tree-shaking 分为三个阶段:

  1. 标记阶段

    • 解析 ES 模块的 import/export,通过 ModuleConcatenationPlugin 分析模块依赖。
    • 对未被引用的导出标记为 /* unused harmony export xxx */(仅标记,不删除)。
  2. 删除阶段

    • 依赖 压缩工具(如 Terser) 剔除标记的死代码。
    • 仅在 production 模式下默认启用(开发模式为保留代码不删除)。
  3. 副作用处理

    • 通过 package.jsonsideEffects 字段标记模块是否有副作用(如全局变量修改、DOM 操作),避免误删有副作用的代码。

2. 代码示例与效果

源文件(同 Rollup 示例)
javascript 复制代码
// utils.js
export const add = (a, b) => a + b;
export const minus = (a, b) => a - b; // 未被使用

// index.js
import { add } from './utils.js';
console.log(add(1, 2));
Webpack 配置(webpack.config.js
css 复制代码
module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  mode: 'production' // 必须为 production 模式才会删除死代码
};
打包结果(dist/bundle.js,简化后)
arduino 复制代码
console.log(3); // add(1,2) 被直接计算,minus 完全删除
  • 效果 :未被使用的 minus 被剔除,且 add 函数因逻辑简单被进一步优化(直接计算结果)。

3. 关键配置:sideEffects

如果模块存在副作用(如修改全局变量),需在 package.json 中声明,避免被误删:

json

json 复制代码
{
  "sideEffects": [
    "./src/polyfill.js", // 有副作用的模块
    "*.css" // CSS 文件通常有副作用(插入样式)
  ]
}
  • sideEffects: false:表示所有模块均无副作用,可安全删除未引用代码。

四、核心差异对比

特性 Rollup Webpack
分析能力 原生支持 ESM 静态分析,更彻底 需依赖插件(ModuleConcatenationPlugin),分析逻辑较复杂
删除死代码时机 打包阶段直接删除 依赖压缩工具(Terser)在优化阶段删除
开发模式效果 即使开发模式也会删除死代码 production 模式删除(开发模式保留用于调试)
输出可读性 输出代码接近源码,Tree-shaking 效果直观 输出代码经压缩混淆,效果需反推
适用场景 库文件打包(如工具库、组件库) 应用程序打包(依赖复杂、需处理多种模块类型)

五、总结

  • Rollup:Tree-shaking 实现简洁高效,输出代码干净,适合库文件打包(如 React、Vue 等)。
  • Webpack:Tree-shaking 需配合压缩工具和模式配置,功能更全面(支持多种模块类型、复杂依赖),适合应用程序打包。

六、直观点

  • 整个过程就像 "清理房间":先摸清房间里的物品(依赖解析)→ 标记没用的垃圾(标记死代码)→ 直接扔掉垃圾(删除死代码);
  • 对比 Webpack :Rollup 是 "边打包边扔垃圾",Webpack 是 "先标记垃圾,最后找清洁工(Terser)扔",所以 Rollup 的产物更直观看到剔除效果。
相关推荐
ywf12151 小时前
前端的dist包放到后端springboot项目下一起打包
前端·spring boot·后端
恋猫de小郭1 小时前
2026,Android Compose 终于支持 Hot Reload 了,但是收费
android·前端·flutter
hpoenixf7 小时前
2026 年前端面试问什么
前端·面试
还是大剑师兰特7 小时前
Vue3 中的 defineExpose 完全指南
前端·javascript·vue.js
泯泷7 小时前
阶段一:从 0 看懂 JSVMP 架构,先在脑子里搭出一台最小 JSVM
前端·javascript·架构
mengchanmian8 小时前
前端node常用配置
前端
华洛8 小时前
利好打工人,openclaw不是企业提效工具,而是个人助理
前端·javascript·产品经理
xkxnq9 小时前
第六阶段:Vue生态高级整合与优化(第93天)Element Plus进阶:自定义主题(变量覆盖)+ 全局配置与组件按需加载优化
前端·javascript·vue.js
A黄俊辉A9 小时前
vue css中 :global的使用
前端·javascript·vue.js
小码哥_常10 小时前
被EdgeToEdge适配折磨疯了,谁懂!
前端