
這篇文章是啟發於 JSDC 2025 SerKo 大大分享的 「程式碼減重:你的 Tree-Shaking 在偷懶嗎?」,會從 SerKo 分享的內容為起點,補充一些自己本來也是很模糊的地方,以及透過一些實驗來觀察 Tree Shaking 的狀況。
什麼是 Tree Shaking?
從字面上來看就是「搖樹」的意思,但是為什麼要「搖樹」呢?目的就是把樹木上沒用的枯葉搖掉,只保留還有用途的綠葉。延伸到前端開發領域中的話,也就是「在打包的時候,把沒有用到的程式碼移除掉,只保留需要的內容,以縮減打包後檔案的大小,也就不會讓使用者在使用網頁時,因為下載一些不必要的內容,而影響使用體驗。Tree Saking 這個動作,通常都是透過 Webpack、 Rollup 這類的打包工具 (bundler) 來處理。
打包工具怎麼做到 Tree Shaking?
了解完 Tree Shaking 的用途後,我們也來進一步看看打包工具怎麼做到 Tree Shaking 這件事情。
首先,打包工具為避免不小心搖掉不該搖的東西,會以最保守方向來進行 Tree Shaking。也就是說,只要被打包工具認為某些部分「有出現些微可能被使用到徵兆」,就不會被 tree shaking 掉。
在進行 Tree Shaking 時,打包工具會去做靜態分析的動作。所謂的「靜態分析」白話一點來理解的話,也就是打包工具會將所有檔案和關聯檔案 (例如: 一個檔案內有 import 哪些東西進來) 之間的關係分析出來,就像是一個樹狀結構一樣,一個檔案的內容可能會有一個分支連結到另一個檔案。當這樣的靜態分析完成後,打包工具在實際上進行檔案的打包時,也就可以知道哪些變數、函式實際上並沒有在整個完整個關係圖裡面,也就可以將沒有關聯的內容給移除掉,不放在打包後的檔案內。
沒用到一定會被 Tree Shaking 掉?
延續前面說的打包工具怎麼做到 Tree Shaking 的部分,是否就可以推論為「沒用到的部分,一定會被 Tree Shaking 掉」呢?其實答案是否定的,這裡可以分為兩個大部分來觀察。
第一個部分、JavaScript 的模組化系統特性
JavaScript 有兩種模組化系統,分別是 CommonJS 和 ES Module,由於兩種模組化系統的特性不同,CommonJS 是動態分析,ES Module 是靜態分析,所以 Tree Shaking 的效果有差異。
這裡直接透過實際的例子來觀察這兩種模組化系統在 Tree Shaking 上的效果,主要會是 JavaScript 搭配 Webpack 這個打包工具來觀察結果。
CommonJS
這是要使用的一些函式
// ✅ 實際會用到的小函式
function add(a, b) {
return a + b;
}
// 🚫 不會用到的函式
function random() {
return Math.random();
}
module.exports = { add, random };
這是實際使用的地方
const { add } = require("./utils.cjs");
console.log(add(1, 2));
雖然 random 實際上沒有被使用到,但是打包後的結果仍然可以觀察到 random 的程式碼存在於打包後的檔案內。
這是因為 CommonJS 的 require() 是在 runtime 進行載入的動作,沒辦法在打包的時候就透過靜態分析明確地知道哪些內容不會被使用到,所以在 CommonJS 的情境下,通常無法做到精準移除未使用的部分,導致未使用的函式仍可能被打包進輸出檔案中。
接著來看 ES Module 的情境。
一樣先準備要使用的函式,其中一個函式不會使用到。
// ✅ 實際會用到的小函式
export function add(a, b) {
console.log("addddddd"); return a + b;
}
// 🚫 不會用到的函式
export function random() {
console.log("ramdooooooo");
return Math.random();
}
這是實際使用的地方
import { add } from "./utils.js";
console.log(add(1, 2));
在 ES Module 中,觀察打包後產生的檔案,可以發現到只有出現 add 這個 function,也就是説在這個情境下,有確實地進行 Tree Shaking。

