前言
测试:我没看见新功能啊,开发同学有问题啊。
我:刷新一下呢?
测试:哦有了有了
我:😡😡😡
不行,这种对话太频繁了,测试、产品、用户、后端甚至老板,时不时就会提出这种问题。痛定思痛,一定得想个办法处理一下。
方案探索
方案1:通知平台
- 新增加一个发消息的平台
- 后端监听这个消息的数据库
- 并且利用 websocket ,在进入页面的时候就进行连接。
- 前端专门暴露一个通知框给这个 ws 使用
这样一来,不管后面想通知什么,都可以走这个平台,有很强的可复用性。
后端同学:?再弄个平台再监听再发送,我哪有那么多功夫。
我:😭😭😭
方案2:build 时追加文件
参考了这篇方案 juejin.cn/post/720774…
- 在build时,生成一个
_version.js文件,写入当前时间
const fs = require('fs');
const version = Date.now();
const content = `window._version = '${version}';`;
fs.writeFileSync('./_version.js', content);
-
将该文件加入最终生成的 index.html 中
-
去轮询
_version.js文件,观察远程的 window._version 文件是否和本地不一致
这个方案确实是靠前端可以做到的,但是这时候有同学就要问了:
imooimoo,这个方案虽好,但是有点太过复杂了。有没有更简单的方案啊?
有的兄弟有的,在刚才的方案中,有一个点尤为关键,就是轮询 目标文件,如果仔细想想,真的有必要新去建立一个文件然后轮询吗,我们就不能轮询整个前端文件进行对照吗?
举个例子,这是一个网站的所有文件:
由于是 spa 单页面设计,变动基本都在 js 里,我们只需要去看所有 js 文件是否变动就可以了。
甚至可以再进一步,我们只需要看哈希值有没有变化就行,也就是拿到所有文件的名字进行对照即可。
再再进一步,我们甚至可以只请求 html 文件,因为所有的 js 都是通过 script 标签进去的。在这里一定能提取出他们的名字
思路已经有了,开写!
整体实现
- 如何请求当前的 html 文件?查阅资料可知
const html = await fetch('/').then((res) => res.text());
- 如何解析出 script 标签
const reg = /<script(?:\s+[^>]*)?>(.*?)</script\s*>/gi;
const list: string[] = html.match(reg) || [];
最难的两步已经找到了答案,剩下的就信手拈来了。贴个完整代码:
import { Notification } from '@arco-design/web-react';
export class Updater {
oldScript: string[] = [];
intervalId: number | null = null;
constructor() {
this.init();
}
static async getScriptList() {
const html = await fetch('/').then((res) => res.text());
const reg = /<script(?:\s+[^>]*)?>(.*?)</script\s*>/gi;
const list: string[] = html.match(reg) || [];
return list;
}
static compare(oldArr: string[], newArr: string[]) {
const isSame =
oldArr.length === newArr.length &&
oldArr.every((v, i) => v === newArr[i]);
if (!isSame) {
Notification.warning({
id: 'updater',
title: '更新提示',
content: '页面有更新,请刷新页面来获取最新功能',
duration: 30 * 1000,
});
}
}
async init() {
const list: string[] = await Updater.getScriptList();
this.oldScript = list;
}
start() {
const time = 1000 * 60 * 60; // 周期为 1h
this.intervalId = window.setInterval(async () => {
const list = await Updater.getScriptList();
Updater.compare(this.oldScript, list);
this.oldScript = list; // 始终更新缓存
}, time);
}
stop() {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
}
}
非常简单,只需要添加这个文件,并启用功能即可。
const updater = new Updater();
updater.start();
大功告成,完结撒花,给个赞吧~