Tree Shaking 具體做了什麼?
前言
Javascript 絕大多數情況需要通過網路進行載入再執行,載入的檔案越小,整體執行時間更短,所以就有了 Tree Shaking 去除無用程式碼,從而減小檔案體積。
什麼是 Tree Shaking
Tree-shaking(搖樹) 是一個術語,通常指通過打包工具"搖"我們的程式碼,將未引用程式碼 (Dead Code) "搖" 掉。在 Webpack 專案中,有一個入口檔案,相當於一棵樹的主幹,入口檔案有很多依賴的模組,相當於樹枝,雖然依賴了某些模組,但其實只使用其中的某些方法,通過 Tree Shaking,將沒有使用的方法搖掉,這樣來達到刪除無用程式碼的目的。
Tree Shaking 具體做了什麼
我們通過例子來詳細瞭解一下 Webpack 中 Tree Shaking 到底做了什麼
-
未使用的函式消除
// utils.js
export function sum(x, y) {
return x + y;
}
export function sub(x, y) {
return x - y;
}
// index.js
import { sum } from "./utils";
// import * as math from "./utils";
console.log(sum(1, 2));
我們在 utils 中定義了 sum 與 sub 兩個方法, 僅使用了 sum 方法,而 sub 方法並沒有被使用。我們一起看一下打包後的結果
(()=>{"use strict";console.log(3)})();
-
未使用的 JSON 資料消除
// main.json
{
"a": "a",
"b": "b"
}
// index.js
import main from "./main.json";
console.log(main.a);
可以看到僅使用了 JSON 中的 a。我們一起看一下打包後的結果
(()=>{"use strict";console.log("a")})();
Tree Shaking 的原理
Tree Shaking 在去除程式碼冗餘的過程中,程式會從入口檔案出發,掃描所有的模組依賴,以及模組的子依賴,然後將它們連結起來形成一個 “抽象語法樹” (AST)。隨後,執行所有程式碼,檢視哪些程式碼是用到過的,做好標記。最後,再將“抽象語法樹”中沒有用到的程式碼“搖落”。經歷這樣一個過程後,就去除了沒有用到的程式碼。
前提是模組必須採用 ES6 Module 語法,因為 Tree Shaking 依賴 ES6 的靜態語法:import 和 export。不同於 ES6 Module,CommonJS 支援動態載入模組,在載入前是無法確定模組是否有被呼叫,所以並不支援 Tree Shaking 。如果專案中使用了 babel 的話, @babel/preset-env
預設將模組轉換成 CommonJs 語法,因此需要設定 module:false
。
CommonJS 與 ES6 Module 模組的依賴的區別在於,CommonJS 是 動態的 ,ES6 Module 是 靜態的 。
CommonJS 匯入時, require
的路徑引數是支援表示式的,路徑在程式碼執行時是可以動態改變的,所以如果在程式碼編譯階段就建立各個模組的依賴關係,那麼一定是不準確的,只有在程式碼運行了以後,才可以真正確認模組的依賴關係,因此說 CommonJS 是動態的。
ES6 模組不是物件,它的對外介面只是一種靜態定義,在程式碼編譯,靜態解析階段就會生成,這樣我們就可以使用各種工具對 JS 模組進行依賴分析,優化程式碼。
Development 模式下
// webpack.config.js
module.exports = {
// mode: "production",
mode: "development",
devtool: false,
optimization: {
usedExports: true, // 目的使未被使用的 export 被標記出來
},
};
打包後的 bundle.js
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "sum": () => (/* binding */ sum)
/* harmony export */ });
/* unused harmony export sub */
function sum(x, y) {
return x + y;
}
function sub(x, y) {
return x - y;
}
1、可以看到未被使用的 sub 會被標記為 /* unused harmony export sub */
,不會被 __webpack_require__.d
進行 exports 繫結;
關於 __webpack_require__.d
的含義,可參考 [深入瞭解 webpack 模組載入原理] http://segmentfault.com/a/1190000024457777 一文。
2、經過壓縮工具(UglifyJSPlugin)壓縮後,未使用的介面程式碼會被刪除。原理顯而易見,未被 __webpack_require__.d
引用,所以壓縮工具可以將其安全移除。
Production 模式下
由前面的例子可以看出 dist/bundle.js
中整個 bundle 都已經被 壓縮工具 壓縮和混淆破壞,但是如果仔細觀察,則不會看到引 sub
函式,但能看到 sum
函式的混淆破壞版本( function r(e){return e*e*e}n.a=r
)。
再看一下兩次打包的檔案體積會發現,bundle 的體積明顯減少了。
Tree Shaking 和 sideEffects
提到 Tree Shaking 就要聊一下 sideEffects。什麼是 sideEffects ,sideEffects 又是與 Tree Shaking 如何搭配使用的?
sideEffect(副作用) 的定義是,在匯入時會執行特殊行為的程式碼,而不是僅僅暴露一個 export 或多個 export。
webpack v4 開始新增了一個 sideEffects
特性,通過給 package.json
加入 sideEffects: false
宣告該包模組是否包含副作用,從而可以為 Tree Shaking 提供更大的優化空間。
舉例說明
// a.js
// 無副作用,僅僅是單純的 export
function a () {
console.log('a')
}
export default {
a
}
// b.js
function b () {
console.log('b')
}
// 執行了特殊行為
Array.prototype.fun = () => {}
export default {
b
}
如果 a 在 import 後未使用,Tree Shaking 完全可以將其優化掉;但是 b 在 import 後未使用,但因為存在他還執行了為陣列原型添加了方法,副作用還是會被保留下來。這時就需要使用 sideEffects: false
,可以強制標識該包模組不存在副作用,那麼不管它是否真的有副作用,只要它沒有被引用到,整個 模組/包 都會被完整的移除。
如果你的專案中存在一些副作用程式碼 b 需要被保留下來,比如 polyfill、css、scss、less 等,可以按下面方法一樣配置;保證必要的程式碼不被 Tree Shaking
// package.json
{
"name": "your-project",
"sideEffects": ["./src/b.js", "*.css"]
}
總結
通過以上講解,使 Webpack 更精確地檢測無效程式碼,完成 Tree Shaking 操作,需要符合以下條件:
-
使用 ES6 Module 語法(即
import
和export
)。 -
確保沒有
@babel/preset-env
等工具將 ES6Module 語法轉換為 CommonJS 模組。 -
optimization: { minimize: true, usedExports: true }
。 -
使用支援 Tree Shaking 的包。
參考連結
Tree shaking 原理及應用 (http://segmentfault.com/a/1190000040037144)
Tree Shaking (http://webpack.docschina.org/guides/tree-shaking/)
- 追求極致效能的Qwik
- Tree Shaking 具體做了什麼?
- 用了模板字面量型別,同事直呼太強了!
- TS 4.7 版本新特性,讓 infer 更簡單
- type 和 interface 傻傻分不清楚?
- 用了 TS 條件型別,同事直呼 YYDS!
- TypeScript 中的型別到底是個啥?
- 要寫出好程式碼,你需要懂點兒“底層思維”
- 硬核解析 Webpack 事件流核心!
- 回望 2021 年大前端技術,未來可期!
- 如何為 Node.js 的 require 函式新增鉤子?
- 如何學好 Electron 技術?
- 目標位元組,HTML、CSS、JS 誰更好上手?
- 你不知道的 TypeScript 泛型
- 一文徹底讀懂ESLint
- 這 10 道 TS 練習題,你能答對幾道?
- Emoji 表情還能這樣玩?
- Chrome 93 帶來了哪些有意思的新特性?
- Promise 竟被他玩出了四十八種花樣
- 這個 19.4K 的 canvas 庫,功能很強大!