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


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

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

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

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

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

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

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

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

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

    下面以 vue 项目为例

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

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

    let firstV = [] //记录初始获得的 script 文件字符串
    let currentv = [] //记录当前获得的 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>/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() 得到的结果示例如下:

    [
        '',
        '',
    ]
    

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

    [
        '',
        '',
    ]
    

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

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

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

    1.png

    这里可以轮询打包后的 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 缓存
    
    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
    
    
    <meta http-equiv="Pragma" content="no-cache"> 
    
    
    <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,优势是只需要取响应头中的字段,服务器不需要返回响应体,节约资源;

  • 相关阅读:
    2022吃透这份“Java面试八股文+各大厂的面试真题”金九银十稳了
    算法总结:递归优化,二路归并,快速幂
    HTTP协议再邂逅:HTTP协议结构和通讯原理
    Java语言怎样接入代码demo
    配置中心
    AI电话机器人能否代替人工?优缺点介绍
    【华为OD机试】删除目录
    肠道微生物群与帕金森以及相关影响因素
    CUDA编程:矩阵乘运算从CPU到GPU
    Elasticsearch:搜索架构
  • 原文地址:https://www.cnblogs.com/zsxblog/p/17958326