Automatic update of scripts after deployment

Prologue





Good afternoon, dear readers. This article is devoted to the problem faced by the developers of more or less serious websites, namely, the problem of automatically updating scripts in the user's browser after deployment.



, , , , , , , . , , . โ€“ , , .



, F5, , , .







, .



, , VueJS 2.6.x, js- webpack'.



-, , github.



:



โ†’ -

โ†’ github



, , . , , .





- .



, , . , , .





Vue, /src/plugins/AutoReload. npm .



- vue-cli, : store, , axios - dayjs . Element.





โ€“ . :



  • . API, (, , ). , , .
  • . โ€“ , , , . , .
  • . .


, VueJS Webpack. , webpack js-, . , , version.json, .



, (development/production/etc.) package.json. :



{
    "AppVersion": "1.0.0",
    "Build": "development",
    "BundleVersion": "2020-11-07T10:42:33.731Z"
}


, vue.config.js, :



const path = require('path');

//   version.json   
const AutoReloadUtils = require('./src/plugins/AutoReload/versionGenerator');
AutoReloadUtils.generateVersionFile(path.resolve(path.join(__dirname, 'public/version.json')));

module.exports = {
    ...
};


version.json:



versionGenerator.js
const path = require('path');
const fs = require('fs');

module.exports = {
    /**
     *     
     * @param {String} filename    
     */
    generateVersionFile: function (filename) {
        //     package.json
        const packageJson = fs.readFileSync('./package.json');
        const version = JSON.parse(packageJson).version || 0;

        fs.writeFileSync(filename, `{
    "AppVersion": "${version}",
    "Build": "${process.env.NODE_ENV}",
    "BundleVersion": "${new Date().toISOString()}"
}
`);
    }
}




serve, build version.json, get-, .



โ„–1. version.json public, VueJS ยซ ยป. .



โ„–2. version.json git', , .



AutoReload



:



  1. , .
  2. vue-router, . . Vue , , , , ( ) . .
  3. .




. , , :



  • Enabled โ€“ , true.
  • CheckInterval โ€“ , 60. .
  • Notification โ€“ , true. , ยซยป, .
  • NotificationMessage โ€“ , ยซ , .ยป. Element, , . , , alert.


.



Config.js
import { isBoolean } from './utils';

/**
 *    
 */
export default class Config {
    /**
     * 
     * @param {Object} origin 
     */
    constructor(origin) {
        /**
         *   
         * @type {Boolean}
         */
        this.Enabled = isBoolean(origin.Enabled) ? origin.Enabled : true;

        /**
         *      
         * @type {Number}
         */
        this.CheckInterval = origin.CheckInterval ?? 1 * 60;

        /**
         *     
         * @type {Boolean}
         */
        this.Notification = isBoolean(origin.Notification) ? origin.Notification : true;

        /**
         *  
         * @type {String}
         */
        this.NotificationMessage = origin.NotificationMessage
            ?? '  ,   .';
    }
}






, โ€“ Element', create Vue:



import AutoReload from '@/plugins/AutoReload';
...
new Vue({
    router,
    store,

    created() {
        Vue.use(AutoReload, {
            config: {
                //  
                Enabled: true,
                //  
                CheckInterval: 60,
            },
            router: this.$router,
            vm: this,
        });
    },

    render: h => h(App),
}).$mount('#app');








AutoReload.js
import Config from './Config';
import { getVersion } from './utils';

/** @typedef {import('./Version').default} Version */

/**
 *   
 */
export default class AutoReload {
    /**
     * 
     * @param {Object} options 
     */
    constructor(options) {
        /**   */
        this.router = options.router;

        /**  Vue */
        this.vm = options.vm;

        /**  */
        this.config = new Config(options.config);

        /**
         *   
         * @type {Version}
         */
        this.lastVersion = null;

        /**
         *   
         * @type {Number}
         */
        this.timer = null;
    }

    /**   */
    async init() {
        const config = this.config;

        if (config.Enabled) {
            //    
            this.lastVersion = await getVersion();

            if (this.lastVersion && config.CheckInterval > 0) {
                //    
                this.timer = setInterval(async () => {
                    this.check();
                }, config.CheckInterval * 1000);
            }

            //    
            this.router.beforeEach(async (to, from, next) => {
                await this.check(this.router.resolve(to).href);
                next();
            });
        }
    }

    /**
     *    
     * @param {String} href  
     */
    async check(href) {
        //    
        const version = await getVersion();

        if (this.lastVersion.BundleVersion != version.BundleVersion) {
            //   

            //  
            if (this.timer) {
                clearInterval(this.timer);
                this.timer = null;
            }

            if (this.config.Notification) {
                //    
                await this.vm.$alert(this.config.NotificationMessage, '', {
                    type: 'warning',
                    confirmButtonText: 'OK',
                    closeOnClickModal: true,
                    closeOnPressEscape: true,
                }).catch(() => { });
            }

            //    
            //   ,     ,
            //        ,    
            this.lastVersion = await getVersion();

            this.reload(href);
        }
    }

    /**
     *  
     * @param {String} href  
     */
    reload(href) {
        if (href) {
            window.location.href = href;
        } else {
            window.location.reload(true);
        }
    }
}




check href. , .



version.json . , , .



, F5 (window.location.reload(true)). , . , .. next() .



, .



โ„–1.



version.json . ยซยป , , URL. , version.json .



, , . , , .



, .



, : ยซ60 ยป, ยซ60 ยป. , . , .



โ„–2.

C# , JS . .



C#: https://pastebin.com/T1PsMy4N



JS: https://pastebin.com/a4z25b1H



:



//   
var windows0 = WordForm.get(0, "", "", "");  // ""
var windows1 = WordForm.get(1, "", "", "");  // ""
var windows2 = WordForm.get(2, "", "", "");  // ""

//       
var totalWindows0 = WordForm.getAsCount(0, "", "", "");  // "0 "
var totalWindows1 = WordForm.getAsCount(1, "", "", "");  // "1 "
var totalWindows2 = WordForm.getAsCount(2, "", "", "");  // "2 "






, , , . .







, - , , , . , - -, - .



? , , , , , , .



- , : , .







, , , . js- . .



, / , . , , โ€“ .







, , . , store. , , .



, , : .







, , - changelog', . , . , . , , .





, .



, , .





, . , ยซยป, ?



? :






All Articles