在前端项目中,为了实现对版本迭代更新的监控以及页面的自动更新,我们可以采取一系列巧妙的措施。首先,需要禁止浏览器对索引页面(index.html)和版本信息文件(version.json)进行缓存。
为此,我们可以在 Nginx 中进行不缓存处理,通过添加相应的配置来实现,即当遇到以.htm、.html 或.json 结尾的文件请求时,设置其过期时间为-1。
location ~ .*.(htm|html|json)?$ {
expires -1;
}
接下来,利用 Vite 插件在打包生产代码时自动生成版本信息。我们创建一个版本信息插件,其中包含了相关的处理逻辑。当进行构建开始时,会生成版本信息文件的路径。
这里我们以编译时间作为版本信息,将其转换为 JSON 格式的内容。如果公共目录存在,就直接写入版本信息文件;若不存在,则先创建公共目录,再进行写入操作。
在项目根目录新建一个plugin来存放vite的插件,创建一个versionUpdatePlugin.js来编写打包更新插件
// versionUpdatePlugin.js
const fs = require('fs')
const path = require('path')
const writeVersion = (versionFile, content) => {
// 通过fs模块写入文件
fs.writeFile(versionFile, content, (err) => {
if (err) throw err
})
}
export default (options) => {
let config
return {
name: 'version-update',
configResolved(resolvedConfig) {
// 存储最终解析的配置
config = resolvedConfig
},
buildStart() {
// 生成版本信息文件路径存到dist的version.json
const file = config.publicDir + path.sep + 'version.json'
// 这里使用编译时间作为版本信息
const content = JSON.stringify({ version: options.version })
if (fs.existsSync(config.publicDir)) {
writeVersion(file, content)
} else {
fs.mkdir(config.publicDir, (err) => {
if (err) throw err
writeVersion(file, content)
})
}
},
}
}
在 Vite 配置文件中,定义一个全局变量来表示当前应用的版本,其值为当前的时间戳。同时,在插件配置中加入我们自定义的版本信息插件,并传递相应的版本信息。
// vite.config.js
export default defineConfig((config) => {
// 获取当前的时间
const now = new Date().getTime()
return {
...
define: {
// 定义全局变量
__APP_VERSION__: now,
},
plugins: [
...
versionUpdatePlugin({
version: now,
}),
],
...
}
})
然后,在路由跳转时进行实时的版本检测,本质就是在路由拦截器去做这个操作。
// 路由拦截
router.beforeEach((to, from, next) => {
checkUpdate()
next()
}
- 具体来说,在路由的全局前置守卫中进行版本检查,当触发路由跳转时,先执行版本检查的操作。
- 如果处于开发环境则直接跳过,否则通过发送请求获取服务器端的版本信息。
- 若本地的版本与服务器端的版本不一致,就弹出一个提示消息,告知用户发现新内容并正在自动更新。
- 消息显示一定时间后关闭,关闭时进行页面的重新加载以获取新的索引页面。
- 通过这样的方式,能够及时地发现版本更新并实现页面的自动更新,提升用户体验和项目的维护便利性。
function checkUpdate() {
if (import.meta.env.DEV) return
axios.get('/version.json').then((res) => {
if (__APP_VERSION__ !== res.data.version) {
MessageBox.confirm('检测到版本更新,请刷新页面!!', '版本升级提示', {
type: 'warning',
showClose: false,
showCancelButton: false,
closeOnClickModal: false,
closeOnPressEscape: false
})
.then(() => {
router.go(0)
})
.catch(() => {})
}
})
}
最终效果
当然实现的方式有很多,这里只是其中一种比较常见的做法,相对来说是比较简单
下面是其他方式,可以了解一下
方式一:使用 Service Worker
- 在 Service Worker 中监听
fetch
事件,当请求特定资源(如版本信息文件)时进行版本比较。 - 如果版本有更新,通过
postMessage
等方式通知页面进行更新操作。
方式二:利用消息队列机制
- 服务端在有更新时向特定的消息队列发送更新消息。
- 前端通过订阅该消息队列来获取更新通知,并执行相应的更新流程。
方式三:基于 WebSocket 长连接
- 建立前端与服务端的 WebSocket 连接。
- 服务端主动推送更新信息到前端。