webpack + vue 項目 自定義 插件 解決 前端 JS 版本 更新 問題


       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:

Webpack插件開發

vue-router源碼分析-整體流程

Vue.js——vue-router 60分鍾快速入門

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM