Webpack 是一個前端資源加載/打包工具。它將根據模塊的依賴關系進行靜態分析,然后將這些模塊按照指定的規則生成對應的靜態資源。
它的異步加載原理是,事先將編譯好后的靜態文件,通過js對象映射,硬編碼進打包后的 manifest.xxxx.js 文件中,然后通過JSONP原理按需加載每個chunk。
每個子模塊加載完畢之后,瀏覽器將會進行本地緩存,從而節省了網絡帶寬。
Webpack編譯后的目錄結構如下:
從結構目錄來看,整個項目的入口就是index.html,我們來看看index.html的內容:
<!DOCTYPE html> <html> <head> <meta charset=utf-8> <title>www.phpdragon.com</title> <script></script> <link href=/static/css/app.4a4f13cfbe19aa22b83c451c96f49338.css rel=stylesheet> </head> <body> <div id=app> <transition :name=transitionName> <router-view></router-view> </transition> </div> <script type=text/javascript src=/static/js/manifest.f726e03ee32bcdee633a.js></script> <script type=text/javascript src=/static/js/vendor.fa5ac3dc9fb740bb48c5.js></script> <script type=text/javascript src=/static/js/app.af822fb16a16ef70005b.js></script> </body> </html>
其中引入了3個js文件,和一個css樣式文件。
我們看看manifest.f726e03ee32bcdee633a.js這個js加載器。
找到這段代碼:
是不是發現很熟悉!這一段jsonp代碼,這就是用來實現按需加載js資源文件的加載器。
講到了這,從上述代碼就引出了一個問題:
當前端腳本重新編譯了以后,項目發布采用的是刪除重置發布,由於靜態文件只加載一次的緣故,會導致按需加載模塊時報錯:Loading chunk " + e + " failed.
原因是瀏覽器已經緩存了manifest.f726e03ee32bcdee633a.js這個加載器,然后訪問新的導航欄的時候,服務器端已經不存在舊版本的JS靜態資源文件了,從而導致系統異常。
解決方法:
方案一:每次都加載項目入口文件 index.html。也就是說服務器端設置index.html文件不緩存、或者通過URL后綴添加隨機字符串來解決。
這個方案適合入口URL可變更的系統。但對於ERP這種內部系統來說,可行性不是很好。
由於入口只加載一次,導致點擊系統的其他導航欄URL操作,無法做到再次加載index.html文件,刷新瀏覽器緩存的JS。
第一種方案可以放棄了!
方案二:給每個導航欄路由URL添加隨機數。
這個也不行,並不會導致index.html被重新加載。具體原因,請詳看源碼解析:vue-router源碼分析-整體流程
方案三: 系統內部通過ajax請求獲取版本信息從而提示更新。
這個可行,但對后端系統有侵入性,需要后端同學配合。對與跨工種跨部門來說,這種方式屬於下策。
方案四:
方案二提到了路由,那么vue的路由是否提供了鈎子機制,從而進行攔截呢?
官方是提供的,詳看官方文檔:vue導航鈎子。
通過如下這段代碼,我們就能實現我們想要的功能了。一、確保了檢測的頻率。同時也對系統內部的AJAX請求減少侵入性代碼。
router.beforeEach((to, from, next) => { // ... })
1.最終解決如下:在 main.js 中添加如下代碼:
router.beforeEach((to, from, next) => { axios.get('../static/version.json?_=' + Math.random()).then(response => { if (200 == response.status) { if (process.env.VERSION !== response.data.version) { var message = "系統版本有更新,點擊確認加載最新,或按【CTRL + F5】!" Vue.prototype.$alert(message, '系統提示', { confirmButtonText: '確定', callback: function(){ window.location.reload(true); } }); return; } next(); } }).catch(err => { console.error(err); next(); }); });
2.添加版本變量:
3.給編譯環境添加env變量:
4.通過Webpack的編譯插件機制,引入 diy-plugin.js 自定義插件腳本,生成版本信息:
'use strict'; var FStream = require('fs'); var Archiver = require('archiver'); //npm install archiver /** * 版本信息生成插件 * @author phpdragon@qq.com * @param options * @constructor */ function DiyPlugin(options) { this.options = options || {}; this.options.outZipFile = this.options.path + '/front.zip'; !this.options.versionDirectory && (this.options.versionDirectory = 'static'); } //apply方法是必須要有的,因為當我們使用一個插件時(new somePlugins({})),webpack會去尋找插件的apply方法並執行 DiyPlugin.prototype.apply = function (compiler) { var self = this; compiler.plugin("compile", function (params) { var dir_path = this.options.context + '/' + self.options.versionDirectory; var version_file = dir_path + '/version.json'; var content = '{"version":' + self.options.env.VERSION + '}'; FStream.exists(dir_path, function (exist) { if (exist) { writeVersion(self, version_file, content); return; } FStream.mkdir(dir_path, function (err) { if (err) throw err; console.log('\n創建目錄[' + dir_path + ']成功'); writeVersion(self, version_file, content); }); }); }); //編譯器'對'所有任務已經完成'這個事件的監聽 compiler.plugin("done", function (stats) { console.log("開始打包壓縮編譯文件..."); var output = FStream.createWriteStream(self.options.outZipFile); var archiveZip = Archiver('zip', {zlib: {level: 9}}); archiveZip.on('error', function (err) { throw err; }); archiveZip.pipe(output); archiveZip.directory(self.options.path + '/' + self.options.versionDirectory, self.options.versionDirectory); archiveZip.file(self.options.path + '/index.html', {name: 'index.html'}); //archive.glob(self.options.path + '/*.*'); archiveZip.finalize(); }); }; const writeVersion = (self, versionFile, content) => { console.log("\n當前版本號:" + self.options.env.VERSION); console.log("開始寫入版本信息..."); //寫入文件 FStream.writeFile(versionFile, content, function (err) { if (err) throw err; console.log("版本信息寫入成功!"); }); //刪除之前的壓縮包 FStream.exists(self.options.outZipFile, function (exists) { if (exists) { FStream.unlinkSync(self.options.outZipFile); } }); } module.exports = DiyPlugin;
5.在webpack配置文件中添加 diy-plugin.js 編譯鈎子:
6. ok,至此結束。 執行編譯:
npm run build
7. 訪問項目,再次編譯版本,打開之前的項目界面,點擊其他導航菜單,效果如下:
以上,日常的一些開發點滴。
PS: