vue3+vite纯前端实现自动触发浏览器刷新更新版本内容,并在打包时生成版本号文件

前言

在前端项目中,有时候为了实现自动触发浏览器刷新并更新版本内容,可以采取一系列巧妙的措施。我的项目中是需要在打包时候生成一个version.js文件,用当前打包时间作为版本的唯一标识,然后打包发版 ,从实现对版本更新的监控。

因为项目使用 vite 打包的,vite又是基于rollup打包,rollup不像webpack有contentHash可以实现增量构建;而是每次打包,所有文件的hash都会更新,这样就会导致浏览器缓存的资源失效。
当用户已经打开页面,此时前端重新部署代码发版,会导致index.html未更新,而项目静态资源已经替换。当用户切换路由时,因为按需加载的原因,会继续访问旧的资源。

项目是基于 vue 3.4.21 + vite 4.3.9 + typescript 5.1.3 + node 16.1.0 开发的。

解决思路

  • 注入版本信息:通过vite构建打包时,注入一个版本信息的version.js 文件,文件内容是一个版本号(这里用时间戳代替)。
  • 定义监控版本函数并执行:定义一个函数并调用执行,该函数内容为生产环境下,从本地缓存中获取版本号,如果没有版本号或者和缓存中的版本号不一致,表示版本已更新。就刷新页面,然后本地存储新的版本号以便下次使用。
  • 在合适的时机(路由载入前),判断是否已经有 version.js 文件,如果有,先删除掉,再重新创建一个<script> 标签并赋值,值为最新版本号(时间戳)。

了解fs模块

开始之前先了解下fs模块吧,本文会使用到。

  • fs 通常是指 Node.js 中的 fs 模块,它是文件系统模块(File System
    module)的简写。 这个模块允许你与文件系统进行交互,包括读取、写入、修改、删除文件等操作 。在 Node.js 中,fs模块是内置的核心模块之一,因此 无需额外安装即可使用
  • fs 模块提供了丰富的 API 来处理文件和目录,包括同步和异步的操作方式。在使用 fs 模块时,需要特别注意错误处理,因为文件操作可能会涉及到磁盘访问和系统资源,因此 处理错误非常重要

其中的 fs.writeFile() 方法用于异步地将数据写入文件。其基本语法如下:

fs.writeFile(file, data, options, callback)

参数说明:

  • file(必需):表示要写入的文件的路径(包括文件名)。

  • data(必需):表示要写入文件的数据,可以是字符串或者 Buffer 对象。

  • options(可选):一个对象,包含指定如何写入文件的选项。常用选项包括:encoding:指定文件的编码,默认为 ‘utf8’。 mode:指定文件的权限,默认为 0o666(可读写)。 flag:指定文件的打开行为,默认为 ‘w’(覆盖写入)。

  • callback(可选):写入操作完成后的回调函数,通常以 (err)形式接收一个可能的错误参数。如果未提供回调函数,则返回一个 Promise。

使用解释:

  • fs.writeFile() 方法将 data 写入到指定的 file 中。如果文件不存在,则会创建该文件;如果文件已存在,则会完全覆盖原有内容。
  • 是异步的,意味着它会立即返回并且不会阻塞后续的代码执行;如果需要在写入文件后执行某些操作,可以使用回调函数或者 Promise
  • 若要进行文件写入操作,通常建议先检查是否有写入权限,并且考虑错误处理以确保应用程序的稳定性。

上面纯前端实现的做法,相对来说比较简单,实现的方式还有很多,比如自定义一个plugin插件 vite实现前端项目打包更新通知用户更新
使用WebSocket实时通信、前端轮询接口检测版本更新等等。感兴趣的可以继续搜资料看。

创建 build.ts 文件

src/utils/文件夹下创建 build.ts文件

// build.ts
import pkg from '../../package.json'
import { resolve } from 'path'
import fs from 'fs'const version = new Date().getTime()
const content = `getVersion(${version})`// 创建版本文件
fs.writeFile(`${resolve(__dirname, '../../dist')}/version.js`, content, (err) => (err ? console.log(err) : console.log('版本文件创建成功')))export const run = () => {console.log(`${pkg.name} - build successfully!`)
}

如果上面这种node提示报错的话,可替换为下面这种

