npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

vite-plugin-build-version-file

v0.1.18

Published

A Vite plugin that emits version.json and injects version metadata into HTML.

Readme

vite-plugin-build-version-file

一个用于 Vite 项目的构建版本信息插件。

它负责在开发态和构建产物中暴露统一的版本信息:

  • 开发态通过 dev server 提供 version.json
  • 构建后在输出目录生成 version.json
  • 普通 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 的落点和 Vite / VitePress 不同:插件通过 generateBundle emit 到 Vite 客户端构建目录的根,而 Nuxt 发布到静态目录的是 assetsDir 内的内容,根目录上的文件不会被带出去。需要线上能访问这个文件时,要么把 filename 指到 assetsDir 内(例如 _nuxt/version.json),要么在构建后自行复制到 public 目录。

默认行为

默认配置下,插件会做这些事:

  • 开发态访问 /version.json 时返回版本信息
  • 构建后输出 version.json
  • 向 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
  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。

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 写入编译后的 JS
  • globalName 注入运行时 HTML
  • version.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 { clearUrlCache } from 'vite-plugin-build-version-file/client'

await clearUrlCache({
  urls: ['/vue-app/', '/vue-app/index.html'],
  redirectUrl: '/vue-app/',
  time: 5
})

调用后会显示全屏 loading 和刷新倒计时。time 单位为秒,默认值为 5;缓存处理提前完成时会立即刷新,到达时限后则无论调用结果如何都会刷新。

参数对象可省略。无参时按顺序检测以下两组 URL:

  1. location.origin + location.pathname
  2. 第一组 URL 拼接 index.html

两组完成后,默认携带随机时间戳跳转到第一组 URL:

await clearUrlCache()

Vue hash 路由会原样保留,缓存时间戳写在 # 前,确保服务器能够收到:

https://xxxx/pc/?_=随机值#/login?redirect=/dashboard/analysis

该方法仅支持同源 URL。它会绕过缓存请求 URL、强制刷新原 URL、通过隐藏 iframe 再次加载页面,最后携带随机时间戳跳转到该 URL。创建 iframe 前会销毁同一页面中已有的缓存刷新 iframe。单次请求或 iframe 加载失败不会阻断最终跳转。

受浏览器限制,前端代码无法直接删除指定 URL 下的全部 HTTP 缓存;该方法只能尽可能刷新对应 Vue 项目的缓存。

开发态行为

开发态下,插件不会向业务项目目录写入文件。

它会通过 Vite dev server 直接返回 JSON:

  • 不污染 public/
  • 不需要生成临时文件
  • 修改插件配置后重启 dev server 即可生效

本地开发

仓库提供了两个调试用示例项目:

  • 普通 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、pkgVersion
  • 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

正常发布包应只包含:

  • dist
  • README.md
  • LICENSE
  • package.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.json through the Vite dev server
  • emits version.json into the build output directory
  • injects window["__VERSION__"] into normal Vite HTML
  • supports VitePress through its top-level transformHtml hook
  • 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:html yourself, 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.

Note that version.json lands in a different place in Nuxt: the plugin emits it through generateBundle at the root of the Vite client build directory, while Nuxt publishes the contents of the assetsDir into the static output. Files sitting at that build root are not carried over. If the file must be reachable in production, either point filename inside the assetsDir (for example _nuxt/version.json) or copy it into the public directory after the build.

Default Behavior

With default options, the plugin:

  • serves /version.json in development
  • emits version.json after build
  • injects window["__VERSION__"] into HTML
  • reads the nearest project package.json
  • writes name as pkgName
  • writes version as pkgVersion
  • uses 0 as the development version
  • uses YYYYMMDDHHmmss as the production version
  • uses Asia/Shanghai as 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
  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.

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: 0 in 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:

  • data is written first
  • pkgName and pkgVersion from package.json override matching fields
  • time and env from the plugin override matching fields
  • payload can 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.time in most cases.
  • Fetch version.json with cache: 'no-store'.
  • window.__VERSION_CHECK_STATE__ is shared by the current page. Repeated checkVersion() calls reuse one check flow.
  • promptPromise makes 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_INTERVAL for the next check.
  • Set CHECK_INTERVAL to one minute or longer to avoid excessive requests.
  • If you configure a custom globalName, replace window.__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 { clearUrlCache } from 'vite-plugin-build-version-file/client'

await clearUrlCache({
  urls: ['/vue-app/', '/vue-app/index.html'],
  redirectUrl: '/vue-app/',
  time: 5
})

The method displays a full-screen loading mask and refresh countdown after it is called. time is measured in seconds and defaults to 5. It refreshes immediately if cache processing finishes early; once the limit is reached, it refreshes regardless of the processing result.

The options object is optional. Without arguments, the following URL groups are checked in order:

  1. location.origin + location.pathname
  2. The first URL with index.html appended

After both groups complete, the method navigates to the first URL with a random timestamp:

await clearUrlCache()

Vue hash routes are preserved. The cache-busting timestamp is added before # so the server receives it:

https://xxx.cn/pc/?_=random-value#/login?redirect=/dashboard/analysis

This method 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 URL with a random timestamp. 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.

Development Behavior

In development, the plugin does not write files into your project directory.

It serves JSON directly through the Vite dev server:

  • no public/ pollution
  • no temporary file generation
  • restart the dev server after changing plugin options

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.json contains time, env, pkgName, and pkgVersion
  • debug/fixture-app/dist/index.html injects window["__VERSION__"] once
  • debug/vitepress-app/.vitepress/dist/meta/version.json contains fixture, time, and env
  • 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.mjs
  • dist/index.cjs
  • dist/index.d.ts
  • fixture app build
  • VitePress fixture build
  • version.json output
  • normal Vite HTML injection
  • VitePress transformHtml injection
  • no duplicate plugin payload logs during VitePress build

Inspect the publish package:

npm pack

The package should contain:

  • dist
  • README.md
  • LICENSE
  • package.json

License

MIT