webpack的splitChunk造成hash一致但文件內容不一致的bug

bug描述

上線後發現頁面白屏,控制檯也沒有任何報錯。經過定位發現,預發佈環境部署了一個vendors_1234.js文件把線上環境的vendors_1234.js覆蓋了(預發佈和線上的靜態文件是一個目錄),但是這兩個文件的內容竟然不一樣。你沒看錯!!hash相同,內容不同!!。本以爲是碰到了萬年不遇的撞hash事件,最後研究發現竟然是webpack的splitChunk導致的,而且有很大概率會出現。所以寫出來讓大家參考!

解決辦法

解決辦法很簡單,爲了照顧心急的同學,先把解決辦法放上,後面再慢慢看解決過程。
修改webpack配置,讓chunkId固定

  • optimization.chunkIds 設置爲 ‘named’,讓chunkId不會隨着chunk數量變化,見下圖。
    在這裏插入圖片描述

  • 另推薦同時設置 optimization.moduleIds = ‘hashed’ 讓module的id也固定,效果與老版的 HashedModuleIdsPlugin 相同。即不會造成新增了個module就造成所有moduleId都變化,從而使所有chunk的hash也跟着變化,讓緩存失效。參考《對Webpack的hash穩定性的初步探索》效果見下圖
    未配置moduleIds和chunkIds
    在這裏插入圖片描述
    配置了moduleIds='hashed’和chunkIds='named’
    在這裏插入圖片描述

前置核心知識點

chunkId:

webpack將打包後的內容都掛載在 window.webpackJsonp 這個數組上。每個元素對應一個打包後js文件的內容,稱爲chunk。每個chunk都有一個chunkId,入口文件通過chunkId從window.webpackJsonp 中讀取js代碼運行,chunkId默認從0開始累加。如下圖中,vendors這個chunk的chunkId是6。可閱讀《記一次對webpack打包後代碼的失敗探究》
幫助理解。
在這裏插入圖片描述

兩個文件hash相同但文件內chunkId不同的情況:

經過試驗和資料查找,推測chunk及hash生成後才做的splitChunk(見下圖),所以的確會產生兩個文件hash相同但文件當中顯示的chunkId不同的情況。
在這裏插入圖片描述
圖片引自《Webpack揭祕——走向高階前端的必經之路》

解決過程TL;DR

復現步驟

  1. 初始狀態:線上vendors_1234的chunkId爲19,線上的index19.js可以正常讀取到window.webpackJsonp[19](僞代碼示意,vendors_1234運行時,把自己的內容放在了這裏)的代碼並正常運行。
  2. 開發優化減少了打包數量,導致vendors的chunkId變爲6,但內容不變,所以hash值沒變(例子見下圖,原因見上文),文件名還是vendors_1234(但是chunkId=6),開發環境的index6.js可以正常讀取vendors_1234並運行。
    在這裏插入圖片描述
  3. 發佈預發佈環境,由於預發佈與線上用的是同一個目錄,這時vendors_1234(chunkId=19)會被覆蓋,變爲vendors_1234(chunkId=6)。
  4. 線上的index19.js嘗試讀取window.webpackJsonp[19]的內容,但是線上此時是vendors_1234(chunkId=6),其內容放在了window.webpackJsonp[6],所以讀取不到正確的代碼,無法正常運行。導致白屏bug!

模擬復現步驟build各種版本

修改異步加載模塊的數量,經試驗,發現下圖中箭頭指向的 x(當時還不知道這個是chunkId)與打包後js文件總數相差2,推斷 x 表示「異步加載的模塊數量」或「當前模塊的索引」,差的2是vendor自身和runtime.js。案例如下(vendors的hash全部相同):
注:runtime.js是爲了解決異步加載模塊變更導致index.js變更的問題,優化緩存。可搜索runtimeChunk瞭解詳情
js總數總比 x 大2
js總數21,x = 19;js總數20,x = 18;js總數19,x = 17;js總數8,x = 6……
在這裏插入圖片描述
**去掉runtime,js總數總比 x 大1 **
js總數7,x = 6;js總數20,x = 19……
此時意識到是chunk數量引起了bug,於是嘗試固定vendors的chunkId來解決問題。

分析runtime.js嘗試固定chunkId

在這裏插入圖片描述
如上圖所示,除了vendors,其他異步加載的chunk都在runtime.js裏列出來了。於是嘗試如何把vendors加入到runtime.js裏。經過短暫的努力,宣告失敗,換思路。

研究webpack - splitChunk配置

經試驗分析如下:

optimization.splitChunks.chunks = 'initial’情況下,vendors會被最後生成,chunkId也會隨着打包數量變化。optimization.splitChunks.chunks爲其他值時,vendors會被先分析,其chunkId始終爲0。

因此猜測,文件的hash生成是在chunk掛在在window.webpackJsonp上之前(此時對webpack打包流程還沒有這麼清晰,只是隱約有個判斷),此時並沒有生成chunkId。所以造成了hash值一樣,chunk內容不一樣。
猜測歸猜測,先放一邊,繼續解決chunkId固定的問題。由於optimization.splitChunks.chunks爲其他值時不滿足需求,所以此路仍然不通,再換。

更加有效的google

偶然發現dev環境下chunkId不是數字
在這裏插入圖片描述
這個發現,加上上文放一邊的那個猜測,開始各種google,發現了兩個較有幫助的資料:
《記一次對webpack打包後代碼的失敗探究》 這篇文章讓我初步瞭解到了webpack打包的原理,也同時對webpackJsonp、chunkId等各種概念有了認識。
《Webpack原理與實踐(一):打包流程》這篇文章讓我進一步瞭解了webpack打包的流程,並且找到了上文猜測的答案,的確與我猜測的吻合。
此時纔對於這些概念有了認識,google了幾個關鍵詞後,直接在webpack文檔中定位到了解決方案:
https://webpack.js.org/configuration/optimization/#optimizationchunkids
同時還優化掉了 HashedModuleIdsPlugin 插件。
至此,問題算是解決了。

總結

  • staging與online一定要充分隔離,不能有共享的情況,包括靜態文件。hash也不保準!
  • 還有一個兜底方案,靜態文件的文件名也加入環境信息,甚至項目信息,如index_prd_hash.js, performance_vendors_staging_hash.js
  • 解決疑難雜症還是要深入原理層面,至少要弄清楚一些核心概念,好針對性的google。
  • 碰到複雜問題,不要怕,慢慢抽絲剝繭,解決過程中會學到很多新東西,解決問題後會得到很大的成就感,與君共勉~
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章