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.mzph.cn/bicheng/50790.shtml

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

相关文章

五大设备制造商的 200 多种机型的安全启动功能完全失效

2012 年&#xff0c;一个由硬件和软件制造商组成的行业联盟采用了安全启动技术&#xff0c;以防范长期存在的安全威胁。这种威胁是恶意软件的幽灵&#xff0c;它可以感染 BIOS&#xff0c;即每次计算机启动时加载操作系统的固件。从那里&#xff0c;它可以保持不受检测和删除&a…

从零开始学Java(超详细韩顺平老师笔记梳理)08——面向对象编程中级(上)IDEA常用快捷键、包、封装、继承

文章目录 前言一、IDEA使用常用快捷键模板/自定义模板 二、包package1. 基本介绍2. 包的命名规范3. 常用的包和如何引入4. 注意事项和细节 三、访问修饰符&#xff08;四类&#xff09;四、封装Encapsulation&#xff08;重点&#xff09;1. 封装介绍2. 封装步骤3. 快速入门4. …

SpringCloud Nacos的配置与使用

Spring Cloud Nacos的配置与使用 文章目录 Spring Cloud Nacos的配置与使用1. 简单介绍2. 环境搭建3. 服务注册/服务发现4. Nacos 负载均衡4.1 服务下线4.2 权重配置4.3 同集群优先访问 5. Nacos 健康检查5.1 两种健康检查机制5.2 服务实例类型 6.Nacos 环境隔离6.1 创建namesp…

【MySQL进阶之路 | 高级篇】表级锁之S锁,X锁,意向锁

1. 从数据操作的粒度划分&#xff1a;表级锁&#xff0c;页级锁&#xff0c;行锁 为了尽可能提高数据库的并发度&#xff0c;每次锁定的数据范围越小越好&#xff0c;理论上每次只锁定当前操作的数据的方案会得到最大的并发度&#xff0c;但是管理锁是很耗资源的事情&#xff…

驾驭代码的无形疆界:动态内存管理揭秘

目录 1.:为什么要有动态内存分配 2.malloc和free 2.1:malloc 2.2:free 3.calloc和realloc 3.1:calloc 3.1.1:代码1(malloc) 3.1.2:代码2(calloc) 3.2:realloc 3.2.1:原地扩容 3.2.2:异地扩容 3.2.3:代码1(原地扩容) 3.2.3:代码2(异地扩容) 4:常见的动态内存的错误…

vite + xlsx + xlsx-style 导出 Excel

如下 npm i 依赖 npm i xlsxnpm i xlsx-style-vite1、简单的使用&#xff1a;.vue文件中使用 const dataSource ref([]) // 数据源const columns [{title: 用户名,key: userName,width: 120,},{title: 用户组,key: userGroup,width: 120,},{title: 状态,key: enable,width: …

鸿蒙(HarmonyOS)下拉选择控件

一、操作环境 操作系统: Windows 11 专业版、IDE:DevEco Studio 3.1.1 Release、SDK:HarmonyOS 3.1.0&#xff08;API 9&#xff09; 二、效果图 三、代码 SelectPVComponent.ets Component export default struct SelectPVComponent {Link selection: SelectOption[]priva…

浅谈我对RESTful架构的理解

总结说在前面&#xff1a; RESTful API是目前比较成熟的一套互联网应用程序的 API 设计理论&#xff0c;他是一种理论规范&#xff0c;方便不同的前端设备与后端进行通信&#xff0c;在 RESTful 风格的 API 设计架构中&#xff0c;每个网址代表一种资源&#xff08;resource&am…

maven介绍 搭建Nexus3(maven私服搭建)

Maven是一个强大的项目管理工具&#xff0c;它基于项目对象模型&#xff08;POM&#xff1a;Project Object Model&#xff09;的概念&#xff0c;通过XML格式的配置文件&#xff08;pom.xml&#xff09;来管理项目的构建 Maven确实可以被视为一种工程管理工具或项目自动化构…

飞凌嵌入式技术创新日深圳站,8月26日见!

