不靠后端,前端自己实现发版后页面提示用户版本更新
通过利用Webpack根据内容生成文件名的特性,结合fetch请求和时间戳,我们实现了一个简单但有效的版本更新提示功能。这个方案不仅解决了用户和开发者的困扰,还展示了如何在前端实现轮询检测和发布订阅模式的应用。虽然代码并不复杂,但使用面向对象的写法可以使代码更加清晰和易于维护。希望这个方案能为大家在类似场景下的开发提供一些参考和启发。
如何在前端实现版本更新提示功能
在现代Web开发中,尤其是在多部门共用的后台管理系统中,频繁的版本迭代是常态。然而,用户往往不会主动刷新页面,导致他们可能在使用旧版本的页面,从而影响功能体验。为了解决这个问题,我研究并实现了一个在前端检测版本更新并提示用户刷新的方案。本文将详细介绍这个方案的实现思路和代码细节。
整体思路
-
文件命名与缓存:项目使用Webpack + Vue2构建,配置了打包后的文件依据文件内容生成文件名。为了确保浏览器不会缓存请求,我们在请求URL中加上时间戳。
-
轮询检测:通过轮询的方式,在根页面向服务器请求当前页面的HTML内容。
-
比对脚本标签:比对当前HTML文件的
<script>
标签的src
路径是否一致。由于打包后的JS文件会以<script>
标签的形式注入到HTML页面中,因此通过比对<script>
标签可以检测到版本更新。 -
通知用户刷新:如果发现
<script>
标签的路径不一致,则通知用户刷新页面。
实现
1. 创建VersionChecker
类
我们首先创建一个VersionChecker
类,用于处理版本更新的检测和通知。
export class VersionMonitor {
constructor(options = {}) {
this.oldScriptSrc = new Set();
this.newScriptSrc = new Set();
this.listeners = {};
this.intervel = null;
this.options = options;
this.init();
this.timing(options.timer);
}
async init() {
try {
const html = await this.getHtml();
this.oldScriptSrc = this.extractScriptSrc(html);
} catch (error) {
console.error('初始化失败:', error);
}
}
async getHtml() {
try {
const response = await fetch('/?timestamp=' + new Date().getTime());
if (!response.ok) {
throw new Error('网络请求失败');
}
return await response.text();
} catch (error) {
console.error('获取 HTML 失败:', error);
return '';
}
}
extractScriptSrc(html) {
if (!html) {
return new Set();
}
const scriptSrc = new Set();
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const scripts = doc.querySelectorAll('script[src]');
scripts.forEach((script) => {
scriptSrc.add(script.src);
});
return scriptSrc;
}
on(key, fn) {
if (!key || typeof fn !== 'function') {
console.error('无效的 key 或 fn');
return this;
}
(this.listeners[key] || (this.listeners[key] = [])).push(fn);
return this;
}
compare(oldSrc, newSrc) {
if (!oldSrc || !newSrc) {
console.error('无法比较,src 集合为空');
return;
}
if (oldSrc.size !== newSrc.size) {
this.notifyUpdate();
return;
}
for (const src of oldSrc) {
if (!newSrc.has(src)) {
this.notifyUpdate();
return;
}
}
this.notifyNoUpdate();
}
notifyUpdate() {
if (this.listeners.update) {
this.listeners.update.forEach((fn) => fn());
}
}
notifyNoUpdate() {
if (this.listeners['no-update']) {
this.listeners['no-update'].forEach((fn) => fn());
}
}
timing(time = 30000) {
this.intervel = setInterval(async () => {
try {
const newHtml = await this.getHtml();
this.newScriptSrc = this.extractScriptSrc(newHtml);
this.compare(this.oldScriptSrc, this.newScriptSrc);
} catch (error) {
console.error('轮询失败:', error);
}
}, time);
}
destroy() {
clearInterval(this.intervel);
}
async restart() {
try {
await this.init();
this.timing(this.options.timer);
} catch (error) {
console.error('重启失败:', error);
}
}
}
2. 在App组件中使用
在Vue的App
组件中,我们实例化VersionChecker
类,并设置更新通知。
async mounted() {
if (process.env.VUE_APP_ENV === 'pro') { // 通过环境变量判断是否是生产环境
// 实例化该类
const up = new VersionChecker({
timer: 1000 * 60 * 5 // 传入定义好的轮询时间
});
// 更新通知
up.on('update', () => {
up.destroye(); // 每次通知先销毁 保证计时器不叠加
// 弹窗提示用户
this.$confirm('系统有更新,点击刷新按钮立即刷新', '提示', {
type: 'info',
cancelButtonText: '知道了,稍后手动刷新!',
confirmButtonText: '刷新',
closeOnClickModal: false, // 是否点击遮罩(点击空白处)关闭
showClose: false // 是否显示右上角的x
})
.then(() => {
location.reload(); // 点击刷新
up.restart(); // 重新开始轮询
})
.catch((err) => {
console.log(err);
// 用户选择稍后刷新 重新开始轮询
up.restart();
});
});
}
}
1.使用 DOMParser 解析 HTML,并提取 src 属性,使用 Set 存储和比对 src 属性。
2.将比对逻辑拆分为 compare、notifyUpdate 和 notifyNoUpdate 方法,代码更易读和维护。
3.如果需要检测其他属性或顺序,可以轻松扩展 extractScriptSrc 和 compare 方法。
总结
通过利用Webpack根据内容生成文件名的特性,结合fetch
请求和时间戳,我们实现了一个简单但有效的版本更新提示功能。这个方案不仅解决了用户和开发者的困扰,还展示了如何在前端实现轮询检测和发布订阅模式的应用。
虽然代码并不复杂,但使用面向对象的写法可以使代码更加清晰和易于维护。希望这个方案能为大家在类似场景下的开发提供一些参考和启发。
更多推荐
所有评论(0)