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

铁锤妹妹@ 2024-08-04 09:33:01 阅读 95

前言

在前端项目中,有时候为了实现自动触发浏览器刷新并更新版本内容,可以采取一系列巧妙的措施。<code>我的项目中是需要在打包时候生成一个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() // 启动全屏ElLoading

location.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') !== -1

if (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> 元素的集合code>

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) 报错

<code>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实现前端项目打包更新通知用户更新

前端打包同时版本号自增

如何优雅的实现前端版本投产自动触发浏览器刷新更新版本内容



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。