前端轮子(1)--前端部署后-判断页面是否为最新

前端轮子系列:

  1. 前端部署后-判断页面是否为最新
  2. 如何优雅diy响应数据
  3. 如何优雅进行webview调试
  4. 如何实现测试环境的自动登录

项目部署后:通知更新、强制刷新、自测访问的页面为最新代码(是否发版成功)

重点:自测、提测、修改bug 后,确认访问的为最新页面

实现思路

目的:生成新的版本标识,通过对比标识 ==> 当前为最新 还是 旧页面

  1. 生成版本号:如:1.1.10 ,或者时间戳(202512201416)
    • 自动化脚本生成
    • 手动控制
  2. 对比版本号:相等=> 当前为最新;不等 => 当前为旧页面
  3. 通过对比结果 对应处理产品逻辑 完事儿~

具体实现

方案一:生成时间戳,注入变量,控制台打印

在vue-cli项目中

通过vue.config.js 入变量

然后在main.js中打印变量

javascript 复制代码
// main.js
console.log('__BUILD_TIME__', __BUILD_TIME__)
javascript 复制代码
// 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 变量

javascript 复制代码
// main.js
console.log('__BUILD_TIME__', __BUILD_TIME__)
javascript 复制代码
// 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)
    },
  }
})

这种方案,实现了在控制台打印。但是对于生产环境,无法做到版本号的对比

为啥? 因为上线 一般会去掉日志的打印, 所以咱通过版本号来~

方案二:记录版本号,轮询对比版本号

  1. 编写生成版本号的脚本,生成版本号文件
  2. build结束,调用脚本去生成版本号文件
  3. 轮询对比版本号文件,如果版本号不一致,做相应操作
javascript 复制代码
// dist/version.json
{
  "buildTime": "2025-12-20 14:16:00"
}

prebuild : build前执行的脚本
postbuild:build结束后执行的脚本,用于生成版本号文件

javascript 复制代码
// package.json
{
  "scripts": {
    "postbuild": "node scripts/generate-version-dist.mjs"
  }
}
javascript 复制代码
// 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)
})

轮询对比版本号

  1. 轮询对比版本号文件,如果版本号不一致,做相应操作
  2. 页面隐藏、切后台时,停止轮询,页面关闭时,停止轮询
  3. 页面显示、切前台时,开始轮询
javascript 复制代码
// 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()
javascript 复制代码
/**
 * 轮询 /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

源码

xiaoyi1255

结语

如果本文对你有收获,麻烦动动发财的小手,点点关注、点点赞!!!👻👻👻

因为收藏===会了

如果有不对、更好的方式实现、可以优化的地方欢迎在评论区指出,谢谢👾👾👾

相关推荐
IT_陈寒3 小时前
Python开发者必知的5大性能陷阱:90%的人都踩过的坑!
前端·人工智能·后端
codingWhat3 小时前
介绍一个手势识别库——AlloyFinger
前端·javascript·vue.js
代码老中医3 小时前
2026年CSS彻底疯了:这6个新特性让我删掉了三分之一JS代码
前端
不会敲代码13 小时前
Zustand:轻量级状态管理,从入门到实践
前端·typescript
踩着两条虫3 小时前
VTJ.PRO 双向代码转换原理揭秘
前端·vue.js·人工智能
扉川川3 小时前
OpenClaw 架构解析:一个生产级 AI Agent 是如何设计的
前端·人工智能
远山枫谷3 小时前
一文理清页面/组件通信与 Store 全局状态管理
前端·微信小程序
codingWhat3 小时前
手撸一个「能打」的 React Table 组件
前端·javascript·react.js
HelloReader3 小时前
Tauri 应用安全从开发到发布的威胁防御指南
前端
bluceli3 小时前
WebAssembly实战指南:将高性能计算带入浏览器
前端·webassembly