背景
我們的項目是後臺管理系統,框架是Vue
,構建工具是Webpack
。
問題
每一次修改代碼後打包發佈到生產環境後,用戶需要手動刷新才能獲取到最新的代碼。
原因
Webpack
打包之後會根據文件內容生成一個hash
值,然後再按照[name].[hash].js
的格式生成文件名;
然後再根據文件的路由對應關係生成一個runtime.[hash].js
的文件,這個文件會監控瀏覽器的路由變化去服務器加載對應的js
文件。
而runtime.[hash].js
文件是放在index.html
中,在開始進入系統時已經加載。
所以當服務器重新發版之後,生成的文件會出現hash
值改變,這時候當用戶跳轉路由時,請求加載的文件名在服務器上已經找不到了,就會報錯:Loading chunk [name].[hash].js failed
方案
思路:既然能監控到加載js
的錯誤信息,那麼就可以在處理錯誤信息的代碼中加入刷新頁面的功能,讓頁面自動去刷新頁面,然後再獲取最新的runtime.[hash].js
文件,用戶再次點擊時就可以正常訪問了。
按照思路,分爲以下2步:
-
找到處理錯誤信息的代碼,加入刷新頁面邏輯
經過幾番查找,處理錯誤信息的代碼在
runtime.[hash].js
中,如下:var u = new Error; d = function(a) { o.onerror = o.onload = null, clearTimeout(b); var c = r[e]; if (0 !== c) { if (c) { var t = a && ("load" === a.type ? "missing" : a.type), f = a && a.target && a.target.src; u.message = "Loading chunk " + e + " failed.\n(" + t + ": " + f + ")", u.type = t, u.request = f, c[1](u) } r[e] = void 0 } }
難點在於,
runtime.[hash].js
是由webpack
單獨生成的,我們不能通過項目中的代碼來修改,而且暫時沒有找到通過配置的方式來修改。目前的臨時解決方案:
通過
Loading chunk
關鍵字在webpack
模塊中找到了對應的文件,文件地址:webpack/lib/web/JsonpMainTemplatePlugin.js
,找到處理錯誤代碼,並添加刷新頁面代碼:Template.indent([ "if(chunk) {", Template.indent([ "var errorType = event && (event.type === 'load' ? 'missing' : event.type);", "var realSrc = event && event.target && event.target.src;", "error.message = 'Loading chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realSrc + ')';", "error.type = errorType;", "error.request = realSrc;", "chunk[1](error);" ]), "}", "location.reload(ture)", // 在此處添加刷新頁面代碼 "installedChunks[chunkId] = undefined;" ]),
重新執行
npm run build
命令就可以了問題的來了,自動佈署時會重新拉取
webpack
包,還是不能生效。解決方案就是自己
Fork webpack
項目,修改以上代碼後,再自己發佈一個npm
包,然後將項目中的
webpack
引用名改成自己的npm
包名,這樣就可以了,缺點就是後續的升級比較麻煩。 -
刷新頁面時,取消緩存,獲取最新的文件
自動刷新解決了,發現瀏覽器並沒從服務器上拉取文件,原因就是瀏覽器緩存了。
解決方案:在文件請求路徑添加參數,參數值設置爲
hash
值,比如:<script src="/static/js/runtime.68e22691.js?t=68e22691"> <link href="/static/css/vendors.bbf38da36d1cd1d96a5e.css?t=cd1d96a5e" rel="stylesheet">
怎麼配置呢?
找到
webpack
的output
,設置成如下:output: { path: OUTPUT_PATH, publicPath: '/', filename: 'static/js/[name].[chunkhash:8].js?t=[chunkhash:8]', chunkFilename: 'static/js/[name].[chunkhash:8].js?t=[chunkhash:8]' }
優化
按照目前的解決方案,確實可以實現不再需要手動刷新獲取最新的資源,但是有個問題就是刷新之後並不會跳轉到用戶點擊的頁面,還是停留在當前頁面,需要再點擊一次纔到跳轉。
所以我們可以在刷新之前,彈出一個確認框,告知用戶服務器發版或升級了,需要刷新頁面才能獲取最新的資源。用戶點擊確認之後再進行刷新操作。