背景
在中后台SPA项目里,在发布了新版本后,由于浏览器缓存、不同路由文件懒加载等的机制存在,此时用户浏览器所加载到的文件,并非是最新的代码,甚至可能是新旧版本代码杂糅在一块,从而可能引发一些问题。如何在项目部署之后,提醒用户有版本更新,并且引导用户刷新页面,以获取最新的版本文件?
实现思路
在项目打包时,生成一个随机数在version.json文件里,然后去轮询这个文件,如果当前随机数和上次随机数不同,则触发更新提示 关于轮询,有 setTimeout、setInterval、window.requestAnimationFrameAPI可以实现,在浏览器标签页面进入休眠时(例如电脑edge浏览器)、浏览器App进入后台时(手机),定时器可能会停止运行或代码运行变慢,而且由于消息队列的原因,setTimeout、setInterval可能并不能保证真正执行代码的时间 不过我们的场景对于定时器并没有太高的要求,因此选择setInterval即可
实现webpack插件
/config/generate-version-plugin.ts
import fs from "fs";
import path from "path";
import { type webpack } from "umi";
const NAME = "update-version";
function generateFile(path: string, content: string) {
fs.writeFileSync(path, content);
}
class GenerateVersionPlugin {
options: { versionFileName?: string; keyName?: string } = {};
version: string = "";
constructor(options: { versionFileName?: string; keyName?: string }) {
this.options = {
// json 版本文件名称
versionFileName: "update_version.json",
// json key 值
keyName: "UPDATE_VERSION",
...options,
};
this.version = `${Date.now()}`;
}
apply(compiler: webpack.Compiler) {
compiler.hooks.beforeRun.tap(NAME, () => {
console.log(process.env.NODE_ENV);
console.log("before run");
// 生成的版本 json 文件建议放置在 public 文件夹下
const filePath = path.resolve("public", this.options.versionFileName as string);
console.log(filePath);
// 生成文件
generateFile(filePath, `{"${this.options.keyName}": "${this.version}"}`);
});
compiler.hooks.done.tap(NAME, () => {
console.log("done ...");
});
}
}
export default GenerateVersionPlugin;
注册webpack插件
/config/config.ts
import proxy from "api/proxy";
import { defineConfig } from "umi";
import GenerateVersionPlugin from "./generate-version-plugin";
export default defineConfig({
chainWebpack(memo, { env }) {
if (env === "production") {
memo.plugin("GenerateVersionPlugin").use(new GenerateVersionPlugin({}));
}
}
});
轮询version.json文件
/**
* 读取到更新json文件版本内容
*/
function fetchUpdateVersionFile() {
return new Promise((resolve, reject) => {
// 注意:文件请求路径 /update_version.json,是相对于在 public 文件下的 index.html 的位置而言的,/update_version.json 代表 update_version.json 文件与 index.html 文件夹同目录。
fetch(`/update_version.json?_v=${Date.now()}`)
.then((res) => {
// console.log(res.body);
return res.body;
})
.then((body) => {
const reader = body?.getReader();
reader
?.read()
.then((val: any) => {
let str = "";
for (let i = 0; i < val.value.length; i++) {
str += String.fromCharCode(val.value[i]);
}
return JSON.parse(str);
})
.then((json) => {
resolve(json);
})
.catch((err) => {
reject(err);
});
})
.catch((err) => {
reject(err);
});
});
}
打开更新提示
import { Modal } from "antd";
function to(promise: Promise<any>) {
return promise
.then((data) => [null, data])
.catch((err) => {
return [err, undefined];
});
}
let currentVersion = "";
let timer: NodeJS.Timeout | null = null;
/**
* 打开更新提示
*/
export function openUpdateVersionNotify() {
fetchUpdateVersionFile().then(
(res) => {
console.log("版本号:", res);
timer = setInterval(async () => {
const [err, res] = await to(fetchUpdateVersionFile());
if (err) return;
console.log(res);
if (!currentVersion) {
currentVersion = res["UPDATE_VERSION"];
}
if (res["UPDATE_VERSION"] !== currentVersion) {
console.log("版本更新了。。。");
clearInterval(timer as NodeJS.Timeout);
Modal.confirm({
title: "温馨提示",
content: (
<div>
检测到新的系统更新,包含了性能提升与全新功能,为了您获得更好的体验,建议立即更新。
</div>
),
okText: "立即更新",
cancelText: "稍后更新",
onOk() {
location.reload();
},
});
}
}, 1000 * 60);
},
(err) => {
console.log("更新版本:", err);
},
);
}
在app.ts入口文件执行
app.ts
if (process.env.NODE_ENV === "production") {
openUpdateVersionNotify();
}
Comments