前端轮子(1)--前端部署后-判断页面是否为最新
2025年12月21日 10:18
前端轮子系列:
- 前端部署后-判断页面是否为最新
- 如何优雅diy响应数据
- 如何优雅进行webview调试
- 如何实现测试环境的自动登录
项目部署后:通知更新、强制刷新、自测访问的页面为最新代码(是否发版成功)
重点:自测、提测、修改bug后,确认访问的为最新页面
![]()
实现思路
目的:生成新的版本标识,通过对比标识 ==> 当前为最新 还是 旧页面
- 生成版本号:如:1.1.10 ,或者时间戳(202512201416)
- 自动化脚本生成
- 手动控制
- 对比版本号:相等=> 当前为最新;不等 => 当前为旧页面
- 通过对比结果 对应处理产品逻辑 完事儿~
具体实现
方案一:生成时间戳,注入变量,控制台打印
在vue-cli项目中
通过vue.config.js 入变量
然后在main.js中打印变量
// main.js
console.log('__BUILD_TIME__', __BUILD_TIME__)
// vue.config.js
const _date = new Date()
const BUILD_TIME = _date.toLocaleString ? _date.toLocaleString() : _date.getTime()
module.exports = {
configureWebpack: {
plugins: [
new webpack.DefinePlugin({
__BUILD_TIME__: JSON.stringify(BUILD_TIME)
})
]
},
}
在vite项目中
通过vite.config.js 注入 BUILD_TIME 变量
然后在main.js中打印 BUILD_TIME 变量
// main.js
console.log('__BUILD_TIME__', __BUILD_TIME__)
// vite.config.js
const _date = new Date()
const BUILD_TIME = _date.toLocaleString ? _date.toLocaleString() : _date.getTime()
export default defineConfig(() => {
return {
define: {
__BUILD_TIME__: JSON.stringify(BUILD_TIME)
},
}
})
这种方案,实现了在控制台打印。但是对于生产环境,无法做到版本号的对比
为啥? 因为上线 一般会去掉日志的打印, 所以咱通过版本号来~
方案二:记录版本号,轮询对比版本号
- 编写生成版本号的脚本,生成版本号文件
- build结束,调用脚本去生成版本号文件
- 轮询对比版本号文件,如果版本号不一致,做相应操作
// dist/version.json
{
"buildTime": "2025-12-20 14:16:00"
}
prebuild: build前执行的脚本
postbuild:build结束后执行的脚本,用于生成版本号文件
// package.json
{
"scripts": {
"postbuild": "node scripts/generate-version-dist.mjs"
}
}
// scripts/generate-version-dist.mjs
import { writeFile } from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const root = path.resolve(__dirname, '..')
async function main() {
const _date = new Date()
const BUILD_TIME = _date.toLocaleString ? _date.toLocaleString() : _date.getTime()
const outPath = path.join(root, 'dist', 'version.json')
await writeFile(outPath, JSON.stringify({ BUILD_TIME }, null, 2) + '\n', 'utf-8')
}
main().catch((e) => {
console.error('[generate-version] failed', e)
process.exit(1)
})
轮询对比版本号
- 轮询对比版本号文件,如果版本号不一致,做相应操作
- 页面隐藏、切后台时,停止轮询,页面关闭时,停止轮询
- 页面显示、切前台时,开始轮询
// main.js
import { createVersionPoller } from './utils/versionPoller'
createVersionPoller({
versionUrl: '/version.json',
storageKey: '__VERSION_CHECK__CURRENT_BUILD_TIME__',
intervalMs: 15_000,
maxDelayMs: 60_000,
onResult: ({ remoteVersion, remoteBuildTime }) => {
// 轮询到json文件 成功,额外处理逻辑
},
onError: (e) => {
// 轮询失败,额外处理逻辑
},
onUpdate: () => {
// 版本号不一致,需要更新,额外处理逻辑
}
}).start()
/**
* 轮询 /version.json,对比 buildTime 判断是否需要更新(setTimeout + 错误指数退让)
*
* 行为说明:
* - start() 会自动注册监听:visibilitychange / beforeunload / unload
* - stop() 会清理定时器并卸载监听
* - 请求失败会指数退让(delay *= 2,最大不超过 maxDelayMs);成功后恢复 intervalMs
* - currentBuildTime 会写入 localStorage(storageKey)
*
* @param {Object} options
* @param {string} [options.versionUrl='/version.json'] 版本文件地址
* @param {string} [options.storageKey='__VERSION_CHECK__CURRENT_BUILD_TIME__'] localStorage key
* @param {number} [options.intervalMs=15000] 正常轮询间隔(毫秒)
* @param {number} [options.maxDelayMs=60000] 退让最大间隔(毫秒)
* @param {(info:{localBuildTime:string,remoteBuildTime:string,remoteVersion:string,data:any})=>void} [options.onResult] 每次成功拉取后的回调
* @param {(error:any)=>void} [options.onError] 拉取失败回调
* @param {(info:{localBuildTime:string,remoteBuildTime:string,remoteVersion:string,data:any})=>void} [options.onUpdate] 发现新 buildTime 时回调(默认会 stop)
*/
export function createVersionPoller({
versionUrl = '/version.json',
storageKey = '__VERSION_CHECK__CURRENT_BUILD_TIME__',
intervalMs = 15000,
maxDelayMs = 60000,
onResult,
onError,
onUpdate
} = {}) {
let timer = null
let stopped = true
let delayMs = intervalMs
let bound = false
const readLocal = () => window.localStorage.getItem(storageKey) || ''
const writeLocal = (v) => v && window.localStorage.setItem(storageKey, String(v))
function clear() {
if (!timer) return
window.clearTimeout(timer)
timer = null
}
function isVisible() {
return document.visibilityState !== 'hidden'
}
async function fetchRemote() {
const sep = versionUrl.includes('?') ? '&' : '?'
const url = `${versionUrl}${sep}t=${Date.now()}`
const res = await fetch(url, { cache: 'no-store', headers: { 'Cache-Control': 'no-cache' } })
if (!res.ok) throw new Error(`fetch ${versionUrl} failed: ${res.status}`)
return await res.json()
}
async function tick() {
if (stopped || !isVisible()) return
const localBuildTime = readLocal() || ''
try {
const data = await fetchRemote()
const remoteVersion = String(data?.version || '')
const remoteBuildTime = String(data?.BUILD_TIME || '')
delayMs = intervalMs
onResult?.({ localBuildTime, remoteBuildTime, remoteVersion, data })
console.log( remoteBuildTime , '=>', localBuildTime)
if (remoteBuildTime && remoteBuildTime !== localBuildTime) {
// 记录最新 buildTime,避免重复提示同一个更新
writeLocal(remoteBuildTime)
onUpdate?.({ localBuildTime, remoteBuildTime, remoteVersion, data })
}
} catch (e) {
delayMs = Math.min(maxDelayMs, Math.max(500, delayMs * 2))
onError?.(e)
}
timer = window.setTimeout(tick, delayMs)
}
function onVisibilityChange() {
if (stopped) return
if (!isVisible()) return clear()
clear()
timer = window.setTimeout(tick, 0)
}
function ensureBound() {
if (bound) return
bound = true
document.addEventListener('visibilitychange', onVisibilityChange)
window.addEventListener('beforeunload', stop)
window.addEventListener('unload', stop)
}
function ensureUnbound() {
if (!bound) return
bound = false
document.removeEventListener('visibilitychange', onVisibilityChange)
window.removeEventListener('beforeunload', stop)
window.removeEventListener('unload', stop)
}
function start() {
if (!stopped) return
stopped = false
delayMs = intervalMs
ensureBound()
if (isVisible()) timer = window.setTimeout(tick, 0)
}
function stop() {
stopped = true
clear()
ensureUnbound()
}
return { start, stop }
}
注意点
- 轮询版本文件,拼 时间戳、设置请求头 避免命中缓存
- 轮询版本文件,时间间隔不要太短,耗费网络,虽然已经做了轮询指数退让
- 生成的版本文件 生成到dist下 注意访问路径
- 版本号需要上传到git,需要手动执行脚本&commit + push
源码
结语
如果本文对你有收获,麻烦动动发财的小手,点点关注、点点赞!!!👻👻👻
因为收藏===会了
如果有不对、更好的方式实现、可以优化的地方欢迎在评论区指出,谢谢👾👾👾