前端版本检测提示更新
1、方案
- 构建阶段生成 version.json:在
vue.config.js中提前计算版本号,既注入到前端(process.env.APP_VERSION),也写入输出目录的version.json; - 前端轮询比对:应用启动后每 30 秒请求一次
version.json,禁用缓存并携带时间戳,比较版本号; - 交互提示:复用 Ant Design Vue 的
Modal.confirm,展示当前/最新版本与环境; - 缓存策略:Nginx 对 HTML/
version.json禁止缓存,对 JS/CSS/图片继续长缓存; - CI/CD 配合:所有环境沿用既有脚本,只是构建产物目录多了一份实时的
version.json。
2、落实
2.1 跨平台构建脚本配置(Cross Platform Build Script Configuration)
为了确保 Windows/Linux/macOS 环境都能正确设置时间戳,我们在 package.json 中使用 cross-env 和独立的时间戳脚本:
// package.json "scripts": { "build": "cross-env BUILD_TIMESTAMP=$(node scripts/get-timestamp.js) NODE_OPTIONS=--openssl-legacy-provider vue-cli-service build --mode production", "build-develop": "cross-env BUILD_TIMESTAMP=$(node scripts/get-timestamp.js) NODE_OPTIONS=--openssl-legacy-provider vue-cli-service build --mode develop", "build-testing": "cross-env BUILD_TIMESTAMP=$(node scripts/get-timestamp.js) NODE_OPTIONS=--openssl-legacy-provider vue-cli-service build --mode testing", "build-release": "cross-env BUILD_TIMESTAMP=$(node scripts/get-timestamp.js) NODE_OPTIONS=--openssl-legacy-provider vue-cli-service build --mode release" }
// scripts/get-timestamp.js // 跨平台获取时间戳 const timestamp = new Date().toISOString().slice(0, 16).replace(/[-T:]/g, "")
关键点:
-
cross-env确保环境变量在不同操作系统下正确传递- 独立的时间戳脚本避免 shell 命令兼容性问题
2.2 版本号只生成一次(Build-time Deterministic Versioning)
为了彻底解决"版本号打架"问题,我们在 vue.config.js 中采用了模块加载时确定时间戳的策略:
// vue.config.js const fs = require("fs") const path = require("path") const packageJson = require("./package.json") // ⚠️ 重要:在模块加载时确定构建时间戳,避免多进程构建时版本号不一致 // Webpack/Vite 多进程构建时,每个 worker 进程独立执行代码,如果多次调用 new Date() // 可能在不同进程、不同时间点生成不同的时间戳,导致同一构建产物版本号不一致 // 方案:优先使用 CI/CD 传入的 BUILD_TIMESTAMP,否则在模块加载时确定统一时间戳 const BUILD_TIMESTAMP = process.env.BUILD_TIMESTAMP || (() => { const now = new Date() return now.toISOString().slice(0, 16).replace(/[-T:]/g, "") })() // 格式化时间戳:年月日时分(如202310301500) function getFormattedTimestamp() { // 使用模块加载时确定的统一时间戳 return BUILD_TIMESTAMP } // 获取当前环境名称 function getEnvName() { return process.env.VUE_APP_ENV || "develop" } // 生成复合版本号:基础版本+环境+时间戳 function getAppVersion() { const baseVersion = packageJson.version const envName = getEnvName() const timestamp = getFormattedTimestamp() return `${baseVersion}-${envName}-${timestamp}` } // ⚠️ 关键:构建阶段仅生成一次版本号和环境标识,保持前端注入与 version.json 完全一致 const buildEnvName = getEnvName() const buildVersion = getAppVersion() module.exports = { configureWebpack: { plugins: [ new webpack.DefinePlugin({ "process.env.APP_VERSION": JSON.stringify(buildVersion), "process.env.APP_ENV": JSON.stringify(buildEnvName) }) ] }, chainWebpack(config) { config.plugin("generate-version-json").use({ apply(compiler) { compiler.hooks.done.tap("GenerateVersionJsonPlugin", () => { fs.writeFileSync( path.resolve(__dirname, "edu/version.json"), JSON.stringify( { version: buildVersion, env: buildEnvName, timestamp: new Date().toISOString(), publicPath: "/child/edu", }, null, 2 ) ) }) } }) } }
核心改进点:
- 优先使用 CI/CD 传入的时间戳:通过
BUILD_TIMESTAMP环境变量,让 Jenkins 统一管理版本号; - 模块加载时确定兜底时间戳:如果没有传入环境变量,在模块加载时(而非函数调用时)确定时间戳,避免多进程构建时产生不同版本号;
- 全局复用版本号:
buildVersion和buildEnvName在模块顶层确定后,被 DefinePlugin 和 version.json 生成逻辑共同使用。
这样即使构建过程持续 5 ~ 10 分钟,注入的版本号和静态文件里的版本仍保持一致。这其实是把"构建产物视为不可变工件"的原则落地——保证任何使用该工件的入口看到的元数据都是同一个快照。
2.3 版本检查器(Runtime Polling & Cache Busting)
class VersionChecker { currentVersion = process.env.APP_VERSION publicPath = "/child/edu" checkInterval = 30 * 1000 init() { console.log(`📌 当前前端版本:${this.currentVersion}(${process.env.APP_ENV})`) this.startChecking() document.addEventListener("visibilitychange", () => { if (document.visibilityState === "visible" && !this.hasNotified) { this.checkForUpdate() } }) } async checkForUpdate() { const url = `${this.publicPath}/version.json?t=${Date.now()}` const response = await fetch(url, { cache: "no-store" }) if (!response.ok) return const latestInfo = await response.json() if (latestInfo.version !== this.currentVersion && !this.hasNotified) { this.hasNotified = true this.stopChecking() this.showUpdateModal(latestInfo.version, latestInfo.env) } } }
这里有两个容易被忽略的细节:
fetch显式加cache: "no-store",再叠加时间戳参数,防止 CDN / 浏览器任何一层干预;visibilitychange监听,保证窗口重新激活时立即比对,避免用户在后台等了很久才看到弹窗。
入口 main.ts 在应用 mount 之后调用 versionChecker.init(),即可把整个检测链路串起来。

浙公网安备 33010602011771号