前端页面迭代更新部署通知用户

1,183 阅读3分钟

前端重新部署通知用户刷新网页

痛点、应用场景

当页面迭代上线之后,用户停留在老页面(更新之后=》加入浏览器标签---访问页面、不加入浏览器标签--访问页面::涉及系统权限是否过期),用户不知道网页重新部署了,体验不到新功能 或者跳转页面时候 有时 js 连接 hash 变了导致报错跳不过去

应用场景

功能迭代前端页面重新部署 用户第一时间访问新功能

痛点

页面更新之后,不刷新页面的情况下,用户如何快速访问到新功能或者告知用户有新功能更新,不能老是提醒用户刷新 不友好 不智能 不专业

解决方案

后端支持

使用 webSocket 和后端进行实时通讯,前端页面部署完之后,后端给通知,前端给 Message 进行提示; 可以优化使用 EvnentSource 或 socket 智能后端往前端推送消息,前端无法给后端发送通信,此需求也不需要给后端发送;

前端自己实现

  1. 对比 meta 标签中 version:通过往 html 文件 插入 meta 标签 带上 version 版本号,轮询 对比 新更新页面和旧页面 version,当 version 不一样时,提醒用户页面有更新 做操作(刷新页面)
  2. 对比引入 js 文件 hash 值不同:单页面文件都需要通过打包成 html、js、静态文件,轮询 对比 html 文件中引入 js 文件的 hash 值,当值不同时,提醒用户页面有更新 做操作(刷新页面)

两者区别

  1. 若 html 中没有插入 version 则需要手动引入 sethtml 插件 加入 meta 标签
  2. 系统框架不一样 vue3 vue2 webpack vite 打包方式不一致 则做不到开箱即用
  3. 插入 version 有可能不更新 version
  4. 根据每次打包后 js hash 值进行 轮询判断 --- 开箱即用

代码实现

check-hash

// 1. 封装:
export class Updater {
  oldScript = []; //存储第一次值也就是script 的hash 信息
  newScript = []; //获取新的值 也就是新的script 的hash信息
  dispatch = {}; //小型发布订阅通知用户更新了
  constructor(options) {
    this.oldScript = [];
    this.newScript = [];
    this.dispatch = {};
    this.init(); //初始化
    this.timing(options?.timer); //轮询
  }

  async init() {
    const html = await this.getHtml();
    this.oldScript = this.parserScript(html);
  }

  async getHtml() {
    const { origin, pathname, hash, search } = window.location;
    const date = new Date().valueOf();
    let _url = `${origin}${pathname}?t=${date}${hash}`;
    const html = await fetch(_url).then((res) => res.text()); //读取index html
    return html;
  }

  parserScript(html) {
    const reg = new RegExp(/<script(?:\s+[^>]*)?>(.*?)<\/script\s*>/gi); //script正则
    return html.match(reg); //匹配script标签
  }

  //发布订阅通知
  on(key = "no-update" || "update", fn) {
    (this.dispatch[key] || (this.dispatch[key] = [])).push(fn);
    return this;
  }

  compare(oldArr = [], newArr = []) {
    console.log("oldArr", oldArr);
    console.log("newArr", newArr);
    const base = oldArr.length;
    const arr = Array.from(new Set(oldArr.concat(newArr)));
    console.log("arr", arr);
    //如果新旧length 一样无更新
    if (arr.length === base) {
      this.dispatch["no-update"].forEach((fn) => {
        fn();
      });
    } else {
      //否则通知更新
      this.dispatch["update"].forEach((fn) => {
        fn();
      });
    }
  }

  timing(time = 10000) {
    //轮询
    setInterval(async () => {
      const newHtml = await this.getHtml();
      this.newScript = this.parserScript(newHtml);
      this.compare(this.oldScript, this.newScript);
    }, time);
  }
}
// 2. 使用
const up = new Updater({
  timer: 2000,
});

// todo 回调出三个方法亦可

//未更新通知
up.on("no-update", () => {
  console.log("未更新");
});
//更新通知
up.on("update", () => {
  console.log("更新了");
});

check-version

// 封装
const checkVersion = async (options) => {
  var _a;
  let {
    auto_refresh = false,
    delay = 1e3 * 60,
    success = () => {},
    fail = () => {},
    error = () => {},
  } = options || {};
  const { origin, pathname, hash, search } = window.location;
  const date = new Date().valueOf();
  let _url = `${origin}${pathname}?t=${date}${hash}`;
  try {
    const res = await fetch(_url);
    const text = await res.text();
    const result = text.match(/<meta name="version" content="(.*?)"/);
    const local_versioiin =
      (_a = document.getElementsByTagName("meta")["version"]) == null
        ? void 0
        : _a.content;
    if (delay !== 0) {
      setTimeout(() => {
        checkVersion(options);
      }, delay);
    }
    if (!result || !result[1]) throw Error("no meta version");
    if (search && !search.startsWith("?t=")) {
      let s = search;
      const i = search.indexOf("t=");
      if (i !== -1) {
        s = search.slice(0, i - 1);
      }
      _url = `${origin}${pathname}${s}&t=${date}${hash}`;
    }
    if (result[1] !== local_versioiin) {
      if (auto_refresh) {
        window.location.replace(_url);
      } else {
        success(_url);
      }
    } else {
      fail(_url);
    }
  } catch {
    error();
  }
};

// 使用
checkVersion({
  auto_refresh: false,
  delay: 1000 * 20,
  success: (url) => {
    console.log("更新了");
  },
  fail: (url) => {
    // console.log(url);
  },
  error: () => {
    // consosle.log('error')
  },
});

参考链接: