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

在这里插入图片描述

🤖 作者简介:水煮白菜王,一位资深前端劝退师 👻
👀 文章专栏: 前端专栏 ,记录一下平时在博客写作中,总结出的一些开发技巧和知识归纳总结✍。
感谢支持💕💕💕

目录

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

一、背景

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

二、实现原理

2.1 逻辑

变化
未变化
应用启动
生产环境?
启动定时器
结束流程
等待60秒
获取当前脚本哈希
首次运行?
保存初始哈希
哈希变化?
停止定时器
显示更新提示
用户确认?
刷新页面
记录取消操作

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

  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 = /<script(?:\s+[^>]*)?>(.*?)<\/script\s*>/gireturn 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 = /<script(?:\s+[^>]*)?>(.*?)<\/script\s*>/gireturn 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) returntimerUpdate.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<Set<string>>} 返回包含脚本标签的哈希值的集合*/async fetchScriptHashes() {// 获取首页HTML内容const html = await fetch('/').then((res) => res.text());// 正则表达式匹配所有<script>标签const scriptRegex = /<script(?:\s+[^>]*)?>(.*?)<\/script\s*>/gi;// 获取匹配到的所有<script>标签内容// 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缓存策略默认不用打理

在这里插入图片描述
如果你觉得这篇文章对你有帮助,请点赞 👍、收藏 👏 并关注我!👀
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/897461.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

硬件基础(4):(5)设置ADC电压采集中MCU的参考电压

Vref 引脚通常是 MCU (特别是带有 ADC 的微控制器) 上用来提供或接收基准电压的引脚&#xff0c;ADC 会以该基准电压作为量程参考对输入模拟信号进行数字化转换。具体来说&#xff1a; 命名方式 在不同厂家的 MCU 中&#xff0c;Vref 引脚可能会被标记为 VREF / VREF- / VREF_…

postman接口请求中的 Raw是什么

前言 在现代的网络开发中&#xff0c;API 的使用已经成为数据交换的核心方式之一。然而&#xff0c;在与 API 打交道时&#xff0c;关于如何发送请求体&#xff08;body&#xff09;内容类型的问题常常困扰着开发者们&#xff0c;尤其是“raw”和“json”这两个术语之间的区别…

为什么要使用前缀索引,以及建立前缀索引:sql示例

背景&#xff1a; 你想啊&#xff0c;数据库里有些字段&#xff0c;它老长了&#xff0c;就像那种 varchar(255) 的字段&#xff0c;这玩意儿要是整个字段都拿来建索引&#xff0c;那可太占地方了。打个比方&#xff0c;这就好比你要在一个超级大的笔记本上记东西&#xff0c;每…

【语料数据爬虫】Python爬虫|批量采集会议纪要数据(1)

前言 本文是该专栏的第2篇,后面会持续分享Python爬虫采集各种语料数据的的干货知识,值得关注。 在本文中,笔者将主要来介绍基于Python,来实现批量采集“会议纪要”数据。同时,本文也是采集“会议纪要”数据系列的第1篇。 采集相关数据的具体细节部分以及详细思路逻辑,笔…

Android 线程池实战指南:高效管理多线程任务

在 Android 开发中&#xff0c;线程池的使用非常重要&#xff0c;尤其是在需要处理大量异步任务时。线程池可以有效地管理线程资源&#xff0c;避免频繁创建和销毁线程带来的性能开销。以下是线程池的使用方法和最佳实践。 1. 线程池的基本使用 &#xff08;1&#xff09;创建线…

SQL29 计算用户的平均次日留存率

SQL29 计算用户的平均次日留存率 计算用户的平均次日留存率_牛客题霸_牛客网 题目&#xff1a;现在运营想要查看用户在某天刷题后第二天还会再来刷题的留存率。 示例&#xff1a;question_practice_detail -- 输入&#xff1a; DROP TABLE IF EXISTS question_practice_detai…

深度学习分类回归(衣帽数据集)

一、步骤 1 加载数据集fashion_minst 2 搭建class NeuralNetwork模型 3 设置损失函数&#xff0c;优化器 4 编写评估函数 5 编写训练函数 6 开始训练 7 绘制损失&#xff0c;准确率曲线 二、代码 导包&#xff0c;打印版本号&#xff1a; import matplotlib as mpl im…

【leetcode hot 100 19】删除链表的第N个节点

解法一&#xff1a;将ListNode放入ArrayList中&#xff0c;要删除的元素为num list.size()-n。如果num 0则将头节点删除&#xff1b;否则利用num-1个元素的next删除第num个元素。 /*** Definition for singly-linked list.* public class ListNode {* int val;* Lis…

【iOS逆向与安全】sms短信转发插件与上传服务器开发

一、目标 一步步分析并编写一个短信自动转发的deb插件 二、工具 mac系统已越狱iOS设备:脱壳及frida调试IDA Pro:静态分析测试设备:iphone6s-ios14.1.1三、步骤 1、守护进程 ​ 守护进程(daemon)是一类在后台运行的特殊进程,用于执行特定的系统任务。例如:推送服务、人…

Midjourney绘图参数详解:从基础到高级的全面指南

引言 Midjourney作为当前最受欢迎的AI绘图工具之一&#xff0c;其强大的参数系统为用户提供了丰富的创作可能性。本文将深入解析Midjourney的各项参数&#xff0c;帮助开发者更好地掌握这一工具&#xff0c;提升创作效率和质量。 一、基本参数配置 1. 图像比例调整 使用--ar…

音频进阶学习十九——逆系统(简单进行回声消除)

文章目录 前言一、可逆系统1.定义2.解卷积3.逆系统恢复原始信号过程4.逆系统与原系统的零极点关系 二、使用逆系统去除回声获取原信号的频谱原系统和逆系统幅频响应和相频响应使用逆系统恢复原始信号整体代码如下 总结 前言 在上一篇音频进阶学习十八——幅频响应相同系统、全…

vue3 使用sass变量

1. 在<style>中使用scss定义的变量和css变量 1. 在/style/variables.scss文件中定义scss变量 // scss变量 $menuText: #bfcbd9; $menuActiveText: #409eff; $menuBg: #304156; // css变量 :root {--el-menu-active-color: $menuActiveText; // 活动菜单项的文本颜色--el…

gbase8s rss集群通信流程

什么是rss RSS是一种将数据从主服务器复制到备服务器的方法 实例级别的复制 (所有启用日志记录功能的数据库) 基于逻辑日志的复制技术&#xff0c;需要传输大量的逻辑日志,数据库需启用日志模式 通过网络持续将数据复制到备节点 如果主服务器发生故障&#xff0c;那么备用服务…

熵与交叉熵详解

前言 本文隶属于专栏《机器学习数学通关指南》&#xff0c;该专栏为笔者原创&#xff0c;引用请注明来源&#xff0c;不足和错误之处请在评论区帮忙指出&#xff0c;谢谢&#xff01; 本专栏目录结构和参考文献请见《机器学习数学通关指南》 ima 知识库 知识库广场搜索&#…

程序化广告行业(3/89):深度剖析行业知识与数据处理实践

程序化广告行业&#xff08;3/89&#xff09;&#xff1a;深度剖析行业知识与数据处理实践 大家好&#xff01;一直以来&#xff0c;我都希望能和各位技术爱好者一起在学习的道路上共同进步&#xff0c;分享知识、交流经验。今天&#xff0c;咱们聚焦在程序化广告这个充满挑战…

探索在生成扩散模型中基于RAG增强生成的实现与未来

概述 像 Stable Diffusion、Flux 这样的生成扩散模型&#xff0c;以及 Hunyuan 等视频模型&#xff0c;都依赖于在单一、资源密集型的训练过程中通过固定数据集获取的知识。任何在训练之后引入的概念——被称为 知识截止——除非通过 微调 或外部适应技术&#xff08;如 低秩适…

DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)之添加列宽调整功能,示例Table14基础固定表头示例

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 Deep…

取反符号~

取反符号 ~ 用于对整数进行按位取反操作。它会将二进制表示中的每一位取反&#xff0c;即 0 变 1&#xff0c;1 变 0。 示例 a 5 # 二进制表示为 0000 0101 b ~a # 按位取反&#xff0c;结果为 1111 1010&#xff08;补码表示&#xff09; print(b) # 输出 -6解释 5 的二…

论文阅读分享——UMDF(AAAI-24)

概述 题目&#xff1a;A Unified Self-Distillation Framework for Multimodal Sentiment Analysis with Uncertain Missing Modalities 发表&#xff1a;The Thirty-Eighth AAAI Conference on Artificial Intelligence (AAAI-24) 年份&#xff1a;2024 Github&#xff1a;暂…

WBC已形成“东亚-美洲双中心”格局·棒球1号位

世界棒球经典赛&#xff08;WBC&#xff09;作为全球最高水平的国家队棒球赛事&#xff0c;参赛队伍按实力、地域和历史表现可分为多个“阵营”。以下是基于历届赛事&#xff08;截至2023年&#xff09;的阵营划分及代表性队伍分析&#xff1a; 第一阵营&#xff1a;传统豪强&a…