深入 Webpack 快取機制
如果你知道 Webpack 如何打包,也知道如何分散打包,那麼你一定要怎樣的打包, 在正式環境下的熱修復(Hotfix)讓改變可以縮到最小。
本篇文章基於
- Webpack 版本 4 以上
範例打包切分 (Bundle chunk) 檔案
main.js
(所有專案的原始碼)vendor.js
(node_modules
裡面所有第三方套件)runtime.js
(Webpack runtime)
情境
今天你的產品已經上了正式環境,有人回報說有部分有問題,這時候你需要修復自己的程式,程式碼改完確認沒有問題後,需要把你修改過的程式上到正式機,這時候你該上的程式是哪隻呢?
一般來說我們都會覺得是
main.js
這隻對吧?
到底是不是,讓我們來一探究竟吧!
測試方法
先講如何測試
要怎麼測試呢?每次打包都比較前後檔案內容?這樣比較起來太花時間了,我們有更聰明的方法,使用 Webpack 內建的「contenthash」機制
什麼是 contenthash 機制?
這是一個根據「文件內容」所產生的的雜湊(hash),所以只要文件內容一樣,這個雜湊就會一模一樣。
調整 Webpack 輸出
在 Webpack 輸出的設定,可以設定輸出的檔案名稱,我們改為使用 [name]-[contenthash].js
這麼一來我們只要看檔案名稱,就可以知道內容是不是有差異了!
測試專案
基於 React,我們只用一個簡單的 Hello World 做為範例,先做一次打包輸出,這個為「Before」版本。
Test Project
├─dist
├─src
│ ├─index.js
│ └─App.js
├─.babelrc
├─webpack.config.js
└─package.json
增加改變因素「 Test.js」
這時我們在 ./src
中增加一個 Test.js
,並且讓 App.js
引用 Test.js
並增加 render Test.js
的內容,再做一次打包輸出,這次為「After」 版本。
Test Project
├─dist
├─src
│ ├─index.js
│ ├─Test.js
│ └─App.js
├─.babelrc
├─webpack.config.js
└─package.json
這樣的情境就等於我們只改了自己的程式,並沒有動 library 內容,所以我們預期 vendor 不會改變,只有 main 會改變。
測試結果
main.js
內容有變是我們所預期的
但
vendor.js
內容卻改變了
這點跟我們預期的結果不同,這樣改程式不就連 vendor.js
也要一起上嗎?在正式機環境的修改能夠越少越好,所以我們要來探究為什麼 vendor.js
會改變?
為什麼 vendor.js 會改變?
JavaScript 的程式碼有「執行先後順序」的問題,舉個例子 🌰:「如果 A 相依 B,那麼 A 就要放在 B 的後面,這樣程式執行起來才不會有錯」
而 Webpack 打包會幫你處理所有相依性 (Dependency) 的問題
因為 runtime.js
其實是記錄著對應所有模(Module)與塊 (Chunk)的關係,而有影響的就是 Module Id 與 Chunk Id,而 Webpack 在預設的情況下並不會幫你固定這些 Id,導致在每次打包輸出的時候,這些 Id 都會有變化,導致雖然你只改了你自己的程式碼,但打包的結果卻是 Vendor 的內容也有變化。
所以我們要做的是「固定這些改變因素」
固定 Module Id 與 Chunk Id
解決:固定 Module Id 方法
官方使用 NamedModulesPlugin
來解決這個問題,讓我們來試試!
修改 webpack.config.js
加上 NamedModulesPlugin
官方文件:
用一樣的情境再測試一次
跟我們預期的結果相同vendor.js
的內容一模一樣,這麼一來就固定內容囉!
注意
在開發版本使用 NamedModulesPlugin
,在生產版本使用HashedModuleIdsPlugin
兩者最大的差異在於 HashedModuleIdsPlugin
會優化所產出的打包大小。
如果你使用 Webpack 4 開始的
mode
設定,mode: development
自動會加上NamedModulesPlugin
,但是mode: production
並不會自動使用HashedModuleIdsPlugin
,請務必特別注意。
如果你有切分多個塊(Chunk)
另一個影響因素 Chunk Id,什麼時候 Chunk Id 會改變?
- 增加進入點(Entry)
- 增加第三方模組
- 使用非同步載入(Dynamic Import)功能
通常在熱修復(Hotfix)的時候,前兩種可能性比較低,會發生 Chunk Id 變化的應該都是在程式碼有設定 Dynamic Import 的情境上。
解決方法
原本的 Chunk Id 是每個進入點(Entry)都會有自己的 Chunk Id ,這也是為什麼非同步載入(Dynamic Import)會有可能造成內容不同,使用NamedChunksPlugin
讓 Chunk Id 使用「Chunk 相對路徑對應出的字串」,就可以把 Chunk Id 固定下來。
export default = {
...webpackSettings,
plugins: [
...plugins,
new webpack.NamedChunksPlugin(),
]
}
如果你使用 Webpack 4 開始的
mode
設定,mode: development
自動會加上NamedChunksPlugin
,但是mode: production
並不會自動使用,請務必特別注意。
結語
Webpack 的快取機制真的很多小細節,在這麼多設定的狀況下要把文件寫得好也是挺困難的,所以才會有很多人說 Webpack 文件很爛,而且 Webpack 4 才把一些預設行為納入設定,是比較慢了一點。
另外這樣的快取機制取決於團隊與公司產品架構,不是一定要這樣做,這也是為什麼 Webpack 的 mode
設定會在於 development
版本才固定 Module Id 與 Chunk Id,反而 production
版本不使用這樣的設定,就讓各位自由發揮囉!
相關參考資料
Hi 我是 Ryan,如果覺得這篇文章不錯,請你不吝嗇的給予我「拍手👏」並分享給朋友
或是直接「Follw👀」我,就不會錯過任何新的文章
如果有任何想法也歡迎直接「留言🗣」與我交流,那將是我進步的動力!
你也可以在 LinkedIn 找到我 ↓ ↓ ↓