前端实现版本更新自动检测✅

06-01 1012阅读

前端实现版本更新自动检测✅

🤖 作者简介:水煮白菜王,一位前端劝退师 👻

👀 文章专栏: 前端专栏 ,记录一下平时在博客写作中,总结出的一些开发技巧和知识归纳总结✍。

感谢支持💕💕💕

目录

  • 一、背景
  • 二、实现原理
    • 2.1 逻辑
    • 2.2 一些好处
    • 三 、具体实现
      • 3.1 封装
      • 3.2 关键解析
        • 哈希获取:
        • 对比逻辑:
        • 四、全部代码🚀🚀🚀
          • 4.1 vue3
          • 4.2 vue2
          • 五、一些问题事项
            • 5.1 可能出现的问题
            • 5.2 浏览器兼容

              一、背景

              在现代Web应用中,部署前端版本更新后及时提醒用户进行页面刷新是必要的。由于单页面应用(SPA)的路由特性和浏览器缓存机制,用户浏览器可能不会自动加载最新的代码资源。这会导致用户未能体验到最新的功能变化,甚至遇Bug或不一致的行为。通过实现一种自动检测机制来提醒用户有新的版本,并引导其刷新页面,可以有效地解决这个问题,保证所有用户都能及时使用最新版应用的功能。

              二、实现原理

              2.1 逻辑

              通过对比构建打包出文件的哈希值变化实现版本检测:

              1. 定时轮询:每分钟检查静态资源变化
              2. 哈希对比:通过解析HTML中script标签指纹判断更新
              3. 强制刷新:检测到更新后提示用户刷新页面
              // 核心对比逻辑
              const isChanged = (oldSet, newSet) => {
                return oldSet.size !== newSet.size || 
                       ![...oldSet].every(hash => newSet.has(hash))
              }
              

              2.2 一些好处

              • 通用性强:适用于任意前端框架
              • 无侵入式检测:不依赖构建工具配置
              • 用户可控:提示框让用户选择刷新时机
              • 精准检测:通过对比script标签内容哈希值
              • 低资源消耗:每分钟检测一次,单次请求性能消耗低

                三 、具体实现

                3.1 封装

                // useVersionHash.js 核心实现
                export default function useVersionHash() {
                  // 状态管理
                  const timerUpdate = ref(null)
                  let scriptHashes = new Set()
                  // 生命周期
                  onMounted(() => startTimer())
                  onBeforeUnmount(() => stopTimer())
                  // 业务方法
                  const fetchScriptHashes = async () => { /*...*/ }
                  const compareScriptHashes = async () => { /*...*/ }
                  
                  return { compareScriptHashes }
                }
                

                3.2 关键解析

                哈希获取:

                const fetchScriptHashes = async () => {
                  const html = await fetch('/').then(res => res.text())
                  const scriptRegex = /]*)?>(.*?)/gi
                  return new Set(html?.match(scriptRegex) || [])
                }
                

                对比逻辑:

                if (scriptHashes.size === 0) {
                  // 初始化基准值
                  scriptHashes = newScriptHashes  
                } else if (
                  scriptHashes.size !== newScriptHashes.size ||
                  ![...scriptHashes].every(hash => newScriptHashes.has(hash))
                ) {
                  // 触发更新流程
                  stopTimer()
                  showUpdateDialog()
                }
                

                四、全部代码🚀🚀🚀

                4.1 vue3

                1、use-version-update.js具体逻辑

                // @/utils/use-version-update.js
                import { ref, onMounted, onBeforeUnmount } from 'vue'
                import { ElMessageBox } from 'element-plus'
                let scriptHashes = new Set()
                const timerUpdate = ref(null)
                export default function useVersionHash() {
                  const isProduction = import.meta.env.MODE === 'production'
                  const fetchScriptHashes = async () => {
                    try {
                      const html = await fetch('/').then((res) => res.text())
                      const scriptRegex = /]*)?>(.*?)/gi
                      return new Set(html?.match(scriptRegex) || [])
                    } catch (error) {
                      console.error('获取脚本哈希失败:', error)
                      return new Set()
                    }
                  }
                  const compareScriptHashes = async () => {
                    try {
                      const newScriptHashes = await fetchScriptHashes()
                      if (scriptHashes.size === 0) {
                        scriptHashes = newScriptHashes
                      } else if (
                        scriptHashes.size !== newScriptHashes.size ||
                        ![...scriptHashes].every(hash => newScriptHashes.has(hash))
                      ) {
                        stopTimer()
                        updateNotice()
                      }
                    } catch (error) {
                      console.error('版本检查失败:', error)
                    }
                  }
                  const updateNotice = () => {
                    ElMessageBox.confirm(
                      '检测到新版本,建议立即更新以确保平台正常使用',
                      '更新提示',
                      {
                        confirmButtonText: '确定',
                        cancelButtonText: '取消',
                        type: 'warning'
                      }
                    ).then(() => window.location.reload())
                  }
                  const startTimer = () => {
                    if (!isProduction) return
                    timerUpdate.value = setInterval(compareScriptHashes, 60000)
                  }
                  const stopTimer = () => {
                    timerUpdate.value && clearInterval(timerUpdate.value)
                  }
                  onMounted(startTimer)
                  onBeforeUnmount(stopTimer)
                  return { compareScriptHashes, updateNotice }
                }
                

                2、引入use-version-update.js

                // App.vue
                import versionUpdatefrom '@/utils/use-version-update.js'
                export default {
                  setup() {
                    const { updateNotice } = versionUpdate()
                    return { updateNotice }
                  }
                }
                

                3、Vite 相关配置

                // vite.config.js
                import { defineConfig } from 'vite'
                export default defineConfig({
                  build: {
                    rollupOptions: {
                      output: {
                         // 主入口文件命名规则
                        entryFileNames: 'js/[name]-[hash:8].js',
                        
                        // 代码分割块命名规则
                        chunkFileNames: 'js/[name]-[hash:8].js',
                        
                        // 静态资源文件命名规则
                        assetFileNames: ({ name }) => {
                          const ext = name?.split('.').pop()
                          return `assets/${ext}/[name]-[hash:8].[ext]`
                        }
                      }
                    },
                    // 启用文件哈希的manifest生成
                    manifest: true
                  }
                })
                

                也可以将use-version-update写成以JS、TS模块化封装,在入口文件中main.ts引入

                // use-version-update.ts
                export const versionUpdate = () => {
                  ... 具体处理逻辑
                }
                // main.ts
                import { versionUpdate} from "@/utils/use-version-update"
                if (import.meta.env.MODE == 'production') {
                  versionUpdate()
                }
                

                4.2 vue2

                1、use-version-update.js具体逻辑

                /*
                 * @Author: baicaiKing
                 * @Date: 2025-01-02 13:50:33
                 * @LastEditors: Do not edit
                 * @LastEditTime: 2025-01-03 09:40:36
                 * @FilePath: \code\src\utils\use-version-update.js
                 */
                // 存储当前脚本标签的哈希值集合
                let scriptHashes = new Set();
                let timerUpdate = undefined;
                export default {
                    data() {
                        return {
                        };
                    },
                    created() {
                    },
                    mounted() {
                        // 每60秒检查一次是否有新的脚本标签更新
                        if (process.env.NODE_ENV === 'production') { // 只针对生产环境
                            timerUpdate= setInterval(() => {
                                this.compareScriptHashes()
                            }, 60000);
                        }
                    },
                    beforeDestroy() {
                        clearInterval(timerUpdate);
                        timerUpdate = null;
                    },
                    methods: {
                        /**
                         * 从首页获取脚本标签的哈希值集合
                         * @returns {Promise} 返回包含脚本标签的哈希值的集合
                         */
                        async fetchScriptHashes() {
                            // 获取首页HTML内容
                            const html = await fetch('/').then((res) => res.text());
                            // 正则表达式匹配所有标签
                            const scriptRegex = /]*)?>(.*?)/gi;
                            // 获取匹配到的所有标签内容
                            // const scripts = html.match(scriptRegex) ?? [];
                            const scripts = html ? html.match(scriptRegex) || [] : [];
                            // 将脚本标签内容存入集合并返回
                            return new Set(scripts);
                        },
                        /**
                         * 比较当前脚本标签的哈希值集合与新获取的集合,检测是否有更新
                         */
                        async compareScriptHashes() {
                            // 获取新的脚本标签哈希值集合
                            const newScriptHashes = await this.fetchScriptHashes();
                            if (scriptHashes.size === 0) {
                                // 初次运行时,存储当前脚本标签哈希值
                                scriptHashes = newScriptHashes;
                            } else if (
                                scriptHashes.size !== newScriptHashes.size ||
                                ![...scriptHashes].every((hash) => newScriptHashes.has(hash))
                            ) {
                                // 如果脚本标签数量或内容发生变化,则认为有更新
                                console.info('已检测到更新文件', {
                                    oldScript: [...scriptHashes],
                                    newScript: [...newScriptHashes],
                                });
                                // 清除定时器
                                clearInterval(timerUpdate);
                                // 提示用户更新
                                this.updateNotice();
                            } else {
                                // 没有更新
                                console.info('未检测到更新时机', {
                                    oldScript: [...scriptHashes],
                                });
                            }
                        },
                        updateNotice() {
                            this.$confirm('检测到新版本,建议立即更新以确保平台正常使用', '更新提示', {
                                confirmButtonText: '确定',
                                cancelButtonText: '取消(自行刷新)',
                                type: 'warning'
                            }).then(() => {
                                window.location.reload();
                            }).catch(() => {
                                console.eror('用户取消刷新!');
                            });
                        }
                    },
                };
                

                2、引入use-version-update.js

                // App.vue
                import versionUpdate from "@/util/use-version-update.js";
                export default {
                  name: "app",
                  mixins: [versionUpdate],
                  data() {
                    return {};
                  },
                };
                

                3、Webpack 相关配置

                // vue.config
                module.exports = {
                  configureWebpack: {
                    output: {
                      filename: 'js/[name].[hash].js',
                      // filename: 'js/[name].[contenthash].js',
                    },
                  },
                  devServer: {
                  },
                };
                

                五、一些问题事项

                5.1 可能出现的问题

                问题现象可能原因解决方案
                检测不准确正则匹配失效更新正则表达式
                生产环境未生效环境变量配置错误检查构建配置
                跨域请求失败部署路径不匹配调整fetch请求路径
                内存泄漏定时器未正确清除使用WeakRef优化

                5.2 浏览器兼容

                可结合Service Worker实现无缝更新

                // 支持Service Worker的渐进增强方案
                if ('serviceWorker' in navigator) {
                  navigator.serviceWorker.register('/sw.js')
                    .then(reg => {
                      reg.addEventListener('updatefound', () => {
                        showUpdateNotification()
                      })
                    })
                }
                

                同时要确保服务器配置正确缓存策略,通常Nginx缓存策略默认不用打理

                在这里插入图片描述

                如果你觉得这篇文章对你有帮助,请点赞 👍、收藏 👏 并关注我!👀

                在这里插入图片描述

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

相关阅读

目录[+]

取消
微信二维码
微信二维码
支付宝二维码