实践总结 3 种前端部署后页面检测版本的方法

  • 实践总结 3 种前端部署后页面检测版本的方法已关闭评论
  • 54 次浏览
  • A+
所属分类:Web前端
摘要

领导:为什么每次项目部署后,有的用户要清缓存才能看到最新的页面我:浏览器有默认的缓存策略,如果服务器在响应头中没有禁用缓存,那么浏览器每次请求页面会先看看缓存里面有没有,有的话从缓存取,造成还是取的旧页面。正常来说,用户只需要点击刷新按钮,刷新一下页面就好了,不必清除浏览器缓存刷新。

领导:为什么每次项目部署后,有的用户要清缓存才能看到最新的页面

我:浏览器有默认的缓存策略,如果服务器在响应头中没有禁用缓存,那么浏览器每次请求页面会先看看缓存里面有没有,有的话从缓存取,造成还是取的旧页面。正常来说,用户只需要点击刷新按钮,刷新一下页面就好了,不必清除浏览器缓存刷新。

领导:为什么缓存这么严重,有的用户清除缓存刷新还是不行,关掉浏览器重新进来还是不行,要重启电脑才有效。

我:要重启电脑?这 。。。。。。用户都这样么,还是只有一小部分用户。

领导:不是所有的用户,有个别用户会出现这种情况

我:那可能得到用户电脑上看看了

每次需求投产后,因为有缓存问题导致用户看到的还是旧版内容,使用过程中出现了问题,联系我们才知道项目更新了,用户体验不好;

于是查找资料,寻找合适的方案,根据 评论区 的讨论,实践总结了下面 3 种前端部署后页面检测版本更新的方法

当检测到版本更新则及时通知用户,用户可以选择是否立即更新,并不会影响用户当前进行的业务;

下面以 vue 项目为例

1、轮询打包后的 index.html,比较生成的 js 文件的 hash

项目打包后,index.html 会包含打包后的 js 文件,这些文件的文件名包含的 hash 将会和上一次打包的不同,比较 hash 也就能判断是否有版本更新;

let firstV = [] //记录初始获得的 script 文件字符串 let currentv = [] //记录当前获得的 script 文件字符串  // 获得的文件字符串类似这样 `<script src="/js/chunk-vendors.1234fff.js"></script>`  async function getHtml() { let res = await axios.get('/index.html?date=' + Date.now())     if (res.status == '200') {         let text = res.data         if (text) {             // 解析 html 内容,匹配 script 字符串             let reg = /<script([^>]+)></script>/ig             return text.match(reg)          }     }     return [] } function isEqual(a, b) {     return a.length = Array.from(new Set(a.concat(b))).length }  export async function checkIfNewVersion() {      firstV = await getHtml()      window.checkVersionInterval && clearInterval(window.checkVersionInterval)      window.checkVersionInterval = setInterval(async () =>{          currentV = await getHtml()         console.log(firstV,currentv)         // 当前 script hash 和初始的不同时,说明已经更新         if(!isEqual(firstV, currentv)) {             console.log('已更新')         }     },3000) }  // 文档可见时检测版本是否更新 document.addEventListener("visibilitychange", () => {   if (document.visibilityState === "visible") {     checkIfNewVersion();   } else {     window.checkVersionInterval && clearInterval(window.checkVersionInterval)   } }); 

getHtml() 得到的结果示例如下:

[     '<script src="/js/chunk-vendors.1234fff.js"></script>',     '<script src="/js/app.1234fff.js"></script>', ] 

改动了一点业务代码后,再次打包,上面 app.js 的 hash 就会发生变化

[     '<script src="/js/chunk-vendors.1234fff.js"></script>',     '<script src="/js/app.12ed5ca.js"></script>', ] 

比较两个的结果,如果结果不一样,则代表有版本更新。

2、HEAD 方法轮询响应头中的 etag

ETag 是资源的特定版本的标识符。当资源内容发生变化时,会生成新的 ETag
HEAD 方法请求资源的响应头信息,服务器不会返回响应体,可以节省带宽资源;

实践总结 3 种前端部署后页面检测版本的方法

这里可以轮询打包后的 index.html,取两次响应头中的 eTag 比较,如果不同,说明版本更新了;前提是服务器没有禁用缓存。

let firstEtag = `` //记录第一次进来请求获得的 etag let currentEtag = `` //记录当前的 etag,会不断的刷新  async function getEtag(){     let res = await axios.head('/index.html')     if(res.status == '200'){         if(res.headers && res.headers.etag){             return res.headers.etag         }     }     return '' }  export async function checkEtag() {      firstEtag = await getEtag()      window.checkEtagInterval && clearInterval(window.checkEtagInterval)      window.checkEtagInterval = setInterval(async() =>{         // 每隔一定时间请求最新的 etag         currentEtag = await getEtag()         // 当前最新的 currentEtag 和初始 firstEtag 进行比较,不同则说明资源更新了;         if(firstEtag && currentEtag && firstEtag!==currentEtag){             console.log('已更新')         }     },3000) }  // 文档可见时检测版本是否更新 document.addEventListener("visibilitychange", () => {   if (document.visibilityState === "visible") {     checkEtag();   } else {     window.checkEtagInterval && clearInterval(window.checkEtagInterval)   } }); 

3、监听 git commit hash 变化

