Webpack5 持久化緩存實踐
背景
公司的云his靜態(tài)項目代碼量巨大,依賴的npm包大概有100個,打包一次大概要14分鐘
自研的hammer工具的本地打包雖然能提升部署時間,但是依賴開發(fā)的手動操作
用來存放本地構(gòu)建產(chǎn)物的服務(wù)器容量滿了,所以為了正常使用本地打包功能,還得定期去清理服務(wù)器上的老文件,不夠方便
解決思路
node版本提升 8.x -> 12.x
利用webpack5的持久化緩存提升構(gòu)建效率
速度大幅度提升,快了7倍。
使用基于rust開發(fā)的swc替代babel,測試的構(gòu)建速度提升一分鐘半左右,因生態(tài)不成熟,不能上生產(chǎn)。
關(guān)鍵代碼
module.exports = {
...
cache: {
// 將緩存類型設(shè)置為文件系統(tǒng),默認(rèn)是memory
type: 'filesystem',
buildDependencies: {
// 更改配置文件時,重新緩存
config: [__filename]
}
},
optimization: {
// 值為"single"會創(chuàng)建一個在所有生成chunk之間共享的運行時文件
runtimeChunk: 'single',
moduleIds: 'deterministic',
},
}
webpack 在入口 chunk 中,包含了某些 boilerplate(引導(dǎo)模板),特別是 runtime 和 manifest。這些代碼如果不被單獨抽離會導(dǎo)致即使沒有代碼改動,打包出來的文件名仍然會改變,導(dǎo)致無法命中緩存。webpack4中使用HashedModuleIdsPlugin來生成hash值作為模塊id,在webpack5中已經(jīng)不需要了,moduleIds: 'deterministic',是用來保證模塊的id不會隨著解析順序的變化而變化,生產(chǎn)環(huán)境默認(rèn)開啟。
緩存的方式(從構(gòu)建層面來講)
webpack V4
- cache-loader:建議在開銷較大的loader前加,比如babel-loader、vue-loader等;
- dll:對不經(jīng)常改變版本的依賴(react、lodash),單獨生成動態(tài)鏈接庫(bundle),提高構(gòu)建速度,需要DllPlugin 、DllReferencePlugin 搭配使用,通過引用 dll 的 manifest 文件來把依賴的名稱映射到模塊的 id 上,之后再在需要的時候通過內(nèi)置的 __webpack_require__ 函數(shù)來 require 他們,推薦在開發(fā)模式下使用
webpack V5
- 文件系統(tǒng)緩存,配置方式見上面的關(guān)鍵代碼,作用是將Webpack運行時存在于內(nèi)存中的那些緩存,不是loader的產(chǎn)物,更不是dll,根據(jù)Webpack運行環(huán)境的不同,在dev開發(fā)時依舊使用MemoryCachePlugin,而在build時使用IdleFileCachePlugin。dev/build的二次編譯速度會遠(yuǎn)超cache-loader
一些原理淺談
Webpack 5令人期待的持久緩存優(yōu)化了整個構(gòu)建流程,原理依然還是那一套:當(dāng)檢測到某個文件變化時,根據(jù)依賴關(guān)系,只對依賴樹上相關(guān)的文件進(jìn)行編譯,從而大幅提高了構(gòu)建速度。官方經(jīng)過測試,16000 個模塊組成的單頁應(yīng)用,速度竟然可以提高 98%!其中值得注意的是持久緩存會將緩存存儲到磁盤。
對于一個持續(xù)化構(gòu)建過程來說,第一次構(gòu)建是一次全量構(gòu)建,然后它會將相關(guān)產(chǎn)物序列化緩存在磁盤中(serialize)。后續(xù)構(gòu)建具體流程可以依賴于上一次的緩存進(jìn)行:讀取磁盤緩存 -> 校驗?zāi)K -> 解封模塊內(nèi)容。因為模塊之間的關(guān)系并不會被顯式緩存,因此模塊之間的關(guān)系仍然需要在每次構(gòu)建過程中被校驗,這個校驗過程和正常的 webpack 進(jìn)行分析依賴關(guān)系時的邏輯是完全一致的。
對于 resolver 的緩存同樣可以持久化緩存起來,一旦 resolver 緩存經(jīng)過校驗后發(fā)現(xiàn)準(zhǔn)確匹配,就可以用于快速尋找依賴關(guān)系。如果 resolver 緩存校驗失敗的情況,將會直接執(zhí)行 resolver 的常規(guī)構(gòu)建邏輯。
緩存的安全性設(shè)計
unsafeCache
在webpack 4.x的構(gòu)建過程中基于timestamp比對策略的一種cache方式,它有兩個維度,resolve(解析器)的unsafeCache和module(模塊)的unsafeCache。如果同時開啟,那么從入口文件開始,webpack通過resolve規(guī)則解析所有的依賴文件,將模塊之間的依賴關(guān)系和解析后的文件內(nèi)容保存起來,并存儲依賴的最后變更時間(timestamp),一旦發(fā)現(xiàn)相同引用,返回緩存。
而webpack 5.x版本已經(jīng)放棄了這種緩存策略,默認(rèn)只針對開啟cache選項并且是node_modules下的依賴才開啟unsafeCache,判斷是否有文件系統(tǒng)序列化后的文件信息來判斷是否需要重新構(gòu)建。
safeCache
模塊間的依賴關(guān)系被基于內(nèi)容對比的算法(contentHash)被記錄下來,并存入到ModulGraph的class中的weakmap,相比于依賴時間戳的方式更可靠。
緩存的容量限制
除了需要考慮緩存的安全性,緩存的容量限制也不能忽視,緩存不可能無限疊加,這里就涉及到經(jīng)典的LRU cache算法(Least
Recently Used 最近最少使用)。
- 單向鏈表 添加、刪除節(jié)點O(1),查找O(n)
- 雙向鏈表加哈希表結(jié)合體 O(1)
LRU分析
當(dāng)存在熱點數(shù)據(jù)時,LRU的效率很好,但偶發(fā)性的、周期性的批量操作會導(dǎo)致LRU命中率急劇下降,緩存污染情況比較嚴(yán)重。
LRU算法的改進(jìn)方案
redis使用的改進(jìn)算法LIRS、LRU-K等,感興趣的同學(xué)自行查閱
作者:丁楠。我不生產(chǎn)代碼,我是代碼的搬運工。