作者: 神Q超人
譯者:前端小智
來源:medium
有夢想,有乾貨,微信搜尋 【大遷世界】 關注這個在凌晨還在刷碗的刷碗智。
本文 GitHub https://github.com/qq449245884/xiaozhi 已收錄,有一線大廠面試完整考點、資料以及我的系列文章。
前陣子在和朋友聊 Webpack 的時候,突然提到 Tree Shaking,但很慚愧的是我沒有辦法好好說明 Webpack 是如何做到 Tree Shaking 的,因此就趁這個年假的第一天抽空讀 Webpack 的檔案,然後把理解到的心得寫下來,如果你也有興趣,就一起看下去吧 ?。
Tree Shaking 是什麼
Tree Shaking 是個優化的方式,在 JavaScript 中用來表示移除沒用的程式碼的一個常見術語,之所以叫做 Tree Shaking 的由來似乎是指說“當你大力搖晃一棵樹的時候,樹上就只會留著綠色的葉子,其他枯葉都會落到地上”,而那些綠色的葉子就是打包過後的檔案中,真正有用到的程式碼。
在使用時要注意的是,Tree Shaking 只能夠使用在 static structure
(例如:import
和 export
上),像是 dynamic structure
的 require
就沒辦法被偵測到。舉例來說,import
要載入某個 module
使用的話就一定要在檔案的最上方,但 require
可以在任何地方使用,例如以下場景就必須要等到 runtime
才會知道 module
是什麼:
let module = null;
if (Math.random() * 10 > 5) {
module = require('module1');
} else {
module = require('moudle2');
}
那開始瞭解 Tree Shaking 的工作前,應該會有些人好奇,就算自己從來就沒有特別在 Webpack 中設定 Tree Shaking,但是沒有用的程式碼也都會被移除呀!
那是因為 Tree Shaking 的執行需要 ModuleConcatenationPlugin(圖一),而 Webpack 裡另外有個 mode
,如果你一直沒有特別去設定 mode
的值,那 mode
就預設會是 production
(圖二),然後 production
的預設選項中就會開啟 ModuleConcatenationPlugin
(也是圖二),因此平常不會特別注意到也不奇怪,因為 Webpack 都幫你做好了。
Tree Shaking 的運作
因為 Production 會幫你開啟 ModuleConcatenationPlugin ,所以待會我們實驗的時候,要把 mode 改成 none
(Webpack 檔案說 none
為關掉所有優化設定的模式)。
這邊會附上簡單的 初始化示例配置,有興趣的話,可以把它 clone 下來一起玩看看。
首先在 src
下建立一個 math.js
和 string.js
,接著個別寫下一個方法做 export
,分別是 add
和 composeString
:
const add = (a, b) => a + b;
export default { add };
const composeString = (a, b) => `${a} ${b}`;
export default { composeString };
開啟 src
下的 index.js
,把 add
和 composeString
都 import
,但只使用 add
方法:
import { add } from './math';
import { addString } from './string';
console.log(add(1, 2))
最後到 terminal
中執行 npm run build
或是 webpack
做打包,打包結束後,會發現雖然我們只有 import add
做使用,但是打包後的檔案內容還是會有 composeString
:
不過這很正常,畢竟我們還沒有做任何處理,Webpack 在打包時也不曉得你哪些程式碼到底有沒有用到,就沒辦法幫你把 composeString
移除。
那麼到底什麼樣的程式碼是有用的,怎樣是沒用的呢??
- 最明顯的定義應該是,如果有被執行就代表有用到。像是上面例子的
add
一樣。 - 有
side effect
的程式碼也是被用到的。像是上方的index.js
,看起來什麼方法都沒有提供,但是執行時卻會在console
中留下log
,除此之外,會改變執行環境的polyfill
也是有side effect
的library
。
第一種情況相對容易分辨,但如果是第二種情況的話,可以選擇用 Webpack 中的 sideEffects
屬性來設定。
sideEffects
sideEffects
可以被設定為 Boolean
或是 Array
,當你把它設定為 false
的時候,代表該專案是不會有 sideEffects
的,也就是一律用 export
判斷是否使用。另外 sideEffects
會依賴 providedExports
,用來找出專案中所有 export
的 module
:
以下是 sideEffects 的使用方式:
{
"name": "tree-shaking",
"sideEffects": false,
"version": "1.0.0",
...
}
只要在 package.json
中加上 sideEffects
,並且將值設定為 flase
,就代表該專案內所有的程式碼都沒有 side effect
,因此 Webpack 在打包的時候,就可以把沒有用到的 export
程式碼給移除。
加上 sideEffects
後打包,就不會看到 composeString
在結果裡了:
那現在我們再到 src
中建立另一個 polyfill.js
,在 ployfill.js
裡為 Array 建立自定義的方法,再把它 import
到 index.js
中:
index.js
import './polyfill';
import { add } from './math';
import { addString } from './string';
console.log([].customMethod());
polyfill.js
Array.prototype.customMethod = () => {
console.log('customMethods');
};
如果我們去打包上方的程式碼,polyfill.js
會因為沒有任何 export
,所以不會被 providedExports
抓到,也就不會被打包到 Production
,這會導致專案如果有使用到 Array 的 customMethod
,在執行時就會出錯。面對這種情況,就必須要在 sideEffects 屬性中告知,polyfill.js
是有 side effect 的。設定方法如下:
{
"name": "tree-shaking",
"sideEffects": ["./src/polyfill.js"],
"version": "1.0.0",
...
}
如此一來,polyfill.js
就會直接被打包了:
最後要注意兩件事情:
- 如果各位的專案中也有
import.css
樣式來用的話,也記得要將.css
結尾的檔名放到 sideEffects,例如sideEffects: ["*.css"]
。 - 在
webpack.config.js
裡的optimization
也有sideEffects
,但在這裡設定的值是針對node_modules
中的。
useExported
useExported
的作用和 sideEffects
都是用來判斷是否該移除程式碼,但根據 Webpack 檔案內的說明,useExported
才是真正的 Tree Shaking:
usedExports
會使用 terser 判斷程式碼有沒有 side effect
,如果沒有用到,又沒有 side effect
的話,就會在打包時替它標記上 unused harmony
,並在 minify
(用 Uglifyjs 或其他工具)的時候移除。
在測試 usedExports
之前,先到 math.js
裡加入 square
並 export
:
const add = (a, b) => a + b;
const square = (a, b) => a * b;
export { add, square };
接下來到 webpack.config.js
中加入 optimization.usedExports
:
module.exports = {
...
optimization: {
usedExports: true,
}
};
然後對專案進行打包,就會發現僅僅是 export
,但沒有使用的 square
會被標記上 unused harmony export
:
接著我們使用 uglifyjs-webpack-plugin,把沒有用到的 square
從樹上搖晃下來:
npm install -d uglifyjs-webpack-plugin
webpack.config.js
的設定如下:
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
...
optimization: {
usedExports: true,
minimize: true,
minimizer: [
new UglifyJsPlugin({
uglifyOptions: {
compress: { unused: true },
mangle: false,
output: {
beautify: true
}
},
})
],
}
};
設定完 minimizer
後,再打包一次,就能看見 square
已經被移除了:
usedExports
與 sideEffects
不同的是,usedExports
可以以陳述句為單位去判斷是否有 side effect
,但是 sideEffects 可以讓 Webpack 在打包的時候,直接略過一整個檔案,只要是出現在 sideEffect
裡的檔案就是直接打包,也不用透過 terser
評估副作用。
總結
- Tree Shaking 只能在
static structure
使用,如果專案中的babel
會將static structure
編譯成dynamic structure
的話,要另外設定。 - 使用 sideEffects 時,要寫在
package.json
,如果是要對第三方函式庫優化,要寫在webpack.config.js
裡的optimization
。 usedExports
才是 Tree Shacking,使用時會自動判斷沒使用的程式碼,並標記unused harmony
的註解,要移除的話要另外使用minify
。
程式碼部署後可能存在的BUG沒法實時知道,事後為了解決這些BUG,花了大量的時間進行log 除錯,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug。
原文:
https://medium.com/starbugs/%...
交流
有夢想,有乾貨,微信搜尋 【大遷世界】 關注這個在凌晨還在刷碗的刷碗智。
本文 GitHub https://github.com/qq44924588... 已收錄,有一線大廠面試完整考點、資料以及我的系列文章。