页面自动检测更新
每次打开页面时,自动检测是否有新的版本发布,如果有新的版本发布,提示用户刷新页面。
纯前端实现方案
使用轮询的方式,定时检测是否有新的版本发布。
1. 复杂方案
页面加载时,定时发送请求检测是否有新的版本发布。 重新请求 index.html 文件,检测引用的 js 文件的地址是否改变(也就是与当前引用的 js 文件地址不一致) 重新请求 index.html 文件,检测引用的 css 文件的地址是否改变(也就是与当前引用的 css 文件地址不一致)
如果有新的版本发布,对应的 js 文件或 css 文件地址有改变,提示用户刷新页面。 提示用户刷新页面的原因是,js 文件或 css 文件有改变,说明有新的功能或样式,需要刷新页面才能生效。
用户点击刷新按钮后,重新加载页面。 重新加载页面的原因是,需要加载新的 js 文件或 css 文件,才能生效。 重新加载页面的方式是,使用 location.reload()方法重新加载页面。
// 页面加载时,定时发送请求检测是否有新的版本发布
setInterval(() => {
// 重新请求index.html文件
const resp = await fetch('/? timestamp='+ Date.now()).then((resp) => {
return resp.text()
})
// 解析html文件
const parser = new DOMParser()
const doc = parser.parseFromString(resp, 'text/html')
// 获取当前页面引用的js文件地址
const jsFiles = doc.querySelectorAll('script[src]')
// 获取当前页面引用的css文件地址
const cssFiles = doc.querySelectorAll('link[rel="stylesheet"]')
// 遍历js文件地址
jsFiles.forEach((jsFile) => {
// 对比js文件地址是否改变
if (jsFile.src !== jsFile.getAttribute('src')) {
// 提示用户刷新页面
alert('检测到新的js文件,需要刷新页面才能生效')
// 刷新页面
location.reload()
}
})
// 遍历css文件地址
cssFiles.forEach((cssFile) => {
// 对比css文件地址是否改变
if (cssFile.href !== cssFile.getAttribute('href')) {
// 提示用户刷新页面
alert('检测到新的css文件,需要刷新页面才能生效')
// 刷新页面
location.reload()
}
})
}, 1000 * 60 * 5) // 每5分钟检测一次2. 简易方案
轮询时通过请求系统首页网址(请求方式为
HEAD)获取到响应头。从响应头中获取
etag属性,比对该属性前后是否一致。 如果不一致,说明资源有更新,需要提醒用户刷新页面。 如果一致,说明资源未更新,无需刷新页面。
const currentEtag = await fetch('your-website.com/index.html',{
method: 'HEAD'
}).then(res=>res.headers.get('etag'))
localStorage.setItem('etag', currentEtag)
// 页面加载时,定时发送请求检测是否有新的版本发布
setInterval(() => {
// 获取etag属性
const newEtag = await fetch('your-website.com/index.html',{
method: 'HEAD'
}).then(res=>res.headers.get('etag'))
// 对比etag属性是否改变
if (newEtag !== localStorage.getItem('etag')) {
// 提示用户刷新页面
alert('检测到新的资源,需要刷新页面才能生效')
// 刷新页面
location.reload()
}
}, 1000 * 60 * 5) // 每5分钟检测一次后端实现方案
1. HTTP 轮询
实现步骤 后端提供一个接口,返回当前前端资源的版本号(例如:/api/version)。
前端定时(例如每 5 分钟)请求该接口,获取最新版本号。
前端将获取的版本号与本地存储的版本号进行比较。
如果版本号不一致,则提示用户刷新页面。
后端代码示例(Node.js Express)
// 假设我们有一个版本号,这里用简单的时间戳模拟,实际项目中可以是打包时生成的版本号
let version = Date.now();
app.get("/api/version", (req, res) => {
res.json({ version });
});
// 当有新版发布时,更新version(可以通过发布脚本或管理接口来更新)
// version = newVersion;前端代码示例
// 在页面加载时,获取初始版本号并存储
let currentVersion = localStorage.getItem("version") || "";
// 定时检查版本
setInterval(async () => {
const resp = await fetch("/api/version");
const data = await resp.json();
if (data.version && currentVersion !== data.version) {
// 提示用户刷新
if (confirm("检测到新版本,是否刷新页面?")) {
localStorage.setItem("version", data.version);
location.reload();
}
}
}, 1000 * 60 * 5); // 5分钟检查一次2. SSE(Server-Sent Events)
SSE 允许服务器主动向客户端推送数据,适用于版本更新提示这种服务器需要主动通知客户端的场景。
实现步骤 后端提供一个 SSE 接口,用于推送版本信息。
前端通过 EventSource 连接该接口。
当版本更新时,后端向所有连接的客户端推送新版本号。
前端接收到新版本号后,与本地存储的版本号比较,如果不一致则提示用户刷新。
后端代码示例(Node.js Express)
const express = require("express");
const app = express();
let version = Date.now();
let clients = []; // 存储所有连接的客户端
app.get("/sse", (req, res) => {
// 设置SSE所需的头
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");
// 发送当前版本号
res.write(`data: ${JSON.stringify({ version })}\n\n`);
// 将客户端响应对象保存起来
clients.push(res);
// 当客户端关闭连接时,清除对应的响应对象
req.on("close", () => {
clients = clients.filter((client) => client !== res);
});
});
// 当版本更新时,向所有客户端推送新版本号
function updateVersion(newVersion) {
version = newVersion;
clients.forEach((client) => {
client.write(`data: ${JSON.stringify({ version: newVersion })}\n\n`);
});
}前端代码示例
// 连接SSE接口
const eventSource = new EventSource("/sse");
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
const currentVersion = localStorage.getItem("version");
if (data.version && currentVersion !== data.version) {
// 提示用户刷新
if (confirm("检测到新版本,是否刷新页面?")) {
localStorage.setItem("version", data.version);
location.reload();
}
}
};3. WebSocket
WebSocket 提供了全双工通信通道,适用于需要频繁交互的场景,但用于版本更新提示也是可行的。
实现步骤 后端启动 WebSocket 服务,维护连接和版本信息。
前端通过 WebSocket 连接服务器。
当版本更新时,后端通过 WebSocket 向所有连接的客户端推送新版本号。
前端接收到新版本号后,与本地存储的版本号比较,如果不一致则提示用户刷新。
后端代码示例(使用 ws 库)
const WebSocket = require("ws");
const wss = new WebSocket.Server({ port: 8080 });
let version = Date.now();
wss.on("connection", (ws) => {
// 当有客户端连接时,发送当前版本号
ws.send(JSON.stringify({ type: "version", version }));
// 当版本更新时,向所有客户端推送新版本号
// 假设有一个updateVersion函数,当版本更新时调用
});
function updateVersion(newVersion) {
version = newVersion;
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({ type: "version", version: newVersion }));
}
});
}前端代码示例
const ws = new WebSocket("ws://localhost:8080");
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === "version") {
const currentVersion = localStorage.getItem("version");
if (data.version && currentVersion !== data.version) {
// 提示用户刷新
if (confirm("检测到新版本,是否刷新页面?")) {
localStorage.setItem("version", data.version);
location.reload();
}
}
}
};后端实现方案优劣势分析:
HTTP 轮询:实现简单,但有一定的延迟,并且可能产生不必要的请求。
SSE(Server-Sent Events):服务器可以主动推送,适用于实时性要求不高的场景,连接保持时间长,但兼容性较好。
WebSocket:全双工通信,实时性最高,但需要服务器和客户端都支持 WebSocket,并且需要维护连接,占用资源多。