项目改动提交 git 时会生成唯一的 hash 字符串,将最近提交的 commit hash 作为版本号保存在一个 json 文件中;通过轮询 json 文件,检测里面的版本号是否和上次不同,不同则表示有版本更新;

监听 git commit hash 变化的好处是只要投产的版本有 git 提交记录,而不管静态文件变化还是代码变化,都能检测到版本更新;

在 vue.config.js 中引入 git-revision-webpack-plugin,该插件可获取到项目本地 git 的最新提交 commit hash

const GitRevisionPlugin  = require('git-revision-webpack-plugin') const gitRevision = new GitRevisionPlugin()  const { writeFile , existsSync } = require('fs') if(existsSync('./public')){     fs.writeFile(         './public/version.json',          `{"commitHash":${JSON.stringify(gitRevision.commithash())}`,          (error) =>{}     ) } 

上面代码使用 gitRevision.commithash() 获取 commit hash,将其存入到 public/versionHash.json 文件中;

项目打包会执行上面的代码,生成后的 'versionHash.json' 文件类似这样

// 示例 { "commitHash" : "234fjsdr322f32f322f32f3g32g23jglk32gjkl32lg3" } 

项目改动后,提交改动的地方后,再次打包,会将最新的 commit hash 存入到 public/versionHash.json

// 示例 { "commitHash" : "234fjsdr322f3eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" } 

然后在页面中轮询 '/versionHash.json',比较 commit hash ,检测是否有更新

let firstCommitHash = `` let currentCommitHash = ``  async function getCommitHash() {     // 避免浏览器缓存加上时间戳参数     let res = await axios.get('/versionHash.json?date=' + Date.now())     if (res.status == '200') {         if (res.data && res.data.commitHash) {             return res.data.commitHash         }     }     return '' }  export async function checkCommitHash() {      firstCommitHash = await getCommitHash()      window.checkCommitHash && clearInterval(window.checkCommitHash)      window.checkCommitHash = setInterval(async () => {         // 轮询 versionHash.json 文件         currentCommitHash = await getCommitHash()          if (firstCommitHash && currentCommitHash && firstCommitHash !== currentCommitHash) {              console.log('已更新')             // 作相应处理         }      }, 3000) } 

关于检测版本更新的时机

检测时机,我觉得有三种比较合适,可以灵活搭配上面的方法使用

  • 资源加载错误时(常常发生在切换菜单时),检测版本更新
  • 路由切换发生错误时(也发生在切换菜单时或者当前页面引用其他路由时),检测版本更新
  • 监听 visibilitychange + focus 事件
1、资源加载错误时

前端部署后,某些资源已经更新,当切换菜单时,可能会出现资源加载失败的错误(404)。此时可以使用 addEventListener('error') 捕获资源加载错误

window.addEventListener('error',(event) =>{     // 检测版本更新     // window.location.reload() },true) 
2、路由切换发生错误时

和上面的 addEventListener('error') 捕获资源加载错误类似, vue-routerrouter.onError() 方法可以捕获到路由加载的错误。

路由切换时某些资源加载失败,会抛出 Loading chunk chunk-xxxx failed,可以用正则匹配它并作相应处理;

router.onError((error) =>{     let reg = /Loading.*?failed/g     if(reg.test(error)){         // 检测版本更新         // window.location.reload()     } }) 
3、监听 visibilitychange + focus 事件

visibilitychange:当其选项卡的内容变得可见或被隐藏时,会在 document 上触发 visibilitychange 事件。

当用户导航到新页面、切换标签页、关闭标签页、最小化或关闭浏览器,或者在移动设备上从浏览器切换到不同的应用程序时,该事件就会触发,其 visibilityState 为 hidden

在 pc 端,从浏览器切换到其他应用程序并不会触发 visibilitychange 事件,所以加以 focus 辅佐;当鼠标点击过当前页面(必须 focus 过),此时切换到其他应用会触发页面的 blur 实践;再次切回到浏览器则会触发 focus 事件;

document.addEventListener("visibilitychange", () => {     if (document.visibilityState === "visible") {                  // 开始检测更新     } else {                  // 结束检测更新     } });  document.addEventListener('focus',() =>{      // 开始检测更新 }) 

关于禁用缓存

禁用 html 缓存
<!-- HTTP/1.1 --> <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">  <!-- HTTP/1.0; 与 Cache-Control: no-cache 效果一致 --> <meta http-equiv="Pragma" content="no-cache">   <!-- 如果在 Cache-Control 设置了 "max-age" 或者 "s-max-age" 指令,那么 `Expires` 头会被忽略。--> <meta http-equiv="Expires" content="0"> 

如果只在 html 中设置这个的话,只在 IE 中有效;若要在其他浏览器中生效,则需要对服务器设置禁用缓存;

nginx 设置禁用缓存
// 配置 html 和 htm 文件不缓存 location / {     root   html;     index  index.html index.htm;     add_header Cache-Control "no-cache,no-store,must-revalidate"; } 

总结

本文总结了 3 种前端部署后页面检测版本更新的方法;

  • 轮询打包后的 index.html,比较生成的 js 文件的 hash
  • HEAD 方法轮询响应头中的 etag
  • 监听 git commit hash 变化

3 种都有用武之地,看具体场景和需求;

监听 git commit hash 变化优势是可以检测到静态资源的变化;

HEAD 方法轮询响应头中的 etag,优势是只需要取响应头中的字段,服务器不需要返回响应体,节约资源;