深入 Webpack 快取機制

Ryan Hsu
Its Ok to Make Mistakes
7 min readMay 21, 2019

如果你知道 Webpack 如何打包,也知道如何分散打包,那麼你一定要怎樣的打包, 在正式環境下的熱修復(Hotfix)讓改變可以縮到最小。

本篇文章基於

  • Webpack 版本 4 以上

範例打包切分 (Bundle chunk) 檔案

  • main.js (所有專案的原始碼)
  • vendor.jsnode_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 找到我 ↓ ↓ ↓

--

--