webpack 背後靈魂 (5) - 優化 optimization

集點送紅利 / Hiro
UNNO Technology
Published in
8 min readJul 27, 2020

上回文章:webpack 背後靈魂 (4) — 相容工具

使用過框架建置專案的話,一定都知道開發要 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.cssabout.js

webpack.config.js 內配置了兩個 entry,並把 CSS 抽出單一檔案:

run 過一次編譯後,可以看看終端機顯示了什麼:

黃色螢光區塊標示了我們四個檔案的大小及 chunk,依據 chunk 與打包出來的資料夾,我們可以拆成下圖:

module, chunk & bundle
  • 四支檔案代表了「 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 的空格都去掉了。

OptimizeCssAssetsPlugin

但你可能會發現一個問題:

原本 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 內都引入一次:

引入 moment

接著載入可以秀出編譯過程可視圖的 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

splitChunks 參數說明

接著透過以下設定:

可以把 node_modules 資料夾內的插件都拉出來!是不是瞬間清爽多了。當然也可以透過更改 test 內的正則,把想要打包的拉成一支檔案。

ContextReplacementPlugin

上面的 moment 雖然拉出來了,但看看那些語言,實際上我們可能只用到 enzh-tw ,卻有其他一堆來蹭飯的(?

這時候可以再加入以下設定,意思是指定 moment/locale 下的文件名稱必須符合 zh-twen

編譯後整包 moment 從 659KB 來到了 193KB!

關於 ContextReplacementPlugin
目前看到的都是關於 moment 的例子,如果有套用其它套件的文章的話可以一起交流~

其他像是 lodash 的話,可以透過 ESM 動態引入方式單獨引入想用的,就不用擔心整包會被編譯進去。

import { debounce } from 'lodash-es'

參考資料

--

--