先说一下结论,就是这个方案的代码可能不太严谨,如果项目比较大且重要,建议需要稍作修改或者寻找其他方案!但是优点在于:复制可用,成本较低。
事情是这样的,有一天,本前端仔在发布了项目的新版本到线上的时候,不到一会儿,就听到老板突然大喊起来:前端仔快过来看看,咱们的网站怎么用不了了???我一个激灵,赶紧去排查原因,打开控制台发现项目的各种资源加载失败了,请求状态显示 404 ~
联想到刚刚发布的新版本,应该就是因为线上的项目更新了,但是用户还停留在旧的页面上,因为现在的项目开发,各种脚手架工具默认会在打包的时候给各种资源文件加上唯一的哈希后缀(这是为了防止浏览器缓存,确保用户的请求能拿到最新的资源)。
但是如果用户正在旧的网站上浏览,而旧的请求资源的url地址已经不存在服务上了,就会出现资源加载失败、打不开页面等问题。
enn嗯,,这个问题其实也是一个前端常见的一个问题了,无非就是让用户浏览器感知并进行刷新,拿到最新的资源地址,经过我的各种搜查,发现还是轮询最适合,主要是简单粗暴,容易理解,毕竟本前端仔的脑袋不是很灵活,嘿嘿。
我们的目标是拿到htmL文件里面的script标签,因为我发现在vue项目中,都会生成一个src为app.xxxx.js的script标签,而中间的xxxx就是生成的哈希随机字符串,可以作为项目的版本号。vue项目是单页面项目,通过请求拿到的主页 /idnex.html 是一个单页面html文件,文件很小才几kb,因此完全不用担心什么性能问题。
// 堆代码 duidaima.com // 查询本地的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项目的网站上看看效果。
// 查询线上首页的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 的脚本文件,但是这并不能保证它是唯一的,因此这个方法有点不够严谨,不过,道理都是一样的,只要你的项目里面有一个唯一的标识就可以了,不管是哪个。
// 获取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)好了,终于写完了,虽然是很简单的一个功能,但是写字还是挺花时间的,大家有什么问题欢迎在评论区留言讨论。