Webpack 的 Tree Shaking(搖樹)是一項用於消除 JavaScript 上下文中未引用代碼的優化手段,它能有效減小打包體積。

核心原理

Tree Shaking 的本質是 ​死代碼消除​,它依賴 ​ES6 模塊(ESM)的靜態語法結構​。

  1. 靜態分析​:ESM 的 import/export 語句必須位於模塊頂層(注意:模塊頂層不是模塊文件頂部的意思,模塊頂層可以認為是模塊文件中最外層的代碼區,不在任何函數、類或代碼塊內部),且模塊路徑必須是字符串常量。這樣, Webpack 在編譯階段就能構建出完整的模塊依賴圖,無需運行代碼即可分析出哪些導出值未被其他模塊使用 。

    這時有同學就會問了,那麼動態 import 怎麼判斷呢?

    其實,還是那個關鍵點,是否可以被“靜態分析”。

    // ❌ 難以靜態分析,無法使用搖樹優化
    const componentMap = {
      basic: () => import('./BasicComponent'),
      advanced: () => import('./AdvancedComponent')
    };
    const getComponent = componentMap[userInput]; // 運行時才能確定
    
    // ✅ 條件明確,可以被靜態分析
    if (import.meta.env.VITE_APP_MODE === 'basic') {
      const BasicComponent = await import('./BasicComponent');
    }
    
  2. 標記與清除​:Webpack 的 Tree Shaking 過程大致分為兩步。首先,在編譯階段,Webpack 會遍歷所有模塊,標記(Mark) 出未被使用的導出(通常會在註釋中生成類似 unused harmony export 的提示)。隨後,在代碼壓縮階段,Terser 等壓縮工具會真正將標記過的"死代碼"清除(Shake) 掉 。

這些配置你是否清楚?

要讓 Tree Shaking 生效,需要同時滿足以下條件:

  1. 使用 ES6 模塊語法​:必須使用 importexport 語句。CommonJS 的 requiremodule.exports動態的,無法在編譯時進行靜態分析,因此不支持 Tree Shaking 。
  2. 啓用生產模式或明確配置​:在 Webpack 配置中,將 mode 設置為 'production' 生產模式下會自動開啓相關的優化功能。當然也可以在開發模式下手動配置 optimization.usedExportsoptimization.minimize
// webpack.config.js
module.exports = {
  mode: 'production', // 生產模式自動開啓優化
  optimization: {
    usedExports: true, // 啓用使用導出分析
    minimize: true     // 啓用代碼壓縮(清除死代碼)
  }
};
  1. ​**正確聲明副作用 (sideEffects​​)**​:在項目的 package.json 中,通過 sideEffects 屬性告知 Webpack 哪些文件是"純淨"的(無副作用),可以安全移除。這能防止具有副作用的文件(如全局樣式表、polyfill)被誤刪 。
// package.json
{
  "sideEffects": false, // 表示整個項目都沒有副作用
  // 或明確指定有副作用的文件
  "sideEffects": [
    "**/*.css",
    "./src/polyfill.js"
  ]
}

有同學又會問了,搖樹搖的不是 js 嗎,樣式表 css 怎麼會被搖掉呢?

其實,這裏指的是導入的但是沒有明確導出的 css 樣式表,導入導出是明確的 js 語句,css 是“副作用”,比如:

  • 僅導入但未使用任何導出(如 import './style.css'),屬於是無形的“使用”,可能被誤刪
  • 使用 CSS Modules(如 import styles from './Component.module.css'),被視為有被使用的對象(如 styles.className),通常不會被誤刪

這些問題你遇到過嗎?

開發過程中,以下情況仍可能導致 Tree Shaking 失效,看看你有沒有遇到過:

  • Babel 配置不當​:Babel 預設 @babel/preset-env 可能會將 ESM 轉換為 CommonJS。務必確保其 modules 選項設置為 false,只有 ESM 可以搖樹。
// .babelrc
{
  "presets": [["@babel/preset-env", { "modules": false }]]
}
  • 第三方庫的模塊版本​:優先選擇提供 ES6 模塊版本的庫(如使用 lodash-es 而非 lodash),並採用按需導入的方式 。
// 推薦:按需導入
import { debounce } from 'lodash-es';
// 不推薦:整體導入
import _ from 'lodash';
  • 導出粒度太粗​:儘量使用具名導出而非默認導出對象,有助於進行更精細的分析 。
// 推薦:細粒度導出
export function func1() {}
export function func2() {}

// 謹慎使用:粗粒度導出(不利於分析內部未使用屬性)
export default { func1, func2 };