结论
先说一下结论,就是这个方案的代码可能不太严谨,如果项目比较大且重要,建议需要稍作修改或者寻找其他方案~
但是优点在于:复制可用,成本较低,如果觉得对你有帮助的话,请点个免费的赞噢~
事情起因
事情是这样的,有一天,本前端仔在发布了项目的新版本到线上的时候,不到一会儿,就听到老板突然大喊起来:前端仔快过来看看,咱们的网站怎么用不了了???
我一个激灵,赶紧去排查原因,打开控制台发现项目的各种资源加载失败了,请求状态显示 404 ~
一、事故原因分析
联想到刚刚发布的新版本,应该就是因为线上的项目更新了,但是用户还停留在旧的页面上,
因为现在的项目开发,各种脚手架工具默认会在打包的时候给各种资源文件加上唯一的哈希后缀(这是为了防止浏览器缓存,确保用户的请求能拿到最新的资源)
但是如果用户正在旧的网站上浏览,而旧的请求资源的url地址已经不存在服务上了,就会出现资源加载失败、打不开页面等问题~
二、解决方案
enn嗯,,这个问题其实也是一个前端常见的一个问题了,无非就是让用户浏览器感知并进行刷新,拿到最新的资源地址~
经过我的各种搜查,发现还是轮询最适合,主要是简单粗暴,容易理解,毕竟本前端仔的脑袋不是很灵活,嘿嘿~
轮询逻辑:
利用定时器
setInterval
, 设定一个时间,然后去请求我们的网站资源,再跟本地的资源进行对比,如果发现不一样了,就说明线上更新了新的版本,那么我们就可以通过window.location.reload
方法刷新当前页面~
三、适合vue项目的轮询方案
我们的目标是拿到htmL文件里面的script标签,因为我发现在vue项目中,都会生成一个src为app.xxxx.js的script标签,而中间的xxxx就是生成的哈希随机字符串,可以作为项目的版本号~
vue项目是单页面项目,通过请求拿到的主页 /idnex.html
是一个单页面html文件,文件很小才几kb,因此完全不用担心什么性能问题
原理其实很简单:
- 先获取当前页面的所有script标签,然后筛选src为app.xxxx.js的script标签,最后拿到中间xxxx哈希值
- 通过请求获取线上的index.html主页,同样获取src为app.xxxx.js的script标签并拿到xxxx哈希值
- 两个哈希值进行对比,如果不一样,说明已经更新了,刷新页面即可
四、代码部分
1、获取本地的版本号:
ini
// 查询本地的app.js标签哈希值
const getLocalHash = () => {
let localVersion = '';
let scripts = document.getElementsByTagName("script");
for(let i = 0; i < scripts.length; i++) {
let src = scripts[i].getAttribute("src");
if (src && src.indexOf("app.") != -1) {
// 正则返回中间版本号(如果没有,返回空)
let regRes = /app\.(.*?).js/;
if(regRes.test(src)) {
localVersion = regRes.exec(src)[1];
}
}
}
return localVersion
}
ok,写完了程序我们去各种vue项目的网站上看看效果
首先是 vue官网,直接控制台输入上面的函数,运行,提取成功!
然后是 element plus 官网,也可以看到成功获取了
2、线上版本号
ok,上面已经成功获取了文档中存在的版本号信息,那么,接下来就是发送请求获取线上的版本号,
为了代码能够一键复制使用,这里使用 fetch 来发送请求:
javascript
// 查询线上首页的app.js标签的哈希值
const checkHash = async () => {
return new Promise((resolve, reject) => {
// 加上时间戳,防止缓存
fetch('/?_time=' + Date.now()).then(async res => {
let html = await res.text();//转成字符串判断
//转为文档对象
let doc = new DOMParser().parseFromString(html, "text/html");
let scripts = doc.getElementsByTagName("script");
let newVersion = ''
for(let i = 0; i < scripts.length; i++) {
let src = scripts[i].getAttribute("src");
if (src && src.indexOf("app.") != -1) {
// 正则返回中间版本号(如果没有,返回空)
let regRes = /app\.(.*?).js/;
if(regRes.test(src)) {
newVersion = regRes.exec(src)[1];
}
}
}
resolve(newVersion)
}).catch(err => {
console.log('获取版本号失败', err);
reject(err)
})
})
}
老规矩,测试一下,也是成功的:
五、最后的代码(直接复制可用)
通过上面的一顿操作,我们可以用几个函数就可以分析出本地的版本号以及线上的版本号,接下来我们只要写一个定时器定时去执行,就完成了这个自动检测更新的功能
其实吧,虽然每个vue项目都默认打包生成一个 app.js 的脚本文件,但是这并不能保证它是唯一的,因此这个方法有点不够严谨,不过,道理都是一样的,只要你的项目里面有一个唯一的标识就可以了,不管是哪个~
下面我们优化一下代码,把代码复制到项目里面去定时执行就可以了(时间自己决定,我是10s一次)
建议放到 layout.vue、home.vue\index.vue等需要登陆验证才能进来的组件里去执行,这样未登录的话就不会执行了
javascript
// 获取app.js 的哈希值
const getAppHash = (scripts) => {
let localVersion = '';
for(let i = 0; i < scripts.length; i++) {
let src = scripts[i].getAttribute("src");
if (src && src.indexOf("app.") != -1) {
// 正则返回中间版本号(如果没有,返回空)
let regRes = /app\.(.*?).js/;
if(regRes.test(src)) {
localVersion = regRes.exec(src)[1];
}
}
}
return localVersion
}
// 获取本地的app.js版本号
const getLocalHash = () => {
return getAppHash(document.getElementsByTagName("script"))
}
// 获取线上的app.js版本号
const checkHash = () => {
return new Promise((resolve, reject) => {
// 加上时间戳,防止缓存
fetch('/?_time=' + Date.now()).then(async res => {
let html = await res.text();//转成字符串判断
let doc = new DOMParser().parseFromString(html, "text/html");
let newVersion = getAppHash(doc.getElementsByTagName("script"))
resolve(newVersion)
}).catch(err => {
console.log('获取版本号失败', err);
reject(err)
})
})
}
// 定时执行,自动更新逻辑(每10s检测一次)
setInterval( async () => {
// 本地
let localVersion = getLocalHash();
// 线上
let newVersion = await checkHash();
console.log('对比', localVersion, newVersion)
// 如果不一样,就进行刷新
if(localVersion != newVersion) {
console.log('有新的版本~')
window.location.reload()
}
}, 1000 * 10)
好了,终于写完了,虽然是很简单的一个功能,但是写字还是挺花时间的,求个点赞~