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