前端版本发包动态更新解决方案(一)

本项目实现了一个 Monitor 类,用于监控网页的脚本和 ETag 变化。通过定期检查页面的 script 和 ETag,可以在页面内容发生变化时触发回调函数。
这边前端代码发包,用户侧在监听到不一致的时候,就能直接提示用户刷新页面获取新版本前端数据。

目的

  • 实时监控:实时监控网页的脚本和 ETag 变化。
  • 回调通知:在检测到变化时,通过注册的回调函数通知用户。
  • 暂停功能:提供暂停监控的功能,以便在需要时停止监控。

技术方案及原理

  • 异步请求:使用 fetch API 异步获取页面的 HTML 和 ETag。
  • 正则解析:使用正则表达式解析 HTML 中的<script>标签内容。
  • 事件监听:通过 on 方法注册回调函数,当检测到变化时调用这些回调函数。
  • 状态管理:通过类的属性管理当前和旧的脚本内容及 ETag,以及监控状态。
    在这里插入图片描述
    在这里插入图片描述

运行步骤

引入模块

import {
    
     monitor } from "./path/to/monitor";

注册回调函数

monitor.on("update", () => {
    
    
  console.log("页面内容发生变化, 执行操作函数");
});

启动监控

setInterval(() => {
    
    
  monitor.check();
}, 5000); // 每 5 秒检查一次

暂停监控

monitor.pause(); // 暂停监控

通过 Monitor 类,我们可以实现实时监控网页的脚本和 ETag 变化,并在检测到变化时通过回调函数通知用户。该类提供了灵活的注册回调、暂停监控等功能,适用于需要实时监控页面变化的场景。

完整 JS 代码

class Monitor {
    
    
  constructor() {
    
    
    this.oldScript = []; // 存储旧的脚本内容
    this.newScript = []; // 存储新的脚本内容
    this.oldEtag = null; // 存储旧的 ETag
    this.newEtag = null; // 存储新的 ETag
    this.dispatch = {
    
    }; // 存储注册的回调函数
    this.stop = false; // 控制监控状态
    this.init(); // 初始化监控
  }

  async init() {
    
    
    console.log("初始化版本监听器");
    // 获取初始 HTML
    const html = await this.getHtml();
    // 解析初始脚本内容
    this.oldScript = this.parserScript(html);
    // 获取初始 ETag
    this.oldEtag = await this.getEtag();
  }

  async getHtml() {
    
    
    // 发送 GET 请求获取页面 HTML
    const response = await fetch("/");
    // 读取响应内容
    const html = await response.text();
    return html;
  }

  async getEtag() {
    
    
    // 发送 GET 请求获取页面 ETag
    const response = await fetch("/");
    // 从响应头中提取 ETag
    const etag = response.headers.get("etag");
    return etag;
  }

  parserScript(html) {
    
    
    // 正则表达式匹配 <script> 标签
    const reg = /<script(?:\s+[^>]*)?>(.*?)<\/script\s*>/gi;
    // 匹配所有 <script> 标签内容
    const matches = html.match(reg);
    // 返回匹配结果,如果没有匹配到则返回空数组
    return matches || [];
  }

  on(key, fn) {
    
    
    if (key === "update") {
    
    
      // 只处理 "update" 事件
      if (!this.dispatch[key]) {
    
    
        // 如果没有注册过 "update" 事件
        this.dispatch[key] = []; // 初始化回调函数数组
      }
      this.dispatch[key].push(fn); // 将回调函数添加到数组中
    }
    return this; // 支持链式调用
  }

  pause() {
    
    
    this.stop = !this.stop; // 切换监控状态
  }

  get value() {
    
    
    return {
    
    
      oldEtag: this.oldEtag, // 返回旧的 ETag
      newEtag: this.newEtag, // 返回新的 ETag
      oldScript: this.oldScript, // 返回旧的脚本内容
      newScript: this.newScript, // 返回新的脚本内容
    };
  }

  compare() {
    
    
    // 如果监控已暂停,则返回
    if (this.stop) return;
    // 获取旧脚本内容的长度
    const oldLen = this.oldScript.length;
    // 合并旧脚本和新脚本内容
    const newSet = new Set(this.oldScript.concat(this.newScript));
    // 获取合并后的内容长度
    const newLen = newSet.size;
    if (this.oldEtag !== this.newEtag || newLen !== oldLen) {
    
    
      // 检查 ETag 或脚本内容是否有变化
      this.dispatch.update.forEach((fn) => {
    
    
        // 遍历注册的回调函数
        fn(); // 调用每个回调函数
      });
    }
  }

  async check() {
    
    
    // 获取新的 HTML
    const newHtml = await this.getHtml();
    // 解析新的脚本内容
    this.newScript = this.parserScript(newHtml);
    // 获取新的 ETag
    this.newEtag = await this.getEtag();
    // 打印新的 ETag
    console.log("etag版本检查", this.newEtag);
    // 比较旧内容和新内容
    this.compare();
  }
}
// 导出实例化的 Monitor 对象
export const monitor = new Monitor();