webpack 背後靈魂 (5) - 優化 optimization
使用過框架建置專案的話,一定都知道開發要 npm run dev
,最後編譯要 npm run serve
,但你知道 webpack 依據這兩個指令個別做了什麼事嗎。
仔細想一想,開發時當然是希望可以達到編譯速度快、容易 debug 這幾件事。而最後的專案打包,則會朝向各瀏覽器兼容、程式碼壓縮這些目標去編譯。
前幾回只有提到簡單的設置 production mode 來打包,寫幾行簡單的 code 的話是很快速方便,但隨著專案越大我們也必須做更多的配置,來讓專案朝向高效能的目標邁進。
目錄
- module、chunk 與 bundle
- CSS 優化
- OptimizeCssAssetsPlugin
- terser-webpack-plugin - JS 壓縮
- terser-webpack-plugin - JS 優化
- splitChunks
- ContextReplacementPlugin
module、chunk 與 bundle
在優化 webpack 時,會時不時看到 chunk
這單字,丟 google 只會得到「塊」…究竟是啥也有看沒懂。
以一句話總結的話:
module 是專案內寫的每隻檔案,經由 webpack 編譯時叫做 chunk,
bundle 則是打包出來的檔案。
正常來說 chunk 跟 bundle 會呈現 1 : 1,但根據不同配置比例也會不一樣。
我們拿上回的例子來說:
webpack-demo
|- /src
|- index.js
|- utils.js
|- about.js
|- /css
|- index.css
|- /assets
|- demo.png
|- index.html# JS x 3, CSS x 1, html x 1, png x 1
三支 JS 內容個別為:
注意 index.js
內引入了 index.css
及 about.js
。
webpack.config.js
內配置了兩個 entry,並把 CSS 抽出單一檔案:
run 過一次編譯後,可以看看終端機顯示了什麼:
黃色螢光區塊標示了我們四個檔案的大小及 chunk,依據 chunk 與打包出來的資料夾,我們可以拆成下圖:
- 四支檔案代表了「 4 個 module 」
- entry 設置了兩個所以是「 2 個 chunk 」
- 最後我們透過 mini-css-extract-plugin 抽出 CSS,所以輸出「 3 個 bundle 」
如果有設置 sourceMap 的話,那麼 bundle 數量就會變為兩倍。
CSS 優化
先前有提到使用 mini-css-extract-plugin 抽離 CSS 檔,回顧一下上回的圖:
當 CSS 使用 style-loader 的話,會被編譯在 JS 裡一起壓縮。一但抽離 CSS 檔後,因為吃不到 JS 的壓縮,就會保持跟原本一樣的排版。也因此我們需要另外安裝壓縮 CSS 的 plugin。
OptimizeCssAssetsPlugin
專門壓縮 CSS 用的 plugin,安裝:
$ npm i -D optimize-css-assets-webpack-plugin
並且在 webpack.config.js
內的 optimization
設置 minimizer
:
再重新編譯一次可以發現 CSS 的空格都去掉了。
但你可能會發現一個問題:
原本
minimize
默認 production 模式下是true
,也就是會壓縮 JS 的意思。而我們透過自定義設置minimizer
, webpack 會把壓縮都交給你處理,因此 JS 會變回沒有壓縮的狀態,這時候需要再補上一個專門壓縮 JS 的 plugin。
JavaScript 壓縮
terser-webpack-plugin
還有一個也是壓縮 JS 的叫做 uglifyjs-webpack-plugin,但沒有支援 ES6 語法,都講這麼明了應該懂吧!
安裝:
$ npm i -D terser-webpack-plugin
把 minimizer
的地方改成以下就同時兼容 JS 和 CSS 的壓縮了。
JavaScript 優化
上面提到了 JS 壓縮,也就是把一些空格和排版都去掉最小化,但除了最小化,還會遇到有的沒的問題。
先來模擬專案載個套件來用用,例如常被人詬病太大包的 moment.js。
$ npm i moment --save
在 index.js 以及 utils.js 內都引入一次:
接著載入可以秀出編譯過程可視圖的 webpack-bundle-analyzer:
$ npm install --save-dev webpack-bundle-analyzer
在 webpack.config.js
引入:
Run 一次編譯,會跑出以下的編譯圖:
會發現在不同檔案引入,webpack 竟然編譯兩次 moment 進去,光是在每個檔案內就佔了 659 KB,即使我只寫了一兩行。可以想像如果寫了 10 支檔案都有引入會多麼壯觀(望
但… webpack 會允許這種事發生嗎!
splitChunks
可以透過以下的參數設定,把專案的 chunks 切成你的形狀。如使用 webpack 3 則須參考 CommonsChunkPlugin。
接著透過以下設定:
可以把 node_modules
資料夾內的插件都拉出來!是不是瞬間清爽多了。當然也可以透過更改 test
內的正則,把想要打包的拉成一支檔案。
ContextReplacementPlugin
上面的 moment 雖然拉出來了,但看看那些語言,實際上我們可能只用到 en
或 zh-tw
,卻有其他一堆來蹭飯的(?
這時候可以再加入以下設定,意思是指定 moment/locale
下的文件名稱必須符合 zh-tw
或 en
。
編譯後整包 moment 從 659KB 來到了 193KB!
關於 ContextReplacementPlugin
目前看到的都是關於 moment 的例子,如果有套用其它套件的文章的話可以一起交流~
其他像是 lodash 的話,可以透過 ESM 動態引入方式單獨引入想用的,就不用擔心整包會被編譯進去。
import { debounce } from 'lodash-es'