在 ES Module 中,模組的 import / export 結構在build 的階段可以進行靜態分析,因此打包工具可以在打包時明確判斷哪些內容的匯出有被使用、哪些沒有被使用,因此可以更精準地進行 Tree Shaking,移除未使用的程式碼。
由這個情境我們可以觀察到 ES Module 的確比起 CommonJS 能可以更精準地進行 Tree Shaking,但是事實上只要使用 ES Module 就可以百分之百做到完整的 Tree Shaking 嗎?
再來看另一種寫法,這裡改用 default export 的寫法匯出。
// ✅ 實際會用到的小函式
function add(a, b) {
console.log("addddddd"); return a + b;
}
// 🚫 不會用到的函式
function random() {
console.log("ramdooooooo");
return Math.random();
}
export default { add, random };
因為不是使用 named export 的寫法了,所以只能改用以下的寫法 import 和使用。
import utils from "./utils.js";
console.log(utils.add(1, 2));
這樣改寫後,再次進行打包看看,可以發現到 random 這個函式沒有被移除,Tree Shaking 失效了!

會有這樣的結果是因為對打包工具來說,等於這個檔案匯入的是 default export 的整個值,而這個值是一個物件,裡面同時包含 add 和 random,因此沒有辦法保證沒有使用到 random ,也就無法安全地把 random 從這個物件移掉。
從目前的觀察的情境可以了解到雖然 ES Module 相較於 CommonJS 能更準確地進行 Tree Shaking,但是還是有可能因為寫法而影響到 Tree Shaking 的準確性。
第二個部分、Side Effect 的影響
除了使用的模組化系統和匯出匯入的寫法會影響到 Tree Shaking 的結果外,Side Effect 也有可能會讓 Tree Shaking 沒有正確地發揮作用。常見會產生 Side Effect 的狀況有寫 console.log、setTimeout/setInterval、throw error 、修改 DOM 等。
這邊也直接透過實際的例子來觀察看看。
我們在原本的 utils 檔案裡建立一個 getValue 的函式,接著將執行這個函式取得的值也建立成一個變數並且匯出。
// 新增一個取得 value 的函式
function getValue(value) {
return value;
}
export const value = getValue(value);
因為沒有使用到這個匯出的 value,最後 build 出來的確沒有出現 getValue 這個函式。

但如果在函式中出現一些會產生 Side Effect 的內容後,會發生什麼事呢?
這裡在函式加上 throw error 的內容。
// 新增一個取得 value 的函式
function getValue(value) {
if (!value) {
throw new Error("key is required");
}
return value;
}
export const value = getValue(value);
再次 build 之後,會發現這次出現了 getValue 這個函式。
在這個情境下,Tree Shaking 失敗的原因主要就是因為函式內出現了會有 Side Effect 的內容,白話一點也就是出現了會影響外部的內容。再加上這個函式,會在頂層就被執行。也就是說這個函式當整個 utils.js 被執行時,也就會執行到 getValue。
打包工具會以最保險的方式進行 Tree Shaking,所以打包工具會在確保不影響外部狀況、行為的前提下,將不需要的程式碼刪除。當出現會產生 Side Effect 的程式碼,且這段程式碼會在一開始就被執行時,打包工具為了避免影響到其他地方,就不會將其刪除,以避免影響到其他地方。所以並不是使用了 ES Module,且某段程式碼肉眼看到沒有用到,就一定會被打包工具給刪除,還是得注意這段程式碼是否有 Side Effect 的內容。
避開 Tree shaking 失敗的做法
最後也來總結一下避開 Tree Shaking 失敗的方式有哪些:
第一點是盡可能地使用 ES Module 這個模組化系統,並且若是在一個檔案中,有複雜的邏輯及程式碼時,盡可能地避免用 default export 的方式進行匯出。
第二點是避免讓有 Side Effect 的程式碼在最頂層就被執行,這樣會影響打包工具進行 Tree Shaking 的判斷,導致沒有被使用的程式碼還是被保留了下來。
不論是哪一點,核心重點都在於不要讓打包工具有難以判斷刪除時,是否會影響外部的模糊視角。
總結
雖然大多數時候打包工具的確會幫我們做到 Tree Shaking 的動作,但實際上卻有可能出現沒有確實做到 Tree Shaking 的狀況,特別是程式碼中出現 Side Effect 的時候。所以我們都應該要有這個意識去盡量避免寫出會造成打包工具混淆寫法,以避免打包工具因為以最保險的方式進行 Tree Shaking,而讓打包工具沒能完整地進行 Tree Shaking。
