基于本文回答

播面 播面

文解图解播客视频,多维讲透八股文
0
评论

什么是 Tree Shaking?它的工作原理是什么?

在 Webpack 中,Tree Shaking(树摇)是一个非常关键的性能优化技术。

简单来说,Tree Shaking 就是“消除无用代码”(Dead Code Elimination)的代名词


一、 什么是 Tree Shaking?

如果你把你的应用程序比作一棵“树”:

  • 源代码和依赖库是这棵树的“树枝和绿叶”;
  • 没有用到的代码(比如声明了但从未调用的函数、导入了但未使用的模块)就是“枯黄的死叶”;
  • Tree Shaking 的过程就像是摇晃这棵树,把那些死掉的叶子摇掉,最终只打包真正用到的代码,以此来大幅减小最终生成的 JS 文件体积。

一个直观的例子:

假设你引入了一个工具库 utils.js

javascript
// utils.js
export const add = (a, b) => a + b;
export const minus = (a, b) => a - b; // 这个函数你根本没用过

main.js 中:

javascript
// main.js
import { add } from './utils.js';
console.log(add(1, 2));

开启 Tree Shaking 后,打出来的 bundle 文件中完全不会包含 minus 函数的代码


二、 Tree Shaking 的工作原理

Tree Shaking 的实现依赖于 ES6 模块语法(ESM, import/export 的静态特性,以及 Webpack 与代码压缩工具(如 Terser)的协作。它的工作流程主要分为以下几个步骤:

1. 静态代码分析(依赖 ESM 语法)

这是 Tree Shaking 能工作的前提。

  • CommonJS(require/module.exports)是动态的:你可以在 if 语句或者函数内部 require 一个模块,只有在运行时才知道导入了什么。
  • ESM(import/export)是静态的import 只能在模块的顶部,导入/导出关系在编译期(代码运行前)就能确定。

Webpack 在构建“依赖图谱(Dependency Graph)”时,通过 AST(抽象语法树)解析,能够精准地知道哪个模块输出了什么,以及哪个模块引入了什么。

2. Webpack 标记无用代码(Marking)

在编译阶段,Webpack 会遍历所有代码:

  • 它会追踪每一个 export 出来的变量或函数是否在其他地方被 import 和使用。
  • 如果发现某个 export 未被使用,Webpack 不会立刻删除它,而是在编译后的代码中加入特殊标记。
  • 例如,Webpack 会在未使用的导出前加上注释:/* unused harmony export minus */

3. 代码压缩工具删除代码(Elimination)

Webpack 本身主要负责标记,真正删除死代码的是代码压缩工具(如 TerserUglifyJSESBuild)。

当 Webpack 完成标记后,压缩工具会在生产模式(mode: 'production')下运行。压缩工具看到这些标记以及上下文后,确认这些代码不会被执行且没有副作用,就会直接从代码树中将它们剔除。

4. sideEffects(副作用)标记优化

有些代码虽然没有导出任何东西,但它一被加载就会产生影响(例如修改了全局变量、给 Array.prototype 挂载了方法、或者引入了全局 CSS)。这种行为叫做副作用(Side Effects)

为了避免误删这些有副作用的代码,Webpack 支持在 package.json 中配置 sideEffects 属性:

  • 如果设置 "sideEffects": false:告诉 Webpack “这个项目/库里的文件都是纯净的,如果没有被 import 具名导出,可以直接跳过整个文件”。
  • 如果有副作用文件(如 CSS):可以设置数组形式 "sideEffects": ["*.css", "*.scss"]

三、 如何在 Webpack 中开启 Tree Shaking?

在 Webpack 5 中,Tree Shaking 默认开启得非常好,但你需要确保以下几点:

  1. 使用 ESM 规范编写代码:确保使用 importexport
  2. 避免 Babel 将 ESM 转化为 CommonJS
    .babelrcbabel.config.js 中设置 modules: false
    json
    {
      "presets": [
        ["@babel/preset-env", { "modules": false }]
      ]
    }
  3. 设置模式为生产模式
    webpack.config.js 中设置 mode: 'production',这会自动触发代码压缩和死代码删除。
  4. package.json 中配置 sideEffects
    正确声明项目或依赖包是否有副作用。

四、 为什么有时候 Tree Shaking 会失效?

  1. 使用了 CommonJS 语法:如果用 require 引入模块,Webpack 无法进行静态分析。
  2. Babel 把 ESM 编译成了 CommonJS(最常见坑点)。
  3. 代码含有副作用(Side Effects):如果 Webpack 认为某段代码有副作用,为了不破坏程序逻辑,它会保守地保留这段代码。
  4. 引入了整个大而全的库:例如 import _ from 'lodash'。lodash 官方发布的是 CommonJS 规范,无法 Tree Shaking;应该使用 lodash-es 或单独引入 import cloneDeep from 'lodash/cloneDeep'