飞凌嵌入式技术创新日&#xff08;深圳站&#xff09;将于8月26日举行&#xff0c;一场嵌入式前沿科技的高端局就在眼前。届时&#xff0c;将有多位重量级技术大咖出席&#xff0c;为大家分享最新的研究成果、独到的行业见解和典型的应用案例&#xff0c;紧密结合当前行业热点和…

网络服务综合项目(一键部署shell脚本)

目录 需求&#xff1a; 主机环境描述 注意&#xff1a; 项目需求&#xff1a; 代码讲解 配置本地仓库 安装软件包 配置防火墙 配置策略中的一个布尔值 配置web服务 配置网络仓库 配置DNS服务 配置NTP服务 配置MySQL服务 配置NFS服务 配置论坛服务 进入网站配置…

Python数据分析案例55——基于LSTM结构自编码器的多变量时间序列异常值监测

案例背景 时间序列的异常值检测是方兴未艾的话题。比如很多单变量的&#xff0c;一条风速&#xff0c;一条用电量这种做时间序列异常值检测&#xff0c;想查看一下哪个时间点的用电量异常。 多变量时间序列由不同变量随时间变化的序列组成&#xff0c;这些时间序列在实际应用…

小黄人欢乐来袭,国漫萌物大集结

最近上映的《神偷奶爸》4不知道大家有没有去看&#xff0c;小黄人作为该系列电影的标志性角色&#xff0c;继续以其呆萌可爱的形象和幽默搞怪的性格赢得了观众的喜爱。 在中国动漫中&#xff0c;也有许多可爱且深受观众喜爱的萌物角色。这些角色以其独特的形象、有趣的性格和与…

数据结构day6

一、思维导图 二、模拟面试 typedef定义函数指针的方式typedef int(*p)(int,int);对void*指针的理解&#xff0c;相关应用万能指针&#xff0c;可以定义形参用来接收任意类型的指针变量&#xff0c;也可以定义函数用来返回任意类型的指针变量例如malloc函数在堆区申请内存&…

HTTP协议和RPC协议的区别是什么

从功能层面来说&#xff0c;HTTP协议是一个应用层的超文本传输协议&#xff0c;它是万维网数据通信的一个基础&#xff0c;主要服务在网页端和服务端的一个数据传输上。而RPC是一个远程过程调用协议&#xff0c;它是定位在实现分布式应用之间的一个数据通信&#xff0c;屏蔽了通…

SpringBoot入门:如何新建SpringBoot项目(保姆级教程)

在本文中&#xff0c;我们将演示如何新建一个基本的 Spring Boot 项目。写这篇文章的时候我还是很惊讶的&#xff0c;因为我发现有些java的初学者&#xff0c;甚至工作10年的老员工居然并不会新建一个SpringBoot项目&#xff0c;所以特别出了一篇文章来教大家新建一个SpringBoo…

数据挖掘-数据预处理

来自&#x1f96c;&#x1f436;程序员 Truraly | 田园 的博客&#xff0c;最新文章首发于&#xff1a;田园幻想乡 | 原文链接 | github &#xff08;欢迎关注&#xff09; 文章目录 3.3.1 数据的中心趋势平均数和加权平均数众数&#xff0c;中位数和均值描述数据的离散程度 &a…

VSCode | 修改编辑器注释的颜色

1 打开VsCode的设置进入settings.json 2 添加如下代码 "editor.tokenColorCustomizations": {"comments": "#17e917"},3 保存即可生效

Linux源码阅读笔记14-IO体系结构与访问设备

IO体系结构 与外设通信通常称为输入输出&#xff0c;一般缩写为I/O。在实现外设IO的时候&#xff0c;内核必须处理三个可能出现的问题&#xff1a; 必须根据具体的设备类型和模型&#xff0c;使用各种方法对硬件寻址。内核必须向用户应用程序和系统工具提供访问各种设备的方法…

hugging face 使用教程———快速入门

概述 本篇存在的意义是快速介绍hugging face使用&#xff0c;梳理主要部件&#xff0c;梳理易混淆概念。原因是&#xff1a;目前hugging face的使用&#xff0c;官方放在了3个地方&#xff08;参考链接部分&#xff09;&#xff1a;使用文档、NLP教程、Transformers git的readm…