vite-plugin-build-version-file
vite-plugin-build-version-file
一个用于 Vite 项目的构建版本信息插件。
它负责在开发态和构建产物中暴露统一的版本信息:
- 开发态通过 dev server 提供
version.json - 开发态通过 dev server 提供
loading.html,便于本地调试 - 构建后在输出目录生成
version.json - 构建后生成与
index.html同级的loading.html缓存清理页 - 普通 Vite HTML 自动注入
window["__VERSION__"] - 可选通过
define将同一份 payload 编译进 JS 产物 - VitePress 可通过顶层
transformHtml注入同一份版本信息 - 支持自定义版本号、输出文件名、全局变量名、时区和额外字段
插件只负责生成和暴露版本信息,不负责运行时更新策略。
它不会内置这些业务行为:
- 定时轮询新版本
- 弹窗提示用户刷新
- 用户取消后的重试策略
- 自动刷新页面的时机
这些行为通常应放在业务项目中实现。
适用范围
- Vite 项目
- Vue 3 + Vite 项目
- VitePress 项目
- Nuxt 项目(需自行接
render:html,见下) peerDependencies要求vite >= 5- Node.js 要求
>= 18
安装
pnpm add vite-plugin-build-version-file
也可以使用 npm:
npm install vite-plugin-build-version-file
快速开始
普通 Vite / Vue 3 项目
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import buildVersionPlugin from 'vite-plugin-build-version-file'
export default defineConfig({
plugins: [
vue(),
buildVersionPlugin()
]
})
VitePress 项目
VitePress 构建 HTML 时不完全走普通 Vite index.html 注入链路。需要复用同一个插件实例,并把它的 transformHtml 接到 VitePress 顶层配置上。
import { defineConfig } from 'vitepress'
import buildVersionPlugin from 'vite-plugin-build-version-file'
const buildVersion = buildVersionPlugin()
export default defineConfig({
vite: {
plugins: [buildVersion],
},
transformHtml: buildVersion.transformHtml,
})
注意:这里必须复用同一个 buildVersion 实例。不要分别调用两次 buildVersionPlugin(),否则 transformHtml 拿不到 Vite 插件阶段生成的 payload。
VitePress 构建会包含 client bundle 和 SSR bundle。插件只在最终静态产物 bundle 输出 version.json,避免同一次 VitePress build 出现重复日志或重复 emit。
Nuxt 项目
Nuxt 的 HTML 由 Nitro 渲染,Vite 客户端构建的入口是 JS 模块而不是 index.html,所以插件的 transformIndexHtml 在 Nuxt 里不会被调用,window["__VERSION__"] 不会自动注入。
同样复用同一个插件实例,把它的 headScript() 推进 Nuxt render:html 钩子的 html.head 数组:
import buildVersionPlugin from 'vite-plugin-build-version-file'
const buildVersion = buildVersionPlugin()
export default defineNuxtConfig({
vite: {
plugins: [buildVersion]
},
hooks: {
'render:html'(html) {
html.head.push(buildVersion.headScript())
}
}
})
headScript() 返回完整的 <script>...</script> 片段;injectToHtml: false 时、或 payload 还没生成时返回空字符串(不抛错),所以直接 push 是安全的。
插件会自动识别 Nuxt 的构建资产目录,并把 version.json emit 到该目录,因此 Nuxt 打包后的静态产物会包含这个文件。默认文件路径为 /_nuxt/version.json;如果需要固定使用根路径 /version.json,请将文件复制到 Nuxt public 目录,或在构建后自行复制。
默认行为
默认配置下,插件会做这些事:
- 开发态访问
/version.json时返回版本信息 - 构建后输出
version.json - 构建后输出
loading.html - 向 HTML 注入
window["__VERSION__"] - 自动读取业务项目最近的
package.json - 将
package.json的name写入pkgName - 将
package.json的version写入pkgVersion - 开发态默认版本号为
0 - 构建态默认版本号为当前时间,格式为
YYYYMMDDHHmmss - 默认时区为
Asia/Shanghai
构建态默认输出示例:
{
"pkgName": "your-app-name",
"pkgVersion": "1.0.0",
"time": "20260401153045",
"env": "production"
}
开发态默认输出示例:
{
"pkgName": "your-app-name",
"pkgVersion": "1.0.0",
"time": 0,
"env": "development"
}
配置项
type BuildVersionContext = {
command: 'serve' | 'build'
mode: string
}
type BuildVersionPluginOptions = {
filename?: string
loadingPage?: string | false
globalName?: string
defineName?: string | false
injectToHtml?: boolean
timeZone?: string
version?: string | number | ((ctx: BuildVersionContext) => string | number)
data?: Record<string, any>
log?: (content: any) => string
payload?: (json: any) => any
}
filename
- 类型:
string - 默认值:
"version.json" - 作用:控制开发态访问路径和构建态输出文件路径
示例:
buildVersionPlugin({
filename: 'meta/version.json'
})
构建后输出:
dist/meta/version.json
开发态访问:
/meta/version.json
如果项目配置了 Vite base,访问路径会自动跟随 base。
loadingPage
- 类型:
string | false - 默认值:
"loading" - 作用:控制构建产物中缓存清理页的文件名
默认输出 loading.html,与 index.html 同级。值不带后缀时会自动补 .html,也支持子路径:
buildVersionPlugin({
loadingPage: 'cache/loading'
})
构建后输出:
dist/cache/loading.html
传 false 时不生成该页面。
Nuxt 下与 version.json 使用同一规则,默认输出到 _nuxt/loading.html;如果需要固定使用根路径 /loading.html,请将文件复制到 Nuxt public 目录,或在构建后自行复制。
globalName
- 类型:
string - 默认值:
"__VERSION__" - 作用:控制注入到
window上的属性名
默认注入效果:
<script>
window["__VERSION__"] = { "time": "20260401153045", "env": "production" };
</script>
这里注入的是完整 payload 对象,不是单独版本字符串。
defineName
- 类型:
string | false - 默认值:
"__VERSION__" - 作用:控制是否通过 Vite
define将完整 payload 编译进 JS 产物
示例:
buildVersionPlugin({
defineName: '__APP_VERSION__'
})
业务代码可直接读取:
console.log(__APP_VERSION__)
打包后会在编译阶段被替换成字面量对象,因此适合给 Vue、TS、JS 模块直接消费。
默认情况下会注入 __VERSION__。如果你不需要编译期常量,可显式传 defineName: false 关闭。
如果同时开启 defineName 和 globalName,两者会共用同一份 payload:
defineName写入编译后的 JSglobalName注入运行时 HTMLversion.json仍照常输出
injectToHtml
- 类型:
boolean - 默认值:
true - 作用:控制是否向 HTML 注入
window["__VERSION__"]
设置为 false 后,仍会生成和提供 version.json。
timeZone
- 类型:
string - 默认值:
"Asia/Shanghai" - 作用:控制默认构建时间按哪个时区生成
只影响插件默认生成的 time。如果你传了 version,则以自定义逻辑为准。
version
- 类型:
string | number | ((ctx) => string | number) - 默认值:开发态为
0,构建态为当前时间戳 - 作用:自定义版本号生成逻辑
buildVersionPlugin({
version: ({ command, mode }) => {
if (command === 'serve') {
return 0
}
return `${mode}-20260401153045`
}
})
data
- 类型:
Record<string, any> - 默认值:
{} - 作用:向最终 payload 追加自定义字段
buildVersionPlugin({
data: {
appName: 'admin',
gitBranch: 'release/2026-04',
commitSha: 'abc1234'
}
})
生成结果示例:
{
"appName": "admin",
"gitBranch": "release/2026-04",
"commitSha": "abc1234",
"pkgName": "your-app-name",
"pkgVersion": "1.0.0",
"time": "20260401153045",
"env": "production"
}
字段覆盖顺序:
data会先写入pkgName、pkgVersion会使用项目package.json的值覆盖同名字段time、env会使用插件生成的值覆盖同名字段payload可以在最后改写整个结果
payload
- 类型:
(json: any) => any - 作用:最终改写 payload
适合做字段裁剪、字段重命名或兼容旧系统。
buildVersionPlugin({
payload: (json) => ({
version: json.time,
env: json.env,
name: json.pkgName
})
})
建议返回对象,避免业务端读取结构不稳定。
log
- 类型:
(content: any) => string - 作用:自定义构建完成后的控制台输出
buildVersionPlugin({
log: (payload) => `build version: ${payload.time}`
})
读取版本信息
读取全局变量
const payload = window['__VERSION__']
console.log(payload.time)
console.log(payload.env)
适用于需要知道“当前页面构建版本”的场景。
读取 version.json
const response = await fetch('/version.json', {
cache: 'no-store'
})
const payload = await response.json()
console.log(payload.time)
console.log(payload.env)
适用于和服务器上的最新版本做对比。
做版本检查
插件不内置版本检查 UI。推荐业务项目自行处理。下面示例可以直接放到应用入口、布局组件或独立的版本检查模块中:
const CHECK_INTERVAL = 60 * 1000
const CANCEL_RETRY_DELAY = 5 * 60 * 1000
const VERSION_URL = '/version.json'
// 使用 window 共享状态,避免多个组件或入口各自创建轮询和更新弹窗。
const versionCheckState = (window.__VERSION_CHECK_STATE__ ??= {
timer: 0,
checking: false,
promptPromise: null
})
function scheduleVersionCheck(delay) {
window.clearTimeout(versionCheckState.timer)
versionCheckState.timer = window.setTimeout(() => {
void checkVersion()
}, delay)
}
function showUpdatePrompt() {
if (versionCheckState.promptPromise) {
return versionCheckState.promptPromise
}
// 使用 Element Plus 等异步弹窗时,替换为返回 Promise<boolean> 的业务弹窗函数。
versionCheckState.promptPromise = Promise.resolve(
window.confirm('检测到新版本,是否立即刷新页面?')
).finally(() => {
versionCheckState.promptPromise = null
})
return versionCheckState.promptPromise
}
async function checkVersion() {
// 防止多个入口同时发起版本请求。
if (versionCheckState.checking) {
return
}
versionCheckState.checking = true
let nextDelay = CHECK_INTERVAL
try {
const response = await fetch(VERSION_URL, {
cache: 'no-store'
})
if (!response.ok) {
throw new Error(`version request failed: ${response.status}`)
}
const latestPayload = await response.json()
const currentPayload = window.__VERSION__
if (currentPayload && latestPayload.time !== currentPayload.time) {
const shouldReload = await showUpdatePrompt()
if (shouldReload) {
window.location.reload()
return
}
// 用户取消后,5 分钟后再检查,避免短时间内重复打扰。
nextDelay = CANCEL_RETRY_DELAY
}
} catch (error) {
console.warn('check version failed', error)
} finally {
versionCheckState.checking = false
scheduleVersionCheck(nextDelay)
}
}
void checkVersion()
这个示例有几个关键点:
- 通常比较
payload.time即可 - 请求
version.json时使用cache: 'no-store' window.__VERSION_CHECK_STATE__是当前页面内的共享状态;多个组件重复调用checkVersion()时,只会保留一个检查流程promptPromise让更新弹窗成为单例;同一时间已有弹窗时,不会再次弹出- 用户确认后立即刷新页面;用户取消后固定等待 5 分钟,再开始下一次检查
- 没有检测到更新或请求失败时,按
CHECK_INTERVAL继续检查 CHECK_INTERVAL通常设置为 1 分钟或更长,避免产生过多请求- 如果项目配置了自定义
globalName,将示例中的window.__VERSION__换成对应的全局变量
示例使用原生 window.confirm,因此不依赖 UI 框架。接入 Element Plus、Ant Design 等异步弹窗时,只需让 showUpdatePrompt 内部调用业务弹窗,并在确认/取消时分别返回 true/false;不要在多个组件中各自维护一份弹窗状态。
刷新指定项目缓存
import { redirectToLoading } from 'vite-plugin-build-version-file/client'
redirectToLoading({
urls: ['/vue-app/', '/vue-app/index.html'],
redirectUrl: '/vue-app/',
time: 5,
times: 2,
title: '正在清理缓存',
filename: 'loading.html'
})
redirectToLoading 只负责携带参数跳转到构建产物中的 loading.html,不向当前页面注入任何 DOM,也不在主页面执行清理逻辑。缓存清理与跳转全部发生在 loading.html 内:
- 打开后立即执行清理,并显示全屏 loading
- 缓存处理提前完成时会立即跳转;到达
time时限(单位秒,默认5)后无论调用结果如何都会跳转 - 跳转发生在
loading.html内,主页面代码无需等待
参数对象可省略:
redirectToLoading()
无参时按顺序检测以下两组 URL:
location.origin + location.pathname- 第一组 URL 拼接
index.html
完成后跳转回 window.location.href(query 与 hash 原样保留,不追加缓存戳)。
参数说明:
urls:需要清理缓存的 URL 列表,可传多个。默认如上两条redirectUrl:清理完成或超时后跳转的目标 URL。默认为调用时的window.location.hreftime:最长等待秒数,默认5times:整套清理操作重复执行的次数,默认2title:传入后在 loading 页中心显示该文案;不传只显示 loading 图标filename:loading.html的路径,默认loading.html,相对当前页面解析
除 filename 外的参数会合并成一个 JSON 对象,写在随机参数名的 query 参数下;loading 页按参数值解析,不依赖参数名。
loading 页默认透明背景,不遮挡页面底色;title 传了才显示文字。
仍然可以用旧名调用,行为一致:
import { clearUrlCache } from 'vite-plugin-build-version-file/client'
clearUrlCache({ urls: ['/vue-app/'] })
Vue hash 路由会原样保留。缓存戳只加在 loading 页内部的 fetch / iframe 请求上,用于绕过旧缓存;最终跳转地址原样使用,不附加任何参数:
调用页:https://xxxx/pc/#/login?redirect=/dashboard/analysis
清理请求:https://xxxx/pc/?随机参数名=随机值
最终跳转:https://xxxx/pc/#/login?redirect=/dashboard/analysis
loading.html 内仅支持同源 URL。它会绕过缓存请求 URL、强制刷新原 URL、通过隐藏 iframe 再次加载页面,最后跳转到目标 URL(不附加缓存戳)。创建 iframe 前会销毁同一页面中已有的缓存刷新 iframe。单次请求或 iframe 加载失败不会阻断最终跳转。
受浏览器限制,前端代码无法直接删除指定 URL 下的全部 HTTP 缓存;该方法只能尽可能刷新对应 Vue 项目的缓存。
直接打开 loading.html 且不带 query 时,默认以 loading.html 所在目录作为应用根路径执行同一套清理。
开发态行为
开发态下,插件不会向业务项目目录写入文件。
它会通过 Vite dev server 直接返回,不落文件:
version.json:版本信息 JSONloading.html:与构建态同路径规则(可用loadingPage调整),可直接在浏览器打开调试清理与跳转流程- 不污染
public/ - 不需要生成临时文件
- 修改插件配置后重启 dev server 即可生效
调试 loading 页时,query 参数与构建态一致:参数名随机,值是包含 urls 的 JSON,例如:
http://localhost:5173/loading.html?x7k2q9={"urls":["http://localhost:5173/","http://localhost:5173/index.html"],"redirectUrl":"http://localhost:5173/","time":5,"times":2,"title":"调试"}
也可以直接打开 http://localhost:5173/loading.html,按所在目录执行默认清理。
本地开发
仓库提供了两个调试用示例项目:
- 普通 Vite / Vue 3:
debug/fixture-app - VitePress:
debug/vitepress-app - 引用方式:直接引用根目录
dist/index.mjs
这意味着示例项目测试的是打包后的真实产物。
pnpm install
pnpm build
pnpm dev:fixture
调试 VitePress 示例项目:
pnpm dev:vitepress
构建示例项目:
pnpm build:fixture
pnpm build:vitepress
完整检查打包流程:
pnpm build
pnpm build:fixture
pnpm build:vitepress
npm pack --dry-run
建议检查:
debug/fixture-app/dist/version.json包含time、env、pkgName、pkgVersiondebug/fixture-app/dist/loading.html与index.html同级生成debug/fixture-app/dist/index.html只注入一次window["__VERSION__"]debug/vitepress-app/.vitepress/dist/meta/version.json包含fixture、time、env- VitePress 每个 HTML 只注入一次
window["__VERSION__"] - VitePress build 日志只输出一次插件 payload
发布
pnpm build
pnpm build:fixture
pnpm build:vitepress
npm publish --access public
发布前建议确认:
dist/index.mjs已生成dist/index.cjs已生成dist/index.d.ts已生成debug/fixture-app可以正常构建debug/vitepress-app可以正常构建version.json输出正常- 普通 Vite HTML 注入正常
- VitePress
transformHtml接入后注入正常 - VitePress build 没有重复输出插件 payload 日志
查看最终发包内容:
npm pack
正常发布包应只包含:
distREADME.mdLICENSEpackage.json
English
vite-plugin-build-version-file is a build metadata plugin for Vite projects.
It exposes one consistent version payload in development and production:
- serves
version.jsonthrough the Vite dev server - serves
loading.htmlthrough the Vite dev server for local debugging - emits
version.jsoninto the build output directory - emits a
loading.htmlcache-clearing page next toindex.html - injects
window["__VERSION__"]into normal Vite HTML - supports VitePress through its top-level
transformHtmlhook - supports custom versions, filenames, global variable names, time zones, and extra payload fields
The plugin only generates and exposes version metadata. It does not decide how your app should check for updates or refresh the page.
It intentionally does not include:
- polling logic
- update prompts
- retry rules
- reload timing
Those behaviors should stay in your application code.
Compatibility
- Vite projects
- Vue 3 + Vite projects
- VitePress projects
- Nuxt projects (wire up
render:htmlyourself, see below) vite >= 5- Node.js
>= 18
Installation
pnpm add vite-plugin-build-version-file
Or:
npm install vite-plugin-build-version-file
Quick Start
Vite / Vue 3
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import buildVersionPlugin from 'vite-plugin-build-version-file'
export default defineConfig({
plugins: [
vue(),
buildVersionPlugin()
]
})
VitePress
VitePress does not write production HTML through the same index.html flow as a normal Vite app. Reuse one plugin instance and pass its transformHtml helper to the top-level VitePress config.
import { defineConfig } from 'vitepress'
import buildVersionPlugin from 'vite-plugin-build-version-file'
const buildVersion = buildVersionPlugin()
export default defineConfig({
vite: {
plugins: [buildVersion],
},
transformHtml: buildVersion.transformHtml,
})
Use the same buildVersion instance in both places. Calling buildVersionPlugin() twice creates two isolated instances, and the VitePress transformHtml helper will not receive the payload created during the Vite plugin lifecycle.
VitePress build includes a client bundle and an SSR bundle. The plugin emits version.json only from the final static bundle, so one VitePress build does not print duplicate payload logs or emit the same asset twice.
Nuxt
Nuxt renders HTML through Nitro, and its Vite client build entry is a JS module rather than an index.html, so the plugin's transformIndexHtml is never called and window["__VERSION__"] is not injected automatically.
Reuse the same plugin instance and push its headScript() into the html.head array of the Nuxt render:html hook:
import buildVersionPlugin from 'vite-plugin-build-version-file'
const buildVersion = buildVersionPlugin()
export default defineNuxtConfig({
vite: {
plugins: [buildVersion]
},
hooks: {
'render:html'(html) {
html.head.push(buildVersion.headScript())
}
}
})
headScript() returns a complete <script>...</script> snippet. It returns an empty string when injectToHtml: false or when the payload has not been created yet, so pushing it directly is safe.
The plugin automatically detects Nuxt's build assets directory and emits version.json there, so Nuxt's static output includes the file. The default URL is /_nuxt/version.json; if the file must stay at /version.json, copy it into Nuxt's public directory or copy it after the build.
Default Behavior
With default options, the plugin:
- serves
/version.jsonin development - emits
version.jsonafter build - emits
loading.htmlafter build - injects
window["__VERSION__"]into HTML - reads the nearest project
package.json - writes
nameaspkgName - writes
versionaspkgVersion - uses
0as the development version - uses
YYYYMMDDHHmmssas the production version - uses
Asia/Shanghaias the default time zone
Production payload example:
{
"pkgName": "your-app-name",
"pkgVersion": "1.0.0",
"time": "20260401153045",
"env": "production"
}
Development payload example:
{
"pkgName": "your-app-name",
"pkgVersion": "1.0.0",
"time": 0,
"env": "development"
}
Options
type BuildVersionContext = {
command: 'serve' | 'build'
mode: string
}
type BuildVersionPluginOptions = {
filename?: string
loadingPage?: string | false
globalName?: string
injectToHtml?: boolean
timeZone?: string
version?: string | number | ((ctx: BuildVersionContext) => string | number)
data?: Record<string, any>
log?: (content: any) => string
payload?: (json: any) => any
}
filename
- Type:
string - Default:
"version.json" - Controls the dev URL and build output path.
buildVersionPlugin({
filename: 'meta/version.json'
})
Build output:
dist/meta/version.json
Development URL:
/meta/version.json
If your project configures Vite base, the URL follows that base.
loadingPage
- Type:
string | false - Default:
"loading" - Controls the filename of the cache-clearing page in the build output.
The default emits loading.html next to index.html. When the value has no suffix, .html is appended; sub-paths are supported:
buildVersionPlugin({
loadingPage: 'cache/loading'
})
Build output:
dist/cache/loading.html
Pass false to skip emitting the page.
Nuxt uses the same rule as version.json and emits _nuxt/loading.html by default. If the file must stay at /loading.html, copy it into Nuxt's public directory or copy it after the build.
globalName
- Type:
string - Default:
"__VERSION__" - Controls the property name injected on
window.
Default injection:
<script>
window["__VERSION__"] = { "time": "20260401153045", "env": "production" };
</script>
The injected value is the full payload object, not a single version string.
injectToHtml
- Type:
boolean - Default:
true - Controls whether the plugin injects
window["__VERSION__"].
When set to false, version.json is still served and emitted.
timeZone
- Type:
string - Default:
"Asia/Shanghai" - Controls the time zone used by the default production timestamp.
This only affects the default time. If you pass version, your custom logic wins.
version
- Type:
string | number | ((ctx) => string | number) - Default:
0in development, current timestamp in production - Provides custom version generation.
buildVersionPlugin({
version: ({ command, mode }) => {
if (command === 'serve') {
return 0
}
return `${mode}-20260401153045`
}
})
data
- Type:
Record<string, any> - Default:
{} - Adds custom fields to the final payload.
buildVersionPlugin({
data: {
appName: 'admin',
gitBranch: 'release/2026-04',
commitSha: 'abc1234'
}
})
Example output:
{
"appName": "admin",
"gitBranch": "release/2026-04",
"commitSha": "abc1234",
"pkgName": "your-app-name",
"pkgVersion": "1.0.0",
"time": "20260401153045",
"env": "production"
}
Merge order:
datais written firstpkgNameandpkgVersionfrompackage.jsonoverride matching fieldstimeandenvfrom the plugin override matching fieldspayloadcan rewrite the final object
payload
- Type:
(json: any) => any - Rewrites the final payload.
Use it to trim fields, rename fields, or keep compatibility with an older contract.
buildVersionPlugin({
payload: (json) => ({
version: json.time,
env: json.env,
name: json.pkgName
})
})
Returning an object is recommended so application reads stay stable.
log
- Type:
(content: any) => string - Customizes the build-complete console output.
buildVersionPlugin({
log: (payload) => `build version: ${payload.time}`
})
Reading Version Metadata
Read the injected global
const payload = window['__VERSION__']
console.log(payload.time)
console.log(payload.env)
Use this when you need the version of the currently loaded page.
Read version.json
const response = await fetch('/version.json', {
cache: 'no-store'
})
const payload = await response.json()
console.log(payload.time)
console.log(payload.env)
Use this when you need to compare the current page against the latest deployed version.
Checking for Updates
The plugin does not include update-checking UI. Keep that logic in your app. The following example can be placed in your app entry, layout component, or a dedicated version-check module:
const CHECK_INTERVAL = 60 * 1000
const CANCEL_RETRY_DELAY = 5 * 60 * 1000
const VERSION_URL = '/version.json'
// 使用 window 共享状态,让多个组件或入口复用同一套检查和弹窗。
const versionCheckState = (window.__VERSION_CHECK_STATE__ ??= {
timer: 0,
checking: false,
promptPromise: null
})
function scheduleVersionCheck(delay) {
window.clearTimeout(versionCheckState.timer)
versionCheckState.timer = window.setTimeout(() => {
void checkVersion()
}, delay)
}
function showUpdatePrompt() {
if (versionCheckState.promptPromise) {
return versionCheckState.promptPromise
}
// 使用异步业务弹窗时,替换为返回 Promise<boolean> 的弹窗函数。
versionCheckState.promptPromise = Promise.resolve(
window.confirm('A new version is available. Reload now?')
).finally(() => {
versionCheckState.promptPromise = null
})
return versionCheckState.promptPromise
}
async function checkVersion() {
// 防止多个入口同时发起版本请求。
if (versionCheckState.checking) {
return
}
versionCheckState.checking = true
let nextDelay = CHECK_INTERVAL
try {
const response = await fetch(VERSION_URL, {
cache: 'no-store'
})
if (!response.ok) {
throw new Error(`version request failed: ${response.status}`)
}
const latestPayload = await response.json()
const currentPayload = window.__VERSION__
if (currentPayload && latestPayload.time !== currentPayload.time) {
const shouldReload = await showUpdatePrompt()
if (shouldReload) {
window.location.reload()
return
}
// 用户取消后,等待 5 分钟再开始下一次检查。
nextDelay = CANCEL_RETRY_DELAY
}
} catch (error) {
console.warn('check version failed', error)
} finally {
versionCheckState.checking = false
scheduleVersionCheck(nextDelay)
}
}
void checkVersion()
Key points:
- Compare
payload.timein most cases. - Fetch
version.jsonwithcache: 'no-store'. window.__VERSION_CHECK_STATE__is shared by the current page. RepeatedcheckVersion()calls reuse one check flow.promptPromisemakes the update prompt a singleton, so an already-open prompt is not shown again.- Confirming reloads immediately. Canceling waits five minutes before the next check.
- No update or a failed request uses
CHECK_INTERVALfor the next check. - Set
CHECK_INTERVALto one minute or longer to avoid excessive requests. - If you configure a custom
globalName, replacewindow.__VERSION__with that global variable.
The example uses native window.confirm, so it has no UI framework dependency. With Element Plus, Ant Design, or another async dialog, replace the body of showUpdatePrompt with your app dialog and resolve true on confirm or false on cancel. Keep the shared state in one global object instead of maintaining separate prompt state in each component.
Refreshing a Project Cache
import { redirectToLoading } from 'vite-plugin-build-version-file/client'
redirectToLoading({
urls: ['/vue-app/', '/vue-app/index.html'],
redirectUrl: '/vue-app/',
time: 5,
times: 2,
title: 'Clearing cache',
filename: 'loading.html'
})
redirectToLoading only builds the query string and navigates to the loading.html emitted by the build. It injects nothing into the current page and runs no cleanup logic there. Cache clearing and navigation both happen inside loading.html:
- cleanup starts as soon as the page opens, with a full-screen loading mask
- navigation happens as soon as cleanup finishes; once
timeseconds (default5) elapse, it happens regardless of the result - navigation is triggered inside
loading.html, so the host page does not wait
The options object is optional:
redirectToLoading()
Without arguments, the following URL groups are checked in order:
location.origin + location.pathname- The first URL with
index.htmlappended
After both groups complete, navigation goes back to the caller's window.location.href; query and hash are preserved and no cache-busting parameter is appended.
Options:
urls: cache-clearing targets; multiple URLs are supportedredirectUrl: target after cleanup or timeout; defaults towindow.location.hrefat call timetime: maximum wait in seconds, default5times: how many times the whole cleanup pass runs, default2title: when provided, shows that text in the loading page center; when omitted, only the loading icon is shownfilename: path of the loading page, defaultloading.html, resolved against the current page
Every value except filename is merged into a single JSON object placed under a randomly named query parameter; the loading page parses values, not parameter names.
The loading page uses a transparent background by default, and only shows text when title is provided.
The old name still works and behaves the same:
import { clearUrlCache } from 'vite-plugin-build-version-file/client'
clearUrlCache({ urls: ['/vue-app/'] })
Vue hash routes are preserved. Cache-busting parameters are added only to the fetch / iframe requests made by the loading page; the final navigation target stays exactly as given:
caller: https://xxx.cn/pc/#/login?redirect=/dashboard/analysis
cleanup request: https://xxx.cn/pc/?random-key=random-value
final navigation: https://xxx.cn/pc/#/login?redirect=/dashboard/analysis
loading.html only supports same-origin URLs. It bypasses the cached URL, forces a refresh of the original URL, loads the page again in a hidden iframe, and finally navigates to the target URL without any cache-busting parameter. Any existing cache-refresh iframe created by this method is removed before a new one is created. A failed request or iframe load does not prevent the final navigation.
Browser code cannot directly delete every HTTP cache entry under a specific URL. This method refreshes the corresponding Vue application cache on a best-effort basis.
Opening loading.html directly without a query string treats the page's own directory as the application root and runs the same cleanup flow.
Development Behavior
In development, the plugin does not write files into your project directory.
It serves content directly through the Vite dev server without writing files:
version.json: the version payload as JSONloading.html: the same cache-clearing page as the build output (path configured byloadingPage), so you can open it directly in the browser to debug the cleanup and navigation flow- no
public/pollution - no temporary file generation
- restart the dev server after changing plugin options
The loading page accepts the same query parameters in development: the parameter name is random and the value is JSON containing urls, for example:
http://localhost:5173/loading.html?x7k2q9={"urls":["http://localhost:5173/","http://localhost:5173/index.html"],"redirectUrl":"http://localhost:5173/","time":5,"times":2,"title":"debug"}
You can also open http://localhost:5173/loading.html directly to run the default cleanup for its own directory.
Local Development
The repository contains two fixture apps:
- Vite / Vue 3:
debug/fixture-app - VitePress:
debug/vitepress-app - They import the built package from
dist/index.mjs
This verifies the real build output instead of importing source files directly.
pnpm install
pnpm build
pnpm dev:fixture
Run the VitePress fixture:
pnpm dev:vitepress
Build fixture apps:
pnpm build:fixture
pnpm build:vitepress
Full local package check:
pnpm build
pnpm build:fixture
pnpm build:vitepress
npm pack --dry-run
Check:
debug/fixture-app/dist/version.jsoncontainstime,env,pkgName, andpkgVersiondebug/fixture-app/dist/loading.htmlis emitted next toindex.htmldebug/fixture-app/dist/index.htmlinjectswindow["__VERSION__"]oncedebug/vitepress-app/.vitepress/dist/meta/version.jsoncontainsfixture,time, andenv- each VitePress HTML file injects
window["__VERSION__"]once - VitePress build prints the plugin payload log once
Publishing
pnpm build
pnpm build:fixture
pnpm build:vitepress
npm publish --access public
Before publishing, check:
dist/index.mjsdist/index.cjsdist/index.d.ts- fixture app build
- VitePress fixture build
version.jsonoutput- normal Vite HTML injection
- VitePress
transformHtmlinjection - no duplicate plugin payload logs during VitePress build
Inspect the publish package:
npm pack
The package should contain:
distREADME.mdLICENSEpackage.json
License
MIT
Related Packages
Vite plugin that runs TypeScript type checker on a separate process.
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 0.2.0 | ^3.4.0 | MIT | |
| 0.1.18 | ^3.4.0 | MIT | |
| 0.1.17 | ^3.4.0 | MIT | |
| 0.1.16 | ^3.4.0 | MIT | |
| 0.1.15 | ^3.4.0 | MIT | |
| 0.1.14 | ^3.4.0 | MIT | |
| 0.1.13 | ^3.4.0 | MIT | |
| 0.1.12 | ^3.4.0 | MIT | |
| 0.1.11 | ^3.4.0 | MIT | |
| 0.1.10 | ^3.4.0 | MIT | |
| 0.1.9 | ^3.4.0 | MIT | |
| 0.1.8 | ^3.4.0 | MIT | |
| 0.1.7 | ^3.4.0 | MIT | |
| 0.1.6 | ^3.4.0 | MIT | |
| 0.1.5 | ^3.4.0 | MIT | |
| 0.1.4 | ^3.4.0 | MIT | |
| 0.1.3 | ^3.4.0 | MIT | |
| 0.1.2 | ^3.4.0 | MIT | |
| 0.1.0 | ^3.4.0 | MIT |