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 +346 -40
- package/dist/client.cjs +32 -109
- package/dist/client.d.cts +7 -3
- package/dist/client.d.ts +7 -3
- package/dist/client.mjs +30 -108
- package/dist/index.cjs +262 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.mjs +262 -1
- package/package.json +9 -10
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 =
|
|
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
|
-
-
|
|
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 {
|
|
496
|
+
import { redirectToLoading } from 'vite-plugin-build-version-file/client'
|
|
389
497
|
|
|
390
|
-
|
|
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
|
-
|
|
508
|
+
`redirectToLoading` 只负责携带参数跳转到构建产物中的 `loading.html`,不向当前页面注入任何 DOM,也不在主页面执行清理逻辑。缓存清理与跳转全部发生在 `loading.html` 内:
|
|
509
|
+
|
|
510
|
+
- 打开后立即执行清理,并显示全屏 loading
|
|
511
|
+
- 缓存处理提前完成时会立即跳转;到达 `time` 时限(单位秒,默认 `5`)后无论调用结果如何都会跳转
|
|
512
|
+
- 跳转发生在 `loading.html` 内,主页面代码无需等待
|
|
398
513
|
|
|
399
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
1110
|
+
Key points:
|
|
855
1111
|
|
|
856
1112
|
- Compare `payload.time` in most cases.
|
|
857
1113
|
- Fetch `version.json` with `cache: 'no-store'`.
|
|
858
|
-
-
|
|
859
|
-
-
|
|
860
|
-
-
|
|
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 {
|
|
1126
|
+
import { redirectToLoading } from 'vite-plugin-build-version-file/client'
|
|
866
1127
|
|
|
867
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|