能不能做一个提醒用户页面更新了,需要刷新一下页面的功能,这样用户就可以体验到最新的功能。而不需要我们一个一个地去提醒。
直接上代码:
//autoUpdate.js
import { MessageBox } from 'element-ui';
let lastScripts = [];
/* 获取 html 中 js 资源名称 */
async function extractNewScripts (html) {
const scriptReg = /<script.*src=["'](?<src>[^"']+)/gm;
let result = [];
let match;
while ((match = scriptReg.exec(html))) {
result.push(match.groups.src);
console.log(result);
}
return result;
}
/* 判断浏览器是否需要更新数据 */
async function needUpdate () {
// const newScripts = await extractNewScripts(await fetch('/?_timestamp=' + Date.now()).then(resp => resp.text()));
const newScripts = await extractNewScripts(await fetch('/').then(resp => resp.text()));
if (!lastScripts.length) {
lastScripts = newScripts;
return false;
}
let result = false;
if (newScripts.length !== lastScripts.length) {
result = true;
} else {
for (let i = 0; i < lastScripts.length; i++) {
if (lastScripts[i] !== newScripts[i]) {
result = true;
break;
}
}
}
lastScripts = newScripts;
return result;
}
/* 延时时间 20s */
const DURATION = 20000;
/* 自动刷新 */
export const autoRefresh = () => {
setTimeout(async () => {
const willUpdate = await needUpdate();
console.log(willUpdate);
if (willUpdate) {
MessageBox.confirm('页面有更新,点击确定刷新页面?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// location.reload(true);来强制刷新页面并绕过缓存。如果不加 true,浏览器可能会从其缓存中获取页面
location.reload(true);
}).catch(() => { });
}
autoRefresh();
}, DURATION);
}
//vue.config.js
module.exports = {
configureWebpack: {
output: {
filename: `app-${new Date().getTime()}.js` // 修改文件名为你想要的名称
}
}
}
大致思路是这样的,在 autoRefresh,js 中有个 autoRefresh 函数,每 20s 执行一次,它会每次请求根路径,去判断获取的 html 文件,而后通过正则去判断 script 标签中 js 文件名的变化,如果有变化,就会弹框提示用户去更新。
这个逻辑应该不难理解,不过,我们还需要通过 Nginx 的配置来做适当的调整。
比如:Nginx 配置 add_header Cache-Control “no-cache”,此时浏览器不直接使用缓存,而是每次向服务器发送请求验证资源是否过期,如果没有过期使用缓存中的数据,如果过期了重新获取服务器中的数据。那么这里加时间戳就没有意义了。
再比如:Nginx 配置 add_header Cache-Control “max-age=86400”;缓存一天(可以设置长一点,我记得默认都很久的,js,css15 天的,图片 30 天,html 没有设置缓存时间),此时,在一天之内浏览器获取缓存中的数据,那么如果服务器中的数据发生了变化,此时加上时间戳就是有意义的了,因为不加时间戳,缓存没有过期的时候请求 index.html 文件的时候,还是取缓存中的数据,加上时间戳,浏览器会将其视为新的资源重新下载。
由于我们 Nginx 配置的是 add_header Cache-Control “no-cache”,就不用加时间戳了。
不过第二种做法,还有一丝丝的问题,我们来假设一下
当用户打开了页面,而后关闭,过了比过期时间还要久的时候再打开页面,在此之前服务器的资源更新了,那么页面会获取最新的数据,在判断的时候 lastScripts 首先为空,而后 20s 再次获取是最新的 html, 此时比较就没问题。
当用户打开了页面,而后关闭,此时服务器的资源更新,资源还没过期,用户打开页面,此时 lastScripts 也是存最新的 html,这样就有问题了,因为此时应该提醒用户可以刷新一下页面。如何解决?
这样用户就知道我的页面是不是最新的了。
留了一个小问题,我希望后续可以解决一下,大家有没有解决这个问题的办法,期待你的分享。
上面的方案只是其中的一种,我们还可以做其他的,比如:websocket 。比如:后台写个类似 APP 更新的功能,只不过这里需要轮询。但这两种方案都需要后端配合,还是比较麻烦的,而且一个是需要长连接,一个是需要访问数据库,更加的消耗资源。所以之前的方案应该是一个更加优化的解法,如果把之前留的那个小问题解决了,我觉得它会是这些方法中最优的解法,因为不需要进行验证资源是否过期了,减少了很多 304 请求。