前端重新部署通知用户刷新网页
痛点、应用场景
当页面迭代上线之后,用户停留在老页面(更新之后=》加入浏览器标签---访问页面、不加入浏览器标签--访问页面::涉及系统权限是否过期),用户不知道网页重新部署了,体验不到新功能 或者跳转页面时候 有时 js 连接 hash 变了导致报错跳不过去
应用场景
功能迭代前端页面重新部署 用户第一时间访问新功能
痛点
页面更新之后,不刷新页面的情况下,用户如何快速访问到新功能或者告知用户有新功能更新,不能老是提醒用户刷新 不友好 不智能 不专业
解决方案
后端支持
使用 webSocket 和后端进行实时通讯,前端页面部署完之后,后端给通知,前端给 Message 进行提示; 可以优化使用 EvnentSource 或 socket 智能后端往前端推送消息,前端无法给后端发送通信,此需求也不需要给后端发送;
前端自己实现
- 对比 meta 标签中 version:通过往 html 文件 插入 meta 标签 带上 version 版本号,轮询 对比 新更新页面和旧页面 version,当 version 不一样时,提醒用户页面有更新 做操作(刷新页面)
- 对比引入 js 文件 hash 值不同:单页面文件都需要通过打包成 html、js、静态文件,轮询 对比 html 文件中引入 js 文件的 hash 值,当值不同时,提醒用户页面有更新 做操作(刷新页面)
两者区别
- 若 html 中没有插入 version 则需要手动引入 sethtml 插件 加入 meta 标签
- 系统框架不一样 vue3 vue2 webpack vite 打包方式不一致 则做不到开箱即用
- 插入 version 有可能不更新 version
- 根据每次打包后 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')
},
});
参考链接: