versionUpdater.js 3.28 KB
/*
 * @Date: 2024-02-06 11:38:13
 * @LastEditors: hookehuyr hookehuyr@gmail.com
 * @LastEditTime: 2024-02-06 13:04:25
 * @FilePath: /xysBooking/src/utils/versionUpdater.js
 * @Description: 轮询检测静态资源变更(用于提示用户刷新页面)
 */
/* eslint-disable */
/**
 * 版本更新检查器
 * - 思路:定时拉取 index.html,对比其中 <script> 标签内容是否发生变化
 * - 适用:Vite 构建的产物文件名带 hash,更新后 script src 会变化
 * @class
 */
export class Updater {
    /**
     * @param {{ time?: number }} options 配置项
     */
    constructor(options = {}) {
        this.oldScript = [];
        this.newScript = [];
        this.dispatch = {};
        this.init(); //初始化
        this.timing(options.time); //轮询
    }

    /**
     * 初始化:读取当前 html 并记录 script 标签快照
     * @returns {Promise<void>}
     */
    async init() {
        const html = await this.getHtml();
        this.oldScript = this.parserScript(html);
    }

    /**
     * 拉取 index.html 文本内容
     * @returns {Promise<string>} html 文本
     */
    async getHtml() {
        // TAG: html的位置需要动态修改
        const html = await fetch(import.meta.env.VITE_BASE).then((res) => res.text()); //读取index html
        return html;
    }

    /**
     * 解析 html 中的 script 标签字符串数组
     * @param {string} html html 文本
     * @returns {string[]|null} script 标签数组(match 可能返回 null)
     */
    parserScript(html) {
        const reg = new RegExp(/<script(?:\s+[^>]*)?>(.*?)<\/script\s*>/gi); //script正则
        return html.match(reg); //匹配script标签
    }

    /**
     * 订阅事件
     * @param {'no-update'|'update'|string} key 事件名
     * @param {Function} fn 回调函数
     * @returns {Updater} 当前实例,便于链式调用
     */
    on(key, fn) {
        (this.dispatch[key] || (this.dispatch[key] = [])).push(fn);
        return this;
    }

    /**
     * 对比两次 script 标签快照
     * @param {string[]|null} oldArr 旧快照
     * @param {string[]|null} newArr 新快照
     * @returns {void}
     */
    compare(oldArr, newArr) {
        // 兼容 match 返回 null 的场景,避免 compare 触发运行时异常
        const safeOldArr = Array.isArray(oldArr) ? oldArr : [];
        const safeNewArr = Array.isArray(newArr) ? newArr : [];
        const base = safeOldArr.length;
        // 去重
        const arr = Array.from(new Set(safeOldArr.concat(safeNewArr)));
        //如果新旧length 一样无更新
        if (arr.length === base) {
            const fns = Array.isArray(this.dispatch['no-update']) ? this.dispatch['no-update'] : [];
            fns.forEach((fn) => fn());
        } else {
            //否则通知更新
            const fns = Array.isArray(this.dispatch['update']) ? this.dispatch['update'] : [];
            fns.forEach((fn) => fn());
        }
    }

    /**
     * 开始轮询
     * @param {number} time 轮询间隔(毫秒)
     * @returns {void}
     */
    timing(time = 10000) {
        //轮询
        setInterval(async () => {
            const newHtml = await this.getHtml();
            this.newScript = this.parserScript(newHtml);
            this.compare(this.oldScript, this.newScript);
        }, time);
    }
}