import pkg from '../../package.json'
import fs from 'fs'
import { fileURLToPath, URL } from 'node:url'const version = new Date().getTime()
const content = `getVersion(${version})`// 创建版本文件
fs.writeFile(fileURLToPath(new URL('../../dist/version.js', import.meta.url)), content, (err) =>err ? console.log(err) : console.log('版本文件创建成功')
)
export const run = () => {console.log(`${pkg.name} - build successfully!`)
}
run()

在package.json文件中配置执行 npm run build 时执行的任务

在package.json的build命令上加一行执行 esno ./src/utils/build.ts

{"name": "ceshi","version": "1.0.0","scripts": {"dev": "vite --force","build": "vite build && esno ./src/utils/build.ts"}
}

创建 version.ts 文件

src/utils/文件夹下创建 version.ts文件

import Cookies from 'js-cookie'
import { ElLoading } from 'element-plus'const versionKey = 'version-id'export function getVersionId() {return Cookies.get(versionKey) ? Number(Cookies.get(versionKey)) : 0
}export function setVersionId(version: number) {return Cookies.set(versionKey, String(version))
}export function removeVersionId(version: number) {Cookies.remove(versionKey)
}export function handleVersion() {if (process.env.NODE_ENV !== 'development') {window.getVersion = (version: number) => {if (!getVersionId() || (getVersionId() * 1 && version * 1 !== getVersionId() * 1)) {ElLoading.service() // 启动全屏ElLoadinglocation.reload() // 刷新页面}setVersionId(version) // 保存 以便下次使用判断}}
}export function insertVersionFile() {if (process.env.NODE_ENV !== 'development') {const scriptCollection = document.getElementsByTagName('script')// 判断是否已经有version.js 文件,如果有,先删掉资源引入// const scriptAry =[...scriptCollection] // ie不支持这种写法(HTMLCollection 不是数组)const scriptAry = Array.from(scriptCollection)scriptAry.some((v) => {const flag = v.src.indexOf('version.js') !== -1if (flag) {v.parentNode?.removeChild(v)}return flag})const versionScript = document.createElement('script')versionScript.src = import.meta.env.VITE_BASE_PATH + 'version.js?v=' + new Date().getTime()//document.getElementsByTagName('script')表示返回当前页面中所有 <script> 元素的集合const s = document.getElementsByTagName('script')[0]  s.parentNode?.insertBefore(versionScript, s)}
}
handleVersion()

router =》index.ts 中在前置路由,插入并且检查版本号

在路由跳转时进行实时的版本检测,本质就是在路由拦截器去做这个操作。

import { insertVersionFile } from '/@/utils/version'const router = createRouter({history: createWebHashHistory(),routes: staticRoutes,
})router.beforeEach((to, from, next) => {// 插入并且检查版本号insertVersionFile()NProgress.configure({ showSpinner: false })NProgress.start()if (!window.existLoading) {loading.show()window.existLoading = true}next()})export default router
  • 具体来说,在路由的全局前置守卫中进行版本检查,当触发路由跳转时,先执行版本检查的操作。
  • 如果是生产环境,判断是否已经有 version.js 文件,如果有,先删掉资源引入;然后创建一个<script>标签,并设置 src 值;页面中当前第一个 <script> 元素之前插入一个新的 <script>,从而加载并执行。
  • 通过这样的方式,能够及时地发现版本更新并实现页面的自动更新,提升用户体验和项目的维护便利性。

最终

执行 npm run build 命令行打包项目,最终dist文件夹里多了一个文件 version.js
此时版本号文件就生成了,并且每次打包后这个文件的内容都不一样。

在这里插入图片描述
在这里插入图片描述

最后 f12 查看 dom 结构,我们每次进入路由版本号因为是时间戳,所以都会更新。

在这里插入图片描述

注意事项

1) 报错

import pkg from '../../package.json' 时候,可能会有标红提示报错找不到模块“../../package.json”。请考虑使用 "--resolveJsonModule" 导入带 ".json" 扩展的模块

通常是因为在当前的 Node.js 环境中,默认不支持直接导入 .json 文件作为模块。如果在 TypeScript 项目中使用 import 导入 .json 文件,需要在 tsconfig.json 文件中启用 resolveJsonModule 选项,通过设置 “resolveJsonModule”: true,TypeScript 将会允许导入 .json 文件。

{"compilerOptions": {"resolveJsonModule": true,"esModuleInterop": true  // 如果需要的话,也需要启用这个选项}
}
2) window.getVersion

这是自定义的函数并将其绑定到 window 对象上,需要在项目中新建 types文件夹,新增一个global.d.ts 文件,写入下面代码,才不会标红提示。

interface Window {getVersion: Function
}

另外,如果我们想全局定义一个type类型,可直接在这个文件中定义,就可以全局使用该类型了,比如下面代码,

// 在 TypeScript 中非常有用,特别是当需要处理结构不固定、属性名称和类型不确定的对象时,对象里可以是任意类型,不受属性类型的严格限制
interface anyObj {[key: string]: any
}

可参考:
纯前端实现监控版本更新
vite实现前端项目打包更新通知用户更新
前端打包同时版本号自增
如何优雅的实现前端版本投产自动触发浏览器刷新更新版本内容

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

基于SpringBoot的矩形范围面时空分析-以震中附近历史地震为例

目录 前言 1、分析的必要性 2、分析的紧迫性 一、数据库物理模型及空间分析实现 1、数据库物理模型 2、空间数据库中的空间查询分析 二、Java后台程序开发 1、模型层设计 2、业务层的设计与实现 三、WebGIS功能设计与实现 1、同时展示4幅地图 2、初始化地图 3、展示…

动态创建标签jQuery效果

动态创建标签jQuery效果https://www.bootstrapmb.com/item/14832 使用jQuery来动态创建HTML标签并添加效果是一种常见的方法。以下是一个简单的示例&#xff0c;说明如何使用jQuery来动态创建<div>标签&#xff0c;并给它们添加一些基本的效果。 1. 创建一个新的<di…

go语言day17 通道channel

Golang-100-Days/Day16-20(Go语言基础进阶)/day18_channel通道.md at master rubyhan1314/Golang-100-Days (github.com) go语言day09 通道 协程的死锁-CSDN博客 channel for range 循环通道对象 单向通道 单项通道常用于函数参数&#xff0c;只是用来限定在函数中只能进行通道…

Langchain核心模块与实战[8]:RAG检索增强生成[loader机制、文本切割方法、长文本信息处理技巧]

Langchain核心模块与实战[8]:RAG(Retrieval Augmented Generation,检索增强生成) RAG(Retrieval-Augmented Generation)技术是一种结合检索和生成功能的自然语言处理(NLP)技术。该技术通过从大型外部数据库中检索与输入问题相关的信息,来辅助生成模型回答问题。其核心…

Mysql中(基于GTID方式)实现主从复制,单主复制详细教程

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f427;Linux基础知识(初学)&#xff1a;点击&#xff01; &#x1f427;Linux高级管理防护和群集专栏&#xff1a;点击&#xff01; &#x1f510;Linux中firewalld防火墙&#xff1a;点击&#xff01; ⏰️创作…

《深入探秘Java中的枚举:掌握Enum的魔力》

目录 &#x1f4dd; 枚举枚举的定义枚举的使用1、表示一组固定常量2、实现接口3、枚举与策略模式4、EnumSet5、EnumMap &#x1f4ce; 参考文章 &#x1f600; 准备好了吗&#xff1f;让我们一起步入这座Java神奇的城堡&#xff0c;探寻枚举&#xff08;Enum&#xff09;这个强…

grafana对接zabbix数据展示

目录 1、初始化、安装grafana 2、浏览器访问 3、安装zabbix 4、zabbix数据对接grafana 5、如何导入模板&#xff1f; ① 设置键值 ② 在zabbix web端完成自定义监控项 ③ garafana里添加nginx上面的的三个监控项 6、如何自定义监控项&#xff1f; 以下实验沿用上一篇z…

Python学习笔记44:游戏篇之外星人入侵(五)

前言 上一篇文章中&#xff0c;我们成功的设置好了游戏窗口的背景颜色&#xff0c;并且在窗口底部中间位置将飞船加载出来了。 今天&#xff0c;我们将通过代码让飞船移动。 移动飞船 想要移动飞船&#xff0c;先要明白飞船位置变化的本质是什么。 通过上一篇文章&#xff0…

vue上传Excel文件并直接点击文件列表进行预览

本文主要内容&#xff1a;用elementui的Upload 组件上传Excel文件&#xff0c;上传后的列表采用xlsx插件实现点击预览表格内容效果。 在项目中可能会有这样的需求&#xff0c;有很多种方法实现。但是不想要跳转外部地址&#xff0c;所以用了xlsx插件来解析表格&#xff0c;并展…

使用 vSphere vCenter 管理 ESXi

使用 vSphere vCenter 管理 ESXi 1、新建数据中心 在 vSphere Client 中&#xff0c;左上角图标&#xff0c;进入 “清单”&#xff0c;鼠标右键名称&#xff0c;新建数据中心。 输入数据中心名称&#xff0c;我这里直接使用默认值&#xff0c;点击确定。 2、往数据中心中添加…

Linux epoll 机制——原理图解与源码实现分析

epoll概述 epoll是Linux内核为处理大批量文件描述符而作了改进的poll&#xff0c;它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。 epoll可以理解为event poll&#xff0c;它是一种事件驱动的I/O模型&#xff0c;可以用来替代传统的select和poll模型…

leetcode-98. 验证二叉搜索树

题目描述 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下&#xff1a; 节点的左 子树 只包含 小于 当前节点的数。节点的右子树只包含 大于 当前节点的数。所有左子树和右子树自身必须也是二叉搜索树。 示例 1&…

功能性的安全性保障:TOKEN鉴权校验

1. 引言 在软件开发过程中&#xff0c;确保系统的安全性是至关重要的一环。它不仅关乎保护用户数据的完整性和隐私性&#xff0c;也是维护系统稳定运行的基石。我认为&#xff0c;从宏观角度审视&#xff0c;软件开发的安全性保障主要可分为两大类&#xff1a;功能性的安全性保…

Golang | Leetcode Golang题解之第275题H指数II

题目&#xff1a; 题解&#xff1a; func hIndex(citations []int) int {n : len(citations)return n - sort.Search(n, func(x int) bool { return citations[x] > n-x }) }

JDBC操作MySQL数据

一准备、 1、首先在IDEA中导入导入包&#xff1a;mysql-connector-java-8.0.23 2、写初始化语句 &#xff08;1&#xff09;在目录下找到driver类 &#xff08;2&#xff09;在JDBCUtil函数中把驱动器的类路径改掉 ①打开driver类 ②按住类名 Driver用快捷键 CtrlAltshiftC …

AIGC的神秘面纱——利用人工智能生成内容改变我们的生活

近年来&#xff0c;人工智能生成内容&#xff08;AIGC&#xff09;正在迅速改变我们与数字世界互动的方式。从自动写作到图像生成&#xff0c;AIGC正逐渐走进我们的日常生活。它不仅提高了效率&#xff0c;还为创意和商业活动带来了新的可能性。让我们一起来探索AIGC的世界&…

17.jdk源码阅读之LinkedBlockingQueue

1. 写在前面 LinkedBlockingQueue 是 Java 并发包中的一个重要类&#xff0c;常用于生产者-消费者模式等多线程编程场景。上篇文章我们介绍了ArrayBlockingQueue&#xff0c;并且与LinkedBlockingQueue做了简单的对比&#xff0c;这篇文章我们来详细分析下LinkedBlockingQueue…

从零开始构建你的第一个Python Web应用

在本文中&#xff0c;我们将带领你从零开始构建一个简单的Python Web应用。不需要任何先验知识&#xff0c;我们会一步步地指导你完成设置、框架选择、代码编写到部署的整个过程。无论你是Web开发新手还是希望扩展技能的老手&#xff0c;这篇文章都将为你提供一个实践操作的起点…

Spring-Aop源码解析(二)

书接上文&#xff0c;上文说到&#xff0c;specificInterceptors 不为空则执行createProxy方法创建代理对象&#xff0c;即下图的createProxy方法开始执行&#xff0c;生成代理对象&#xff0c;生成代理对象有两种方式&#xff0c;JDK和CGLIB。 createAopProxy就是决定使用哪…

【数据结构 | 哈希表】一文了解哈希表(散列表)

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…