Vue3 + Vite 构建版本注入实战:一份 version.json 终结「线上到底是哪一版」

Vue3 + Vite 移动端 H5:给每次发版打上「版本指纹」,线上排查再也不抓瞎

背景:一个企业微信里排查了半天的坑

最近在做一个跑在企业微信 WebView 里的 Vue3 H5 项目,遇到一个很典型的尴尬场景:

测试说「你这个功能没生效」,我说「我明明发上去了」,然后两个人对着屏幕大眼瞪小眼------因为谁也不知道当前线上部署的到底是哪一版代码。

企业微信 WebView 有几个要命的限制:

  • 默认看不到控制台,vConsole 也不是随时能开;
  • 有 CDN / 网关缓存,你不确定刷新后拿到的是不是最新产物;
  • 多环境(dev / sit / uat / prod)来回发,很容易发错环境还浑然不觉。

所以我需要一个方案:每次打包自动给产物打上版本指纹(版本号 + git commit + 构建时间 + 环境),并且能用最低成本查到它。

这篇就完整记录一下这套方案,代码可直接抄。技术栈:Vue 3 + Vite + TypeScript

方案概览

一句话:构建时采集信息 → 注入为全局常量 + 落一份 version.json

查看方式给三种,覆盖各种环境:

方式 场景 怎么看
访问 /version.json 企微等看不到控制台时首选 浏览器直接开 https://域名/version.json
启动自动打印 能开控制台 / vConsole 进系统自动 console.log 一行
window.appVersion 随时手动查 控制台敲 window.appVersion

信息来源:

  • 版本号package.jsonversion,发版前手动 bump(语义化版本);
  • 构建时间:打包那一刻的时间(固定东八区);
  • 环境:当前构建 mode。

其中只有「版本号」需要人维护,其余全自动。

一步步实现

1. 抽一个「构建信息采集」文件

先把「读版本号 / 读时间」这几件事收拢到一个独立文件 build/version.ts,别塞进 vite.config.ts 里,配置文件会越来越乱。

ts 复制代码
// build/version.ts
import { readFileSync } from 'node:fs'
import { fileURLToPath, URL } from 'node:url'
import type { Plugin } from 'vite'

export interface BuildInfo {
  /** package.json 的版本号,发版前手动 bump */
  version: string
  /** 打包时间(东八区,YYYY-MM-DD HH:mm:ss) */
  buildTime: string
  /** 构建 mode(development / sit / uat / production) */
  env: string
}

/** package.json 的 version */
function readPkgVersion(): string {
  try {
    const pkg = JSON.parse(
      readFileSync(fileURLToPath(new URL('../package.json', import.meta.url)), 'utf-8'),
    )
    return pkg.version || '0.0.0'
  } catch {
    return '0.0.0'
  }
}

/** 构建时间,固定东八区,格式 YYYY-MM-DD HH:mm:ss */
function readBuildTime(): string {
  return new Date().toLocaleString('sv-SE', { timeZone: 'Asia/Shanghai', hour12: false })
}

/** 采集当前构建的版本信息 */
export function resolveBuildInfo(mode: string): BuildInfo {
  return {
    version: readPkgVersion(),
    buildTime: readBuildTime(),
    env: mode,
  }
}

/** 打包结束时把构建信息写一份 version.json 到产物根目录 */
export function versionJsonPlugin(info: BuildInfo): Plugin {
  return {
    name: 'emit-version-json',
    apply: 'build',
    generateBundle() {
      this.emitFile({
        type: 'asset',
        fileName: 'version.json',
        source: JSON.stringify(info, null, 2),
      })
    },
  }
}

这里有两个容易踩的小坑,单独说一下:

坑一:时间补零。 一开始我用 toLocaleString('zh-CN', ...),结果拿到的是 2026-8-14 09:14:36------月和日都不补零,排序、对齐都难受。换成 'sv-SE'(瑞典 locale)就天然是 2026-08-14 09:15:35,补零 + 空格分隔,正好是我想要的格式,一行搞定,不用手写 padStart

坑二:路径。 build/version.ts 在子目录里,读 package.json 要用 ../package.json,别写成 ./package.json

2. vite.config.ts:注入全局常量 + 挂插件

build/version.ts 只是「采集」,真正让信息进入产物要靠 Vite 的 define(注入编译期全局常量)和刚才那个插件(落 version.json)。

ts 复制代码
// vite.config.ts
import { resolveBuildInfo, versionJsonPlugin } from './build/version'

export default defineConfig(({ mode }) => {
  // 采集一次,define 和 plugin 复用同一份
  const buildInfo = resolveBuildInfo(mode)

  return {
    // 编译期把版本信息替换为字面量常量
    define: {
      __APP_VERSION__: JSON.stringify(buildInfo.version),
      __APP_BUILD_TIME__: JSON.stringify(buildInfo.buildTime),
    },
    plugins: [
      // ...其他插件
      versionJsonPlugin(buildInfo),
    ],
  }
})

define 的值必须是 JSON.stringify 后的字符串------Vite 是把它当代码文本 做替换的,你写 __APP_VERSION__: buildInfo.version(比如 1.2.0)会被当成 1.2.0 这段非法 JS,必须 JSON.stringify 包成 "1.2.0"

一个小提醒:definevite dev 下同样生效,此时 buildTime 是你启动 dev-server 的时刻,version.json 则只在 build 时产出(插件加了 apply: 'build')。

3. 给全局常量补类型声明

