Concepts - Bundle vs Chunk
經常讓人摸不着頭腦的是 chunk 和 bundle 這兩個概念。chunk,翻譯過來就是大塊,也就是代碼塊;而 bundle 則是束,包的意思。從 webpack 給出的術語表中是這麼解釋的:
Chunk: This webpack-specific term is used internally to manage the bundling process. Bundles are composed out of chunks, of which there are several types (e.g. entry and child). Typically, chunks directly correspond with the output bundles however, there are some configurations that don't yield a one-to-one relationship. chunk 在 webpack 中用于内部打包的過程;bundle 由 chunk 組成,一般來說,chunk 和 bundle 是對應的,但是也可能通過配置改變它們一一對應的關系。 Bundle: Produced from a number of distinct modules, bundles contain the final versions of source files that have already undergone the loading and compilation process. bundle 由不同的子產品組成,包含已經進行加載和經過編譯的源檔案的最終輸出檔案。
webpack 文檔的解釋很模糊,chunk 其實是 code splitting 中的概念,當使用到 code splitting 将 bundle 拆分出多個 chunk 就能體會到 chunk 和 bundle 的差別了。
webpack 中用來管理輸出的配置項主要就是output配置項,output可選的屬性還是很多的,常用的有以下部分:
filename:指定每個打包輸出 bundle JS 的名稱,如果是隻指定entry是一個入口檔案,那麼預設也隻會生成一個名稱為main.js的 bundle 檔案。在代碼拆分的時候,需要通過 [hash]來指定不同的檔案名。
chunkFilename:指定非入口 chunk 的名稱,預設是使用 chunk 的 id 來指定,即[id].js
hashSalt:hash 加鹽是一種密碼學中的手段,對需要進行 hash 運算的内容在任意固定位置插入特定的字元串來讓加鹽後的散列結果和沒有加鹽的結果不相同。
hotUpdateChunkFilename:自定義熱更新 chunk 的檔案名,預設是[name].[hash].hot-update.js,這裡的[name]是上面指定的filename,例如:

