关于前端更新部署,通知用户刷新的预想方案

1,040 阅读3分钟

项目技术栈:vue2+js(老项目)

最近在修bug的时候,总会被测试追着问更新了没有,但总是修完了,转头就继续忙别的事情去了,所以就想弄一个功能,在前端部署更新的时候,在项目上弹出相应的提示,告诉他们,更新了需要刷新一下才能获取最新的资源。(由于还是比较常规的,webpack打包,然后部署到服务上)

思路:

  1. 包的时候,都去更新本地的版本号
  2. 主线程放一个定时获取版本号
  3. 览器存的版本号定时做比对(其实跟轮询差不多),然后通知用户是否刷新当前资源。

定好思路就开始干啦!

part1:

1.在public目录下放新建一个版本文件,就叫version.json吧 内容的话,就定义初始版本号

{ "version": 1 }

2.在.gitignore文件忽略我们的版本文件

添加 /public/*.json

3.(划重点)在打包的时候,执行更新的脚本,同时也要规避一下在自己本地开发的时候无需更新版本号,在执行npm run build (vue-cli-service-build) 的时候进行更新版本号。

vue.config.js文件中,通过chainWebpack(通过链式编程的形式,来修改默认的 webpack 配置)这个方法,去添加脚本修改我们的version.json文件

  • 修改文件,咱们先引入node的fs模块const fs = require("fs");
  • chainWebpack方法里,先判断当前构建时执行的命令,打印console.log(process.argv);

image.png

image.png

  • 根据这个可以判断在build的时候,去执行我们更新的脚本。
  • 思路就是通过fs的readFile方法,去读取version.json文件,要是没有版本文件,咱们直接writeFileSync,直接生成一个版本文件,要是存在,咱们就读取其版本内容,并进行更新
  • 我这里定义的版本号是从1开始自增的数字

代码:

fs.readFile("./public/version.json", "utf8", (err, data) => {
        if (!err) {
          let obj;
          try {
            obj = JSON.parse(data);
            obj.version++;
          } catch (error) {
            obj = {
              version: 1
            };
          }
          // 在 JSON 序列化成功才会去同步修改 version.json 文件内容
          if (obj)
            fs.writeFileSync("./public/version.json", JSON.stringify(obj));
          // 没有找到文件,抛出异常
        } else {
          fs.writeFileSync(
            "./public/version.json",
            JSON.stringify({
              version: 1
            })
          );
        }
      });

这样,更新版本号部分就可以啦

作为部署的部分,不用担心因版本号更新的问题,导致拉取代码失败,后补方案也可以通过强拉代码去解决

part2:

获取版本号更新部分

还是老生常谈的方法,定时获取的版本号和本地缓存的版本号进行比较,根据结果是否显示更新弹窗。

// 创建一个定时控制器
let timeFun = (function () {
        let timeId = null;
        function start(fun) {
          if (timeId == null) {
            timeId = setInterval(fun, 60 * 60 * 1000);
          }
        }
        // 销毁当前定时器
        function stop() {
          clearInterval(timeId);
          timeId = null;
        }
        return { start, stop };
      })();

由于,首次进入页面的时候,获取的资源是最新的,所以,首次进入,仅记录最新版本号,并不进行刷新弹窗提示。

代码:

const handler = () => {
    axios.get(location.origin + "/version.json").then(res => {
         // 获取当前版本号
            const version = localStorage.getItem("version");
            // 判断是否首次进入页面,首次进入直接更新版本号,不用提示
            const isFirstLoad = window.performance.navigation.type === 1;
            console.log(res.version, version, isFirstLoad);
            if (version != res.version && isFirstLoad) {
            // 暂停当前定时器,避免重叠后重复弹窗
              timeFun.stop();
              MessageBox.confirm("发现新版本,是否更新?", "提示", {
                closeOnClickModal: false,
                closeOnPressEscape: false,
                showClose: false,
                confirmButtonText: "更新",
                cancelButtonText: `${1}小时后提醒我!`,
                type: "warning",
              })
                .then(() => {
                  MessageBox.close();
                  // 更新版本号
                  localStorage.setItem("version", res.version);
                  location.reload();
                })
                .catch(() => {
                  MessageBox.close();
                  timeFun.start(handler);
                });
            } else {
              localStorage.setItem("version", res.version);
            }
    })
};
   timeFun.start(handler)

基本是这样就实现了

后续:

考虑到主线程的占用问题,该计时器一直在运行,且不释放,因此这是一个其中一个弊端吧。

为了

根据web-worker的相关资料,在vue下使用web-worker

建造一个执行脚本,定时获取版本号,并通过主线程传入的数据进行比对,最终是否给主线程发送更新通知

首先先创建一个脚本文件

// worker.js
let currentVersion = 0;
let timeId = null;
async function getVersion(e) {
  try {
  // 查阅了web-worker文档,支持fetch
    const response = await fetch(location.origin + "/version.json");
    const data = await response.json();
    const latestVersion = data.version;
    console.log(latestVersion, currentVersion);
    if (latestVersion !== currentVersion) {
      // 版本号有更新,发送通知
      console.log(data);
      postMessage(data.version);
    }
  } catch (error) {
    console.error("Failed to get version:", error);
  }
}
// setInterval(getVersion, timeout);

// 监听主线程发送过来的数据
addEventListener("message", ({ data }) => {
  if (data.type === "update") {
    console.log("update", data);
    currentVersion = data.version;
    timeId = setInterval(getVersion, data.timeout || 60 * 60000);
  }
  if (data.type === "handon") {
    clearInterval(timeId);
  }
  if (data.type === "destory") {
    removeEventListener("message");
  }
});

在主线程引用该脚本

由于worker的引入路径问题,因此需要借助一些worker-loader在打包的时候解析好引入路径的问题

// App.vue
import Worker from "worker-loader!./worker.js";

methods: {
    startWorker() {
      this.worker = new Worker();
      this.worker.postMessage({
        version: Number(localStorage.getItem("version")),
        type: "update",
      });
      this.worker.onmessage = ({ data }) => {
        // 判断是否首次进入页面,首次进入直接更新版本号,不用提示
        const isFirstLoad = window.performance.navigation.type === 1;
        if (isFirstLoad) {
          this.worker.postMessage({
            type: "handon",
          });
          MessageBox.confirm("发现新版本,是否更新?", "提示", {
            closeOnClickModal: false,
            closeOnPressEscape: false,
            showClose: false,
            confirmButtonText: "更新",
            cancelButtonText: `${1}小时后提醒我!`,
            type: "warning",
          })
            .then(() => {
              MessageBox.close();
              // 更新版本号
              localStorage.setItem("version", data);
              location.reload();
            })
            .catch(() => {
              MessageBox.close();
              this.worker.postMessage({
                version: Number(localStorage.getItem("version")),
                type: "update",
              });
            });
        } else {
          localStorage.setItem("version", res.version);
        }
      };
    },
    stopWorker() {
      this.worker.postMessage({
        type: "destory",
      });
      this.worker.terminate();
      this.worker = null;
    },
  },
  created() {
    this.startWorker();
  },
  beforeDestroy() {
    if (this.worker) {
      // 销毁子线程
      this.stopWorker();
    }
  },
  

以上记录一些,反思和不断更新的过程。如有建议,望各路大神指教指教!