__APP_VERSION__ 这些常量凭空出现,TS 不认识。在 env.d.ts 里声明一下,顺带给 window.appVersion 也补上类型:

ts 复制代码
// env.d.ts
// 构建期由 vite.config.ts 的 define 注入
declare const __APP_VERSION__: string
declare const __APP_BUILD_TIME__: string

interface Window {
  /** 构建版本信息,启动时挂载,便于控制台随时读取 */
  appVersion?: import('@/utils/env').AppVersion
}

因为构建产物文件(build/version.ts)不在 tsconfig.jsoninclude 里,还要去 tsconfig.node.jsoninclude 补一条 "build/**/*.ts",否则 vue-tsc 会漏检这个文件。

4. 应用侧统一出口

我们项目有条硬规矩:所有 import.meta.env.* 必须经 @/utils/env 出口读取 ,不允许业务代码直接摸 import.meta.env。版本信息也遵循同样的约定,在这里加一个 appVersion 出口:

ts 复制代码
// src/utils/env.ts
export const appVersion = {
  version: __APP_VERSION__,
  buildTime: __APP_BUILD_TIME__,
  /** 汇总展示串,如 `1.2.0 2026-08-14 14:30:00` */
  full: `${__APP_VERSION__} ${__APP_BUILD_TIME__}`,
}

export type AppVersion = typeof appVersion

full 是为了控制台里一眼能读,把版本号和构建时间拼成一行;AppVersion 类型顺手导出,给 window.appVersion 复用,避免重复定义。

5. 启动时打印 + 挂到 window

最后一步,做「查看」。新建一个启动插件,干两件事:启动打印一行、挂到 window 供随时手动读。

ts 复制代码
// src/plugins/version.ts
import { appVersion } from '@/utils/env'

export function setupVersion() {
  window.appVersion = appVersion
  console.log(`[版本] ${appVersion.full}`)
}

然后在应用启动编排里第一个调用它(它没有任何依赖,放最前,保证任何环境都能第一时间看到部署版本):

ts 复制代码
// src/plugins/index.ts
import { setupVersion } from './version'

export function setupPlugins(app: App) {
  setupVersion() // 无依赖,放最前
  setupPinia(app)
  // ...后续 router / vant / ...
}

三种查看方式,实测覆盖各种环境

打完包,dist 根目录会多出一个 version.json

json 复制代码
{
  "version": "1.2.0",
  "buildTime": "2026-08-14 09:20:14",
  "env": "sit"
}

方式一:直接访问 /version.json(企微场景首选)

浏览器开 https://<域名>/version.json,不用进应用、不用开控制台,一眼就能确认「线上部署的到底是哪一版」。企微 WebView 看不到控制台时,这是最省事的办法。

方式二:启动日志(能开控制台 / vConsole 时)

进应用自动打印一行:

text 复制代码
[版本] 1.2.0 2026-08-14 09:20:14

方式三:window.appVersion(随时手动查)

控制台里敲:

js 复制代码
window.appVersion       // { version, buildTime, full }
window.appVersion.full  // "1.2.0 2026-08-14 09:20:14"

发版流程:只有版本号要手动维护

整套方案里,只有版本号 version 需要人管 ,构建时间、环境全是打包时自动采集。所以发版前记得先 bump 一下 package.json 的版本号(遵循语义化版本):

sh 复制代码
# 补丁 / 小版本 / 大版本,按语义化版本选一个
npm version patch   # 1.2.0 → 1.2.1
npm version minor   # 1.2.0 → 1.3.0
npm version major   # 1.2.0 → 2.0.0

# 然后正常打包,其余信息自动注入
pnpm build:sit

小结

方案不复杂,核心就一句话:构建时采集信息 → define 注入全局常量 + 插件落一份 version.json。落地下来最实用的两点:

  • version.json 是排查线上部署版本的银弹,尤其在企微这种开不了控制台的环境,访问一个静态文件就够了;
  • 构建时间 + 环境比版本号更「诚实」,版本号靠人 bump 容易忘,但时间和环境是打包时自动盖的戳,「发没发对环境、是不是刚发的」一目了然。

从此测试再说「你这功能没生效」,先让他开一下 /version.json 对下版本和时间,是不是发错环境、是不是缓存没刷,当场就有答案。

相关推荐
西安小哥4 小时前
破局与重生:大厂前端如何借力 AI 转型“超级全栈“
前端·人工智能
sunly_4 小时前
TypeScript总结:16、面向对象速查
前端·javascript·typescript
MXN_小南学前端4 小时前
React超长文本域中实现“返回顶部”浮动按钮
前端·javascript·react.js
学习嵌入式的小周4 小时前
Notepad++8.8.7下载安装(附安装包)
前端·notepad++
gs801405 小时前
解构 Cordis:面向“时空可组合性”的 TypeScript 元框架深度剖析
前端·javascript·typescript
岁岁种桃花儿5 小时前
Vue核心语法第一篇:Vue是什么?
前端·javascript·vue.js
观无6 小时前
若依EasyExcel实现单元格合并
开发语言·前端·javascript
愚公搬代码6 小时前
【愚公系列】《Web应用安全》001-VMware的安装
前端·安全
xiaohaiAIgeo6 小时前
【2026年】HG/T 20656-2024化工暖通空调设计规范:新版标准的变化与影响
java·前端·javascript·科普知识
立少→万能汉编6 小时前
用“立少→超文本”写静态网页,标签<倍>
服务器·前端·javascript