vite-plugin-build-version-file 0.1.17 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,7 +5,9 @@
5
5
  它负责在开发态和构建产物中暴露统一的版本信息:
6
6
 
7
7
  - 开发态通过 dev server 提供 `version.json`
8
+ - 开发态通过 dev server 提供 `loading.html`,便于本地调试
8
9
  - 构建后在输出目录生成 `version.json`
10
+ - 构建后生成与 `index.html` 同级的 `loading.html` 缓存清理页
9
11
  - 普通 Vite HTML 自动注入 `window["__VERSION__"]`
10
12
  - 可选通过 `define` 将同一份 payload 编译进 JS 产物
11
13
  - VitePress 可通过顶层 `transformHtml` 注入同一份版本信息
@@ -27,6 +29,7 @@
27
29
  - Vite 项目
28
30
  - Vue 3 + Vite 项目
29
31
  - VitePress 项目
32
+ - Nuxt 项目(需自行接 `render:html`,见下)
30
33
  - `peerDependencies` 要求 `vite >= 5`
31
34
  - Node.js 要求 `>= 18`
32
35
 
@@ -81,12 +84,40 @@ export default defineConfig({
81
84
 
82
85
  VitePress 构建会包含 client bundle 和 SSR bundle。插件只在最终静态产物 bundle 输出 `version.json`,避免同一次 VitePress build 出现重复日志或重复 emit。
83
86
 
87
+ ### Nuxt 项目
88
+
89
+ Nuxt 的 HTML 由 Nitro 渲染,Vite 客户端构建的入口是 JS 模块而不是 `index.html`,所以插件的 `transformIndexHtml` 在 Nuxt 里不会被调用,`window["__VERSION__"]` 不会自动注入。
90
+
91
+ 同样复用同一个插件实例,把它的 `headScript()` 推进 Nuxt `render:html` 钩子的 `html.head` 数组:
92
+
93
+ ```ts
94
+ import buildVersionPlugin from 'vite-plugin-build-version-file'
95
+
96
+ const buildVersion = buildVersionPlugin()
97
+
98
+ export default defineNuxtConfig({
99
+ vite: {
100
+ plugins: [buildVersion]
101
+ },
102
+ hooks: {
103
+ 'render:html'(html) {
104
+ html.head.push(buildVersion.headScript())
105
+ }
106
+ }
107
+ })
108
+ ```
109
+
110
+ `headScript()` 返回完整的 `<script>...</script>` 片段;`injectToHtml: false` 时、或 payload 还没生成时返回空字符串(不抛错),所以直接 `push` 是安全的。
111
+
112
+ 插件会自动识别 Nuxt 的构建资产目录,并把 `version.json` emit 到该目录,因此 Nuxt 打包后的静态产物会包含这个文件。默认文件路径为 `/_nuxt/version.json`;如果需要固定使用根路径 `/version.json`,请将文件复制到 Nuxt `public` 目录,或在构建后自行复制。
113
+
84
114
  ## 默认行为
85
115
 
86
116
  默认配置下,插件会做这些事:
87
117
 
88
118
  - 开发态访问 `/version.json` 时返回版本信息
89
119
  - 构建后输出 `version.json`
120
+ - 构建后输出 `loading.html`
90
121
  - 向 HTML 注入 `window["__VERSION__"]`
91
122
  - 自动读取业务项目最近的 `package.json`
92
123
  - 将 `package.json` 的 `name` 写入 `pkgName`
@@ -127,6 +158,7 @@ type BuildVersionContext = {
127
158
 
128
159
  type BuildVersionPluginOptions = {
129
160
  filename?: string
161
+ loadingPage?: string | false
130
162
  globalName?: string
131
163
  defineName?: string | false
132
164
  injectToHtml?: boolean
@@ -166,6 +198,30 @@ dist/meta/version.json
166
198
 
167
199
  如果项目配置了 Vite `base`,访问路径会自动跟随 `base`。
168
200
 
201
+ ### `loadingPage`
202
+
203
+ - 类型:`string | false`
204
+ - 默认值:`"loading"`
205
+ - 作用:控制构建产物中缓存清理页的文件名
206
+
207
+ 默认输出 `loading.html`,与 `index.html` 同级。值不带后缀时会自动补 `.html`,也支持子路径:
208
+
209
+ ```ts
210
+ buildVersionPlugin({
211
+ loadingPage: 'cache/loading'
212
+ })
213
+ ```
214
+
215
+ 构建后输出:
216
+
217
+ ```text
218
+ dist/cache/loading.html
219
+ ```
220
+
221
+ 传 `false` 时不生成该页面。
222
+
223
+ Nuxt 下与 `version.json` 使用同一规则,默认输出到 `_nuxt/loading.html`;如果需要固定使用根路径 `/loading.html`,请将文件复制到 Nuxt `public` 目录,或在构建后自行复制。
224
+
169
225
  ### `globalName`
170
226
 
171
227
  - 类型:`string`
@@ -342,91 +398,187 @@ console.log(payload.env)
342
398
 
343
399
  ## 做版本检查
344
400
 
345
- 插件不内置版本检查 UI。推荐业务项目自行处理:
401
+ 插件不内置版本检查 UI。推荐业务项目自行处理。下面示例可以直接放到应用入口、布局组件或独立的版本检查模块中:
346
402
 
347
403
  ```js
348
404
  const CHECK_INTERVAL = 60 * 1000
405
+ const CANCEL_RETRY_DELAY = 5 * 60 * 1000
349
406
  const VERSION_URL = '/version.json'
350
407
 
408
+ // 使用 window 共享状态,避免多个组件或入口各自创建轮询和更新弹窗。
409
+ const versionCheckState = (window.__VERSION_CHECK_STATE__ ??= {
410
+ timer: 0,
411
+ checking: false,
412
+ promptPromise: null
413
+ })
414
+
415
+ function scheduleVersionCheck(delay) {
416
+ window.clearTimeout(versionCheckState.timer)
417
+ versionCheckState.timer = window.setTimeout(() => {
418
+ void checkVersion()
419
+ }, delay)
420
+ }
421
+
422
+ function showUpdatePrompt() {
423
+ if (versionCheckState.promptPromise) {
424
+ return versionCheckState.promptPromise
425
+ }
426
+
427
+ // 使用 Element Plus 等异步弹窗时,替换为返回 Promise<boolean> 的业务弹窗函数。
428
+ versionCheckState.promptPromise = Promise.resolve(
429
+ window.confirm('检测到新版本,是否立即刷新页面?')
430
+ ).finally(() => {
431
+ versionCheckState.promptPromise = null
432
+ })
433
+
434
+ return versionCheckState.promptPromise
435
+ }
436
+
351
437
  async function checkVersion() {
438
+ // 防止多个入口同时发起版本请求。
439
+ if (versionCheckState.checking) {
440
+ return
441
+ }
442
+
443
+ versionCheckState.checking = true
444
+ let nextDelay = CHECK_INTERVAL
445
+
352
446
  try {
353
447
  const response = await fetch(VERSION_URL, {
354
448
  cache: 'no-store'
355
449
  })
450
+
451
+ if (!response.ok) {
452
+ throw new Error(`version request failed: ${response.status}`)
453
+ }
454
+
356
455
  const latestPayload = await response.json()
357
456
  const currentPayload = window.__VERSION__
358
457
 
359
458
  if (currentPayload && latestPayload.time !== currentPayload.time) {
360
- const shouldReload = window.confirm('检测到新版本,是否刷新页面?')
459
+ const shouldReload = await showUpdatePrompt()
361
460
 
362
461
  if (shouldReload) {
363
462
  window.location.reload()
364
463
  return
365
464
  }
465
+
466
+ // 用户取消后,5 分钟后再检查,避免短时间内重复打扰。
467
+ nextDelay = CANCEL_RETRY_DELAY
366
468
  }
367
469
  } catch (error) {
368
470
  console.warn('check version failed', error)
471
+ } finally {
472
+ versionCheckState.checking = false
473
+ scheduleVersionCheck(nextDelay)
369
474
  }
370
-
371
- window.setTimeout(checkVersion, CHECK_INTERVAL)
372
475
  }
373
476
 
374
- checkVersion()
477
+ void checkVersion()
375
478
  ```
376
479
 
377
- 建议:
480
+ 这个示例有几个关键点:
378
481
 
379
482
  - 通常比较 `payload.time` 即可
380
483
  - 请求 `version.json` 时使用 `cache: 'no-store'`
381
- - 轮询间隔不要过短,通常 1 分钟或更长
382
- - 弹窗、通知、静默刷新等策略放在业务项目里
383
- - 请求失败后继续下一轮检查,避免一次失败中断流程
484
+ - `window.__VERSION_CHECK_STATE__` 是当前页面内的共享状态;多个组件重复调用 `checkVersion()` 时,只会保留一个检查流程
485
+ - `promptPromise` 让更新弹窗成为单例;同一时间已有弹窗时,不会再次弹出
486
+ - 用户确认后立即刷新页面;用户取消后固定等待 5 分钟,再开始下一次检查
487
+ - 没有检测到更新或请求失败时,按 `CHECK_INTERVAL` 继续检查
488
+ - `CHECK_INTERVAL` 通常设置为 1 分钟或更长,避免产生过多请求
489
+ - 如果项目配置了自定义 `globalName`,将示例中的 `window.__VERSION__` 换成对应的全局变量
490
+
491
+ 示例使用原生 `window.confirm`,因此不依赖 UI 框架。接入 Element Plus、Ant Design 等异步弹窗时,只需让 `showUpdatePrompt` 内部调用业务弹窗,并在确认/取消时分别返回 `true`/`false`;不要在多个组件中各自维护一份弹窗状态。
384
492
 
385
493
  ## 刷新指定项目缓存
386
494
 
387
495
  ```ts
388
- import { clearUrlCache } from 'vite-plugin-build-version-file/client'
496
+ import { redirectToLoading } from 'vite-plugin-build-version-file/client'
389
497
 
390
- await clearUrlCache({
498
+ redirectToLoading({
391
499
  urls: ['/vue-app/', '/vue-app/index.html'],
392
500
  redirectUrl: '/vue-app/',
393
- time: 5
501
+ time: 5,
502
+ times: 2,
503
+ title: '正在清理缓存',
504
+ filename: 'loading.html'
394
505
  })
395
506
  ```
396
507
 
397
- 调用后会显示全屏 loading 和刷新倒计时。`time` 单位为秒,默认值为 `5`;缓存处理提前完成时会立即刷新,到达时限后则无论调用结果如何都会刷新。
508
+ `redirectToLoading` 只负责携带参数跳转到构建产物中的 `loading.html`,不向当前页面注入任何 DOM,也不在主页面执行清理逻辑。缓存清理与跳转全部发生在 `loading.html` 内:
509
+
510
+ - 打开后立即执行清理,并显示全屏 loading
511
+ - 缓存处理提前完成时会立即跳转;到达 `time` 时限(单位秒,默认 `5`)后无论调用结果如何都会跳转
512
+ - 跳转发生在 `loading.html` 内,主页面代码无需等待
398
513
 
399
- 参数对象可省略。无参时按顺序检测以下两组 URL:
514
+ 参数对象可省略:
515
+
516
+ ```ts
517
+ redirectToLoading()
518
+ ```
519
+
520
+ 无参时按顺序检测以下两组 URL:
400
521
 
401
522
  1. `location.origin + location.pathname`
402
523
  2. 第一组 URL 拼接 `index.html`
403
524
 
404
- 两组完成后,默认携带随机时间戳跳转到第一组 URL:
525
+ 完成后跳转回 `window.location.href`(query 与 hash 原样保留,不追加缓存戳)。
526
+
527
+ 参数说明:
528
+
529
+ - `urls`:需要清理缓存的 URL 列表,可传多个。默认如上两条
530
+ - `redirectUrl`:清理完成或超时后跳转的目标 URL。默认为调用时的 `window.location.href`
531
+ - `time`:最长等待秒数,默认 `5`
532
+ - `times`:整套清理操作重复执行的次数,默认 `2`
533
+ - `title`:传入后在 loading 页中心显示该文案;不传只显示 loading 图标
534
+ - `filename`:`loading.html` 的路径,默认 `loading.html`,相对当前页面解析
535
+
536
+ 除 `filename` 外的参数会合并成一个 JSON 对象,写在随机参数名的 query 参数下;loading 页按参数值解析,不依赖参数名。
537
+
538
+ loading 页默认透明背景,不遮挡页面底色;`title` 传了才显示文字。
539
+
540
+ 仍然可以用旧名调用,行为一致:
405
541
 
406
542
  ```ts
407
- await clearUrlCache()
543
+ import { clearUrlCache } from 'vite-plugin-build-version-file/client'
544
+
545
+ clearUrlCache({ urls: ['/vue-app/'] })
408
546
  ```
409
547
 
410
- Vue hash 路由会原样保留,缓存时间戳写在 `#` 前,确保服务器能够收到:
548
+ Vue hash 路由会原样保留。缓存戳只加在 loading 页内部的 fetch / iframe 请求上,用于绕过旧缓存;最终跳转地址原样使用,不附加任何参数:
411
549
 
412
550
  ```text
413
- https://xxxx/pc/?_=随机值#/login?redirect=/dashboard/analysis
551
+ 调用页:https://xxxx/pc/#/login?redirect=/dashboard/analysis
552
+ 清理请求:https://xxxx/pc/?随机参数名=随机值
553
+ 最终跳转:https://xxxx/pc/#/login?redirect=/dashboard/analysis
414
554
  ```
415
555
 
416
- 该方法仅支持同源 URL。它会绕过缓存请求 URL、强制刷新原 URL、通过隐藏 iframe 再次加载页面,最后携带随机时间戳跳转到该 URL。创建 iframe 前会销毁同一页面中已有的缓存刷新 iframe。单次请求或 iframe 加载失败不会阻断最终跳转。
556
+ `loading.html` 内仅支持同源 URL。它会绕过缓存请求 URL、强制刷新原 URL、通过隐藏 iframe 再次加载页面,最后跳转到目标 URL(不附加缓存戳)。创建 iframe 前会销毁同一页面中已有的缓存刷新 iframe。单次请求或 iframe 加载失败不会阻断最终跳转。
417
557
 
418
558
  受浏览器限制,前端代码无法直接删除指定 URL 下的全部 HTTP 缓存;该方法只能尽可能刷新对应 Vue 项目的缓存。
419
559
 
560
+ 直接打开 `loading.html` 且不带 query 时,默认以 `loading.html` 所在目录作为应用根路径执行同一套清理。
561
+
420
562
  ## 开发态行为
421
563
 
422
564
  开发态下,插件不会向业务项目目录写入文件。
423
565
 
424
- 它会通过 Vite dev server 直接返回 JSON:
566
+ 它会通过 Vite dev server 直接返回,不落文件:
425
567
 
568
+ - `version.json`:版本信息 JSON
569
+ - `loading.html`:与构建态同路径规则(可用 `loadingPage` 调整),可直接在浏览器打开调试清理与跳转流程
426
570
  - 不污染 `public/`
427
571
  - 不需要生成临时文件
428
572
  - 修改插件配置后重启 dev server 即可生效
429
573
 
574
+ 调试 loading 页时,query 参数与构建态一致:参数名随机,值是包含 `urls` 的 JSON,例如:
575
+
576
+ ```text
577
+ http://localhost:5173/loading.html?x7k2q9={"urls":["http://localhost:5173/","http://localhost:5173/index.html"],"redirectUrl":"http://localhost:5173/","time":5,"times":2,"title":"调试"}
578
+ ```
579
+
580
+ 也可以直接打开 `http://localhost:5173/loading.html`,按所在目录执行默认清理。
581
+
430
582
  ## 本地开发
431
583
 
432
584
  仓库提供了两个调试用示例项目:
@@ -468,6 +620,7 @@ npm pack --dry-run
468
620
  建议检查:
469
621
 
470
622
  - `debug/fixture-app/dist/version.json` 包含 `time`、`env`、`pkgName`、`pkgVersion`
623
+ - `debug/fixture-app/dist/loading.html` 与 `index.html` 同级生成
471
624
  - `debug/fixture-app/dist/index.html` 只注入一次 `window["__VERSION__"]`
472
625
  - `debug/vitepress-app/.vitepress/dist/meta/version.json` 包含 `fixture`、`time`、`env`
473
626
  - VitePress 每个 HTML 只注入一次 `window["__VERSION__"]`
@@ -516,7 +669,9 @@ npm pack
516
669
  It exposes one consistent version payload in development and production:
517
670
 
518
671
  - serves `version.json` through the Vite dev server
672
+ - serves `loading.html` through the Vite dev server for local debugging
519
673
  - emits `version.json` into the build output directory
674
+ - emits a `loading.html` cache-clearing page next to `index.html`
520
675
  - injects `window["__VERSION__"]` into normal Vite HTML
521
676
  - supports VitePress through its top-level `transformHtml` hook
522
677
  - supports custom versions, filenames, global variable names, time zones, and extra payload fields
@@ -537,6 +692,7 @@ Those behaviors should stay in your application code.
537
692
  - Vite projects
538
693
  - Vue 3 + Vite projects
539
694
  - VitePress projects
695
+ - Nuxt projects (wire up `render:html` yourself, see below)
540
696
  - `vite >= 5`
541
697
  - Node.js `>= 18`
542
698
 
@@ -591,12 +747,40 @@ Use the same `buildVersion` instance in both places. Calling `buildVersionPlugin
591
747
 
592
748
  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.
593
749
 
750
+ ### Nuxt
751
+
752
+ 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.
753
+
754
+ Reuse the same plugin instance and push its `headScript()` into the `html.head` array of the Nuxt `render:html` hook:
755
+
756
+ ```ts
757
+ import buildVersionPlugin from 'vite-plugin-build-version-file'
758
+
759
+ const buildVersion = buildVersionPlugin()
760
+
761
+ export default defineNuxtConfig({
762
+ vite: {
763
+ plugins: [buildVersion]
764
+ },
765
+ hooks: {
766
+ 'render:html'(html) {
767
+ html.head.push(buildVersion.headScript())
768
+ }
769
+ }
770
+ })
771
+ ```
772
+
773
+ `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.
774
+
775
+ 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.
776
+
594
777
  ## Default Behavior
595
778
 
596
779
  With default options, the plugin:
597
780
 
598
781
  - serves `/version.json` in development
599
782
  - emits `version.json` after build
783
+ - emits `loading.html` after build
600
784
  - injects `window["__VERSION__"]` into HTML
601
785
  - reads the nearest project `package.json`
602
786
  - writes `name` as `pkgName`
@@ -637,6 +821,7 @@ type BuildVersionContext = {
637
821
 
638
822
  type BuildVersionPluginOptions = {
639
823
  filename?: string
824
+ loadingPage?: string | false
640
825
  globalName?: string
641
826
  injectToHtml?: boolean
642
827
  timeZone?: string
@@ -673,6 +858,30 @@ Development URL:
673
858
 
674
859
  If your project configures Vite `base`, the URL follows that base.
675
860
 
861
+ ### `loadingPage`
862
+
863
+ - Type: `string | false`
864
+ - Default: `"loading"`
865
+ - Controls the filename of the cache-clearing page in the build output.
866
+
867
+ The default emits `loading.html` next to `index.html`. When the value has no suffix, `.html` is appended; sub-paths are supported:
868
+
869
+ ```ts
870
+ buildVersionPlugin({
871
+ loadingPage: 'cache/loading'
872
+ })
873
+ ```
874
+
875
+ Build output:
876
+
877
+ ```text
878
+ dist/cache/loading.html
879
+ ```
880
+
881
+ Pass `false` to skip emitting the page.
882
+
883
+ 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.
884
+
676
885
  ### `globalName`
677
886
 
678
887
  - Type: `string`
@@ -819,91 +1028,187 @@ Use this when you need to compare the current page against the latest deployed v
819
1028
 
820
1029
  ## Checking for Updates
821
1030
 
822
- The plugin does not include update-checking UI. Keep that logic in your app.
1031
+ 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:
823
1032
 
824
1033
  ```js
825
1034
  const CHECK_INTERVAL = 60 * 1000
1035
+ const CANCEL_RETRY_DELAY = 5 * 60 * 1000
826
1036
  const VERSION_URL = '/version.json'
827
1037
 
1038
+ // 使用 window 共享状态,让多个组件或入口复用同一套检查和弹窗。
1039
+ const versionCheckState = (window.__VERSION_CHECK_STATE__ ??= {
1040
+ timer: 0,
1041
+ checking: false,
1042
+ promptPromise: null
1043
+ })
1044
+
1045
+ function scheduleVersionCheck(delay) {
1046
+ window.clearTimeout(versionCheckState.timer)
1047
+ versionCheckState.timer = window.setTimeout(() => {
1048
+ void checkVersion()
1049
+ }, delay)
1050
+ }
1051
+
1052
+ function showUpdatePrompt() {
1053
+ if (versionCheckState.promptPromise) {
1054
+ return versionCheckState.promptPromise
1055
+ }
1056
+
1057
+ // 使用异步业务弹窗时,替换为返回 Promise<boolean> 的弹窗函数。
1058
+ versionCheckState.promptPromise = Promise.resolve(
1059
+ window.confirm('A new version is available. Reload now?')
1060
+ ).finally(() => {
1061
+ versionCheckState.promptPromise = null
1062
+ })
1063
+
1064
+ return versionCheckState.promptPromise
1065
+ }
1066
+
828
1067
  async function checkVersion() {
1068
+ // 防止多个入口同时发起版本请求。
1069
+ if (versionCheckState.checking) {
1070
+ return
1071
+ }
1072
+
1073
+ versionCheckState.checking = true
1074
+ let nextDelay = CHECK_INTERVAL
1075
+
829
1076
  try {
830
1077
  const response = await fetch(VERSION_URL, {
831
1078
  cache: 'no-store'
832
1079
  })
1080
+
1081
+ if (!response.ok) {
1082
+ throw new Error(`version request failed: ${response.status}`)
1083
+ }
1084
+
833
1085
  const latestPayload = await response.json()
834
1086
  const currentPayload = window.__VERSION__
835
1087
 
836
1088
  if (currentPayload && latestPayload.time !== currentPayload.time) {
837
- const shouldReload = window.confirm('A new version is available. Reload now?')
1089
+ const shouldReload = await showUpdatePrompt()
838
1090
 
839
1091
  if (shouldReload) {
840
1092
  window.location.reload()
841
1093
  return
842
1094
  }
1095
+
1096
+ // 用户取消后,等待 5 分钟再开始下一次检查。
1097
+ nextDelay = CANCEL_RETRY_DELAY
843
1098
  }
844
1099
  } catch (error) {
845
1100
  console.warn('check version failed', error)
1101
+ } finally {
1102
+ versionCheckState.checking = false
1103
+ scheduleVersionCheck(nextDelay)
846
1104
  }
847
-
848
- window.setTimeout(checkVersion, CHECK_INTERVAL)
849
1105
  }
850
1106
 
851
- checkVersion()
1107
+ void checkVersion()
852
1108
  ```
853
1109
 
854
- Recommendations:
1110
+ Key points:
855
1111
 
856
1112
  - Compare `payload.time` in most cases.
857
1113
  - Fetch `version.json` with `cache: 'no-store'`.
858
- - Avoid very short polling intervals. One minute or longer is usually enough.
859
- - Keep prompts, notifications, silent reloads, and retry rules in application code.
860
- - Continue polling after failed requests.
1114
+ - `window.__VERSION_CHECK_STATE__` is shared by the current page. Repeated `checkVersion()` calls reuse one check flow.
1115
+ - `promptPromise` makes the update prompt a singleton, so an already-open prompt is not shown again.
1116
+ - Confirming reloads immediately. Canceling waits five minutes before the next check.
1117
+ - No update or a failed request uses `CHECK_INTERVAL` for the next check.
1118
+ - Set `CHECK_INTERVAL` to one minute or longer to avoid excessive requests.
1119
+ - If you configure a custom `globalName`, replace `window.__VERSION__` with that global variable.
1120
+
1121
+ 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.
861
1122
 
862
1123
  ## Refreshing a Project Cache
863
1124
 
864
1125
  ```ts
865
- import { clearUrlCache } from 'vite-plugin-build-version-file/client'
1126
+ import { redirectToLoading } from 'vite-plugin-build-version-file/client'
866
1127
 
867
- await clearUrlCache({
1128
+ redirectToLoading({
868
1129
  urls: ['/vue-app/', '/vue-app/index.html'],
869
1130
  redirectUrl: '/vue-app/',
870
- time: 5
1131
+ time: 5,
1132
+ times: 2,
1133
+ title: 'Clearing cache',
1134
+ filename: 'loading.html'
871
1135
  })
872
1136
  ```
873
1137
 
874
- 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.
1138
+ `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`:
1139
+
1140
+ - cleanup starts as soon as the page opens, with a full-screen loading mask
1141
+ - navigation happens as soon as cleanup finishes; once `time` seconds (default `5`) elapse, it happens regardless of the result
1142
+ - navigation is triggered inside `loading.html`, so the host page does not wait
875
1143
 
876
- The options object is optional. Without arguments, the following URL groups are checked in order:
1144
+ The options object is optional:
1145
+
1146
+ ```ts
1147
+ redirectToLoading()
1148
+ ```
1149
+
1150
+ Without arguments, the following URL groups are checked in order:
877
1151
 
878
1152
  1. `location.origin + location.pathname`
879
1153
  2. The first URL with `index.html` appended
880
1154
 
881
- After both groups complete, the method navigates to the first URL with a random timestamp:
1155
+ 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.
1156
+
1157
+ Options:
1158
+
1159
+ - `urls`: cache-clearing targets; multiple URLs are supported
1160
+ - `redirectUrl`: target after cleanup or timeout; defaults to `window.location.href` at call time
1161
+ - `time`: maximum wait in seconds, default `5`
1162
+ - `times`: how many times the whole cleanup pass runs, default `2`
1163
+ - `title`: when provided, shows that text in the loading page center; when omitted, only the loading icon is shown
1164
+ - `filename`: path of the loading page, default `loading.html`, resolved against the current page
1165
+
1166
+ 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.
1167
+
1168
+ The loading page uses a transparent background by default, and only shows text when `title` is provided.
1169
+
1170
+ The old name still works and behaves the same:
882
1171
 
883
1172
  ```ts
884
- await clearUrlCache()
1173
+ import { clearUrlCache } from 'vite-plugin-build-version-file/client'
1174
+
1175
+ clearUrlCache({ urls: ['/vue-app/'] })
885
1176
  ```
886
1177
 
887
- Vue hash routes are preserved. The cache-busting timestamp is added before `#` so the server receives it:
1178
+ 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:
888
1179
 
889
1180
  ```text
890
- https://xxx.cn/pc/?_=random-value#/login?redirect=/dashboard/analysis
1181
+ caller: https://xxx.cn/pc/#/login?redirect=/dashboard/analysis
1182
+ cleanup request: https://xxx.cn/pc/?random-key=random-value
1183
+ final navigation: https://xxx.cn/pc/#/login?redirect=/dashboard/analysis
891
1184
  ```
892
1185
 
893
- 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.
1186
+ `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.
894
1187
 
895
1188
  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.
896
1189
 
1190
+ Opening `loading.html` directly without a query string treats the page's own directory as the application root and runs the same cleanup flow.
1191
+
897
1192
  ## Development Behavior
898
1193
 
899
1194
  In development, the plugin does not write files into your project directory.
900
1195
 
901
- It serves JSON directly through the Vite dev server:
1196
+ It serves content directly through the Vite dev server without writing files:
902
1197
 
1198
+ - `version.json`: the version payload as JSON
1199
+ - `loading.html`: the same cache-clearing page as the build output (path configured by `loadingPage`), so you can open it directly in the browser to debug the cleanup and navigation flow
903
1200
  - no `public/` pollution
904
1201
  - no temporary file generation
905
1202
  - restart the dev server after changing plugin options
906
1203
 
1204
+ The loading page accepts the same query parameters in development: the parameter name is random and the value is JSON containing `urls`, for example:
1205
+
1206
+ ```text
1207
+ 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"}
1208
+ ```
1209
+
1210
+ You can also open `http://localhost:5173/loading.html` directly to run the default cleanup for its own directory.
1211
+
907
1212
  ## Local Development
908
1213
 
909
1214
  The repository contains two fixture apps:
@@ -945,6 +1250,7 @@ npm pack --dry-run
945
1250
  Check:
946
1251
 
947
1252
  - `debug/fixture-app/dist/version.json` contains `time`, `env`, `pkgName`, and `pkgVersion`
1253
+ - `debug/fixture-app/dist/loading.html` is emitted next to `index.html`
948
1254
  - `debug/fixture-app/dist/index.html` injects `window["__VERSION__"]` once
949
1255
  - `debug/vitepress-app/.vitepress/dist/meta/version.json` contains `fixture`, `time`, and `env`
950
1256
  - each VitePress HTML file injects `window["__VERSION__"]` once