scriptType:指定<script>标簽插入到頁面中的type屬性,預設是什麼都不指定,對于<script>标簽來說,type屬性為空,則會将檔案看作 JS
path:指定整個項目打包輸出的 bundle 的目錄,預設是項目根目錄的dist檔案夾
publicPath:開發環境一般用不到這個配置,如果在使用 WDS 的時候,同時指定publicPath,就表示提供給 WDS 的檔案都來自于publicPath目錄;但是生産環境下可能用來配置 CDN 的路徑字首
sourceMapFilename:僅在 devtool 設定為 'source-map' 時有效,也就是生成的 source map 的檔案名,預設情況下是和 bundle 在同一個目錄中
ecmaVersion:控制生成代碼的 ES 版本,在 webpack4 中這個值是5,在 webpack5 中,這個值是6,也就是允許 ES6 代碼存在
compareBeforeEmit:在打包輸出檔案之前,檢查檔案在目錄中是否已經存在,如果存在就不再新寫入一個相同的檔案,預設是true
iife:添加 IIFE 外層包裹的括号,預設是true
module:預設是true,即允許輸出的 JavaScript 檔案作為子產品類型
pathinfo:在生産環境下預設是true,即引入「所包含子產品資訊」的相關注釋;在開發環境下預設是false,且建議是false
webpack - 緩存
從概念解釋上來說,output.filename指定的是主 bundle 的檔案名稱;output.chunkFilename指定的是 chunk 的檔案名稱,如果為 webpack 隻指定了一個入口entry,那麼output.chunkFilename是沒啥用的,隻有代碼拆分的時候指定多個 chunk,這個配置項才能展現出來,拆分出的 chunk 如果找不到output.chunkFilename就會繼而使用output.filename作為 chunk 檔案名。
web 開發中經常遇到的一個問題就是浏覽器對資源的緩存,導緻釋出的新的 JS 檔案無法生效;過去解決方式一般是在 JS 的檔案名後面添加一串不重複的版本号。在工程化的前端項目裡,顯然無法通過手動修改檔案名來完成替換。
緩存是有用的,通過代碼拆分,我們可以做到将一些不會經常改變的核心代碼抽成一個 chunk 進行打包,并賦予一個長期緩存來解決浏覽器重複請求網絡去加載資源的問題。對于不常更改的 chunk,我們希望每次打包它們的名稱都是固定的,而對于經常修改的 chunk,需要根據内容去每次生成一個唯一的 chunk 名稱來保證更新用戶端的緩存。
通常 webpack 會為每一個子產品配置設定一個唯一的子產品辨別符 module identifier,這個 id 是一個 Int 類型的數字,并且通常從0開始,依據生産的 chunk 依次遞增。
webpack 可以使用一種稱為 substitution(可替換模闆字元串) 的方式,通過使用内容散列(content hash)替換在output.filename或output.chunkFilename配置的模闆字元串來作為輸出 bundle 檔案的名稱,這樣在檔案内容修改時,會計算出新的 hash,浏覽器會使用新的名稱加載檔案,進而使緩存無效。
具體可以使用的模闆字元串見—— loader-utils.interpolateName
模闆字元串
含義
[hash]
根據子產品 id 生成的 hash
[contenthash]
根據檔案内容生成的 hash,每個檔案資源都不相同
[chunkhash]
根據每個 chunk 内容生成的 hash
[name]
module name,如果 module 沒有名稱,則會使用其 id 作為名稱
[id]
module identifier,預設是根據子產品引入的順序,從0開始的整數
[query]
[function]
在上面的模闆字元串中存在三種 hash,預設三種 hash 的長度都是20個字元長度,可以通過加 length 的方法[xxxhash::<length>]指定 hash 的長度。并且如果開發環境使用 WDS,那麼[contenthash]無法是用于開發環境的。
第一種[hash],需要注意的是它是根據子產品 id 生成的,是以每個 chunk 得到的值都是一樣的,在指定代碼拆分以後,對其做了測試,可以看到兩個 chunk 的 hash 都是一樣的。
如果修改其中一個 chunk 的子產品代碼,所有 chunk 的 hash 值都會發生變化,是以使用[hash]是不穩定的,達不到上面我們說的目的。
[chunkhash]是根據每個 chunk 内容生成的 hash 值,這種情況在有些時候它是穩定的,我在修改單獨入口檔案的子產品代碼時,并未影響其它 chunk 的 hash 值。
但是當 chunk 内 CSS 和 JS 混雜的時候,例如在 React 中import一個單獨的 CSS 檔案,這是很常見的事,如果對output.filename使用了[chunkhash],而對導出的 CSS 也使用了[chunkhash],那麼 JS 和 CSS 得到的 hash 值将是一樣的,這時候 JS 和 CSS 的變化會互相影響。例如下面的配置導緻的結果是 JS 主 bundle 的 hash 值和 CSS 的 hash 值始終一樣。
而如果僅對 JS 使用[chunkhash],而 CSS 使用[contenthash],那麼 CSS 發生變化,JS 的 hash 名稱一樣也會變。
至于[contenthash]則是根據具體的子產品内容生成的 hash 值,它能檢測細微層次 module 的變化,由于 chunk 包含 module,[contenthash]是為單個 module 準備的,在使用[contenthash]以後,chunk 中的 CSS 和 JS 子產品不會互相影響。
最後對于進行 code splitting 的項目,建議如下的配置,[contenthash]還可以附加像[contenthash:10]這樣的形式來決定生成的 hash 字元串的長度。
當使用[contenthash]替換 chunk 名稱的時候,對于修改過的 chunk,每次都會生成一個具有新的 chunk 名的 chunk,而舊的 chunk 會依然保留在output.path檔案夾中,這些垃圾檔案會随着每次 build 越來越多。
clean-webpack-plugin是負責清理 build 檔案夾的插件,預設情況下,這個插件會清空在output.path檔案夾裡的所有檔案,以及每次成功重建後所有未使用的 webpack 靜态資源。現在這個插件已經到了 V3.0 版本。