vite-plugin-build-version-file 0.1.17 → 0.1.18
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 +178 -18
- package/dist/index.cjs +12 -0
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +12 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
- Vite 项目
|
|
28
28
|
- Vue 3 + Vite 项目
|
|
29
29
|
- VitePress 项目
|
|
30
|
+
- Nuxt 项目(需自行接 `render:html`,见下)
|
|
30
31
|
- `peerDependencies` 要求 `vite >= 5`
|
|
31
32
|
- Node.js 要求 `>= 18`
|
|
32
33
|
|
|
@@ -81,6 +82,33 @@ export default defineConfig({
|
|
|
81
82
|
|
|
82
83
|
VitePress 构建会包含 client bundle 和 SSR bundle。插件只在最终静态产物 bundle 输出 `version.json`,避免同一次 VitePress build 出现重复日志或重复 emit。
|
|
83
84
|
|
|
85
|
+
### Nuxt 项目
|
|
86
|
+
|
|
87
|
+
Nuxt 的 HTML 由 Nitro 渲染,Vite 客户端构建的入口是 JS 模块而不是 `index.html`,所以插件的 `transformIndexHtml` 在 Nuxt 里不会被调用,`window["__VERSION__"]` 不会自动注入。
|
|
88
|
+
|
|
89
|
+
同样复用同一个插件实例,把它的 `headScript()` 推进 Nuxt `render:html` 钩子的 `html.head` 数组:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import buildVersionPlugin from 'vite-plugin-build-version-file'
|
|
93
|
+
|
|
94
|
+
const buildVersion = buildVersionPlugin()
|
|
95
|
+
|
|
96
|
+
export default defineNuxtConfig({
|
|
97
|
+
vite: {
|
|
98
|
+
plugins: [buildVersion]
|
|
99
|
+
},
|
|
100
|
+
hooks: {
|
|
101
|
+
'render:html'(html) {
|
|
102
|
+
html.head.push(buildVersion.headScript())
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`headScript()` 返回完整的 `<script>...</script>` 片段;`injectToHtml: false` 时、或 payload 还没生成时返回空字符串(不抛错),所以直接 `push` 是安全的。
|
|
109
|
+
|
|
110
|
+
注意 Nuxt 里 `version.json` 的落点和 Vite / VitePress 不同:插件通过 `generateBundle` emit 到 Vite 客户端构建目录的根,而 Nuxt 发布到静态目录的是 assetsDir 内的内容,根目录上的文件不会被带出去。需要线上能访问这个文件时,要么把 `filename` 指到 assetsDir 内(例如 `_nuxt/version.json`),要么在构建后自行复制到 public 目录。
|
|
111
|
+
|
|
84
112
|
## 默认行为
|
|
85
113
|
|
|
86
114
|
默认配置下,插件会做这些事:
|
|
@@ -342,45 +370,97 @@ console.log(payload.env)
|
|
|
342
370
|
|
|
343
371
|
## 做版本检查
|
|
344
372
|
|
|
345
|
-
插件不内置版本检查 UI
|
|
373
|
+
插件不内置版本检查 UI。推荐业务项目自行处理。下面示例可以直接放到应用入口、布局组件或独立的版本检查模块中:
|
|
346
374
|
|
|
347
375
|
```js
|
|
348
376
|
const CHECK_INTERVAL = 60 * 1000
|
|
377
|
+
const CANCEL_RETRY_DELAY = 5 * 60 * 1000
|
|
349
378
|
const VERSION_URL = '/version.json'
|
|
350
379
|
|
|
380
|
+
// 使用 window 共享状态,避免多个组件或入口各自创建轮询和更新弹窗。
|
|
381
|
+
const versionCheckState = (window.__VERSION_CHECK_STATE__ ??= {
|
|
382
|
+
timer: 0,
|
|
383
|
+
checking: false,
|
|
384
|
+
promptPromise: null
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
function scheduleVersionCheck(delay) {
|
|
388
|
+
window.clearTimeout(versionCheckState.timer)
|
|
389
|
+
versionCheckState.timer = window.setTimeout(() => {
|
|
390
|
+
void checkVersion()
|
|
391
|
+
}, delay)
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function showUpdatePrompt() {
|
|
395
|
+
if (versionCheckState.promptPromise) {
|
|
396
|
+
return versionCheckState.promptPromise
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// 使用 Element Plus 等异步弹窗时,替换为返回 Promise<boolean> 的业务弹窗函数。
|
|
400
|
+
versionCheckState.promptPromise = Promise.resolve(
|
|
401
|
+
window.confirm('检测到新版本,是否立即刷新页面?')
|
|
402
|
+
).finally(() => {
|
|
403
|
+
versionCheckState.promptPromise = null
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
return versionCheckState.promptPromise
|
|
407
|
+
}
|
|
408
|
+
|
|
351
409
|
async function checkVersion() {
|
|
410
|
+
// 防止多个入口同时发起版本请求。
|
|
411
|
+
if (versionCheckState.checking) {
|
|
412
|
+
return
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
versionCheckState.checking = true
|
|
416
|
+
let nextDelay = CHECK_INTERVAL
|
|
417
|
+
|
|
352
418
|
try {
|
|
353
419
|
const response = await fetch(VERSION_URL, {
|
|
354
420
|
cache: 'no-store'
|
|
355
421
|
})
|
|
422
|
+
|
|
423
|
+
if (!response.ok) {
|
|
424
|
+
throw new Error(`version request failed: ${response.status}`)
|
|
425
|
+
}
|
|
426
|
+
|
|
356
427
|
const latestPayload = await response.json()
|
|
357
428
|
const currentPayload = window.__VERSION__
|
|
358
429
|
|
|
359
430
|
if (currentPayload && latestPayload.time !== currentPayload.time) {
|
|
360
|
-
const shouldReload =
|
|
431
|
+
const shouldReload = await showUpdatePrompt()
|
|
361
432
|
|
|
362
433
|
if (shouldReload) {
|
|
363
434
|
window.location.reload()
|
|
364
435
|
return
|
|
365
436
|
}
|
|
437
|
+
|
|
438
|
+
// 用户取消后,5 分钟后再检查,避免短时间内重复打扰。
|
|
439
|
+
nextDelay = CANCEL_RETRY_DELAY
|
|
366
440
|
}
|
|
367
441
|
} catch (error) {
|
|
368
442
|
console.warn('check version failed', error)
|
|
443
|
+
} finally {
|
|
444
|
+
versionCheckState.checking = false
|
|
445
|
+
scheduleVersionCheck(nextDelay)
|
|
369
446
|
}
|
|
370
|
-
|
|
371
|
-
window.setTimeout(checkVersion, CHECK_INTERVAL)
|
|
372
447
|
}
|
|
373
448
|
|
|
374
|
-
checkVersion()
|
|
449
|
+
void checkVersion()
|
|
375
450
|
```
|
|
376
451
|
|
|
377
|
-
|
|
452
|
+
这个示例有几个关键点:
|
|
378
453
|
|
|
379
454
|
- 通常比较 `payload.time` 即可
|
|
380
455
|
- 请求 `version.json` 时使用 `cache: 'no-store'`
|
|
381
|
-
-
|
|
382
|
-
-
|
|
383
|
-
-
|
|
456
|
+
- `window.__VERSION_CHECK_STATE__` 是当前页面内的共享状态;多个组件重复调用 `checkVersion()` 时,只会保留一个检查流程
|
|
457
|
+
- `promptPromise` 让更新弹窗成为单例;同一时间已有弹窗时,不会再次弹出
|
|
458
|
+
- 用户确认后立即刷新页面;用户取消后固定等待 5 分钟,再开始下一次检查
|
|
459
|
+
- 没有检测到更新或请求失败时,按 `CHECK_INTERVAL` 继续检查
|
|
460
|
+
- `CHECK_INTERVAL` 通常设置为 1 分钟或更长,避免产生过多请求
|
|
461
|
+
- 如果项目配置了自定义 `globalName`,将示例中的 `window.__VERSION__` 换成对应的全局变量
|
|
462
|
+
|
|
463
|
+
示例使用原生 `window.confirm`,因此不依赖 UI 框架。接入 Element Plus、Ant Design 等异步弹窗时,只需让 `showUpdatePrompt` 内部调用业务弹窗,并在确认/取消时分别返回 `true`/`false`;不要在多个组件中各自维护一份弹窗状态。
|
|
384
464
|
|
|
385
465
|
## 刷新指定项目缓存
|
|
386
466
|
|
|
@@ -537,6 +617,7 @@ Those behaviors should stay in your application code.
|
|
|
537
617
|
- Vite projects
|
|
538
618
|
- Vue 3 + Vite projects
|
|
539
619
|
- VitePress projects
|
|
620
|
+
- Nuxt projects (wire up `render:html` yourself, see below)
|
|
540
621
|
- `vite >= 5`
|
|
541
622
|
- Node.js `>= 18`
|
|
542
623
|
|
|
@@ -591,6 +672,33 @@ Use the same `buildVersion` instance in both places. Calling `buildVersionPlugin
|
|
|
591
672
|
|
|
592
673
|
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
674
|
|
|
675
|
+
### Nuxt
|
|
676
|
+
|
|
677
|
+
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.
|
|
678
|
+
|
|
679
|
+
Reuse the same plugin instance and push its `headScript()` into the `html.head` array of the Nuxt `render:html` hook:
|
|
680
|
+
|
|
681
|
+
```ts
|
|
682
|
+
import buildVersionPlugin from 'vite-plugin-build-version-file'
|
|
683
|
+
|
|
684
|
+
const buildVersion = buildVersionPlugin()
|
|
685
|
+
|
|
686
|
+
export default defineNuxtConfig({
|
|
687
|
+
vite: {
|
|
688
|
+
plugins: [buildVersion]
|
|
689
|
+
},
|
|
690
|
+
hooks: {
|
|
691
|
+
'render:html'(html) {
|
|
692
|
+
html.head.push(buildVersion.headScript())
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
})
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
`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.
|
|
699
|
+
|
|
700
|
+
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.
|
|
701
|
+
|
|
594
702
|
## Default Behavior
|
|
595
703
|
|
|
596
704
|
With default options, the plugin:
|
|
@@ -819,45 +927,97 @@ Use this when you need to compare the current page against the latest deployed v
|
|
|
819
927
|
|
|
820
928
|
## Checking for Updates
|
|
821
929
|
|
|
822
|
-
The plugin does not include update-checking UI. Keep that logic in your app.
|
|
930
|
+
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
931
|
|
|
824
932
|
```js
|
|
825
933
|
const CHECK_INTERVAL = 60 * 1000
|
|
934
|
+
const CANCEL_RETRY_DELAY = 5 * 60 * 1000
|
|
826
935
|
const VERSION_URL = '/version.json'
|
|
827
936
|
|
|
937
|
+
// 使用 window 共享状态,让多个组件或入口复用同一套检查和弹窗。
|
|
938
|
+
const versionCheckState = (window.__VERSION_CHECK_STATE__ ??= {
|
|
939
|
+
timer: 0,
|
|
940
|
+
checking: false,
|
|
941
|
+
promptPromise: null
|
|
942
|
+
})
|
|
943
|
+
|
|
944
|
+
function scheduleVersionCheck(delay) {
|
|
945
|
+
window.clearTimeout(versionCheckState.timer)
|
|
946
|
+
versionCheckState.timer = window.setTimeout(() => {
|
|
947
|
+
void checkVersion()
|
|
948
|
+
}, delay)
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
function showUpdatePrompt() {
|
|
952
|
+
if (versionCheckState.promptPromise) {
|
|
953
|
+
return versionCheckState.promptPromise
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
// 使用异步业务弹窗时,替换为返回 Promise<boolean> 的弹窗函数。
|
|
957
|
+
versionCheckState.promptPromise = Promise.resolve(
|
|
958
|
+
window.confirm('A new version is available. Reload now?')
|
|
959
|
+
).finally(() => {
|
|
960
|
+
versionCheckState.promptPromise = null
|
|
961
|
+
})
|
|
962
|
+
|
|
963
|
+
return versionCheckState.promptPromise
|
|
964
|
+
}
|
|
965
|
+
|
|
828
966
|
async function checkVersion() {
|
|
967
|
+
// 防止多个入口同时发起版本请求。
|
|
968
|
+
if (versionCheckState.checking) {
|
|
969
|
+
return
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
versionCheckState.checking = true
|
|
973
|
+
let nextDelay = CHECK_INTERVAL
|
|
974
|
+
|
|
829
975
|
try {
|
|
830
976
|
const response = await fetch(VERSION_URL, {
|
|
831
977
|
cache: 'no-store'
|
|
832
978
|
})
|
|
979
|
+
|
|
980
|
+
if (!response.ok) {
|
|
981
|
+
throw new Error(`version request failed: ${response.status}`)
|
|
982
|
+
}
|
|
983
|
+
|
|
833
984
|
const latestPayload = await response.json()
|
|
834
985
|
const currentPayload = window.__VERSION__
|
|
835
986
|
|
|
836
987
|
if (currentPayload && latestPayload.time !== currentPayload.time) {
|
|
837
|
-
const shouldReload =
|
|
988
|
+
const shouldReload = await showUpdatePrompt()
|
|
838
989
|
|
|
839
990
|
if (shouldReload) {
|
|
840
991
|
window.location.reload()
|
|
841
992
|
return
|
|
842
993
|
}
|
|
994
|
+
|
|
995
|
+
// 用户取消后,等待 5 分钟再开始下一次检查。
|
|
996
|
+
nextDelay = CANCEL_RETRY_DELAY
|
|
843
997
|
}
|
|
844
998
|
} catch (error) {
|
|
845
999
|
console.warn('check version failed', error)
|
|
1000
|
+
} finally {
|
|
1001
|
+
versionCheckState.checking = false
|
|
1002
|
+
scheduleVersionCheck(nextDelay)
|
|
846
1003
|
}
|
|
847
|
-
|
|
848
|
-
window.setTimeout(checkVersion, CHECK_INTERVAL)
|
|
849
1004
|
}
|
|
850
1005
|
|
|
851
|
-
checkVersion()
|
|
1006
|
+
void checkVersion()
|
|
852
1007
|
```
|
|
853
1008
|
|
|
854
|
-
|
|
1009
|
+
Key points:
|
|
855
1010
|
|
|
856
1011
|
- Compare `payload.time` in most cases.
|
|
857
1012
|
- Fetch `version.json` with `cache: 'no-store'`.
|
|
858
|
-
-
|
|
859
|
-
-
|
|
860
|
-
-
|
|
1013
|
+
- `window.__VERSION_CHECK_STATE__` is shared by the current page. Repeated `checkVersion()` calls reuse one check flow.
|
|
1014
|
+
- `promptPromise` makes the update prompt a singleton, so an already-open prompt is not shown again.
|
|
1015
|
+
- Confirming reloads immediately. Canceling waits five minutes before the next check.
|
|
1016
|
+
- No update or a failed request uses `CHECK_INTERVAL` for the next check.
|
|
1017
|
+
- Set `CHECK_INTERVAL` to one minute or longer to avoid excessive requests.
|
|
1018
|
+
- If you configure a custom `globalName`, replace `window.__VERSION__` with that global variable.
|
|
1019
|
+
|
|
1020
|
+
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
1021
|
|
|
862
1022
|
## Refreshing a Project Cache
|
|
863
1023
|
|
package/dist/index.cjs
CHANGED
|
@@ -142,6 +142,9 @@ function serializePayload(payload) {
|
|
|
142
142
|
function createInjectScript(globalName, payload) {
|
|
143
143
|
return `window[${JSON.stringify(globalName)}] = ${JSON.stringify(payload)};`;
|
|
144
144
|
}
|
|
145
|
+
function createInjectScriptTag(globalName, payload) {
|
|
146
|
+
return `<script>${createInjectScript(globalName, payload)}</script>`;
|
|
147
|
+
}
|
|
145
148
|
function injectScriptToHead(code, script) {
|
|
146
149
|
const scriptTag = `<script>${script}</script>`;
|
|
147
150
|
if (code.includes(script)) {
|
|
@@ -207,6 +210,12 @@ function buildVersionPlugin(options = {}) {
|
|
|
207
210
|
}
|
|
208
211
|
return injectScriptToHead(code, createInjectScript(globalName, payload));
|
|
209
212
|
};
|
|
213
|
+
const headScript = () => {
|
|
214
|
+
if (!injectToHtml || !payload) {
|
|
215
|
+
return "";
|
|
216
|
+
}
|
|
217
|
+
return createInjectScriptTag(globalName, payload);
|
|
218
|
+
};
|
|
210
219
|
const plugin = {
|
|
211
220
|
name: PLUGIN_NAME,
|
|
212
221
|
config(userConfig, env) {
|
|
@@ -290,6 +299,9 @@ function buildVersionPlugin(options = {}) {
|
|
|
290
299
|
Object.defineProperty(plugin, "transformHtml", {
|
|
291
300
|
value: transformHtml
|
|
292
301
|
});
|
|
302
|
+
Object.defineProperty(plugin, "headScript", {
|
|
303
|
+
value: headScript
|
|
304
|
+
});
|
|
293
305
|
return plugin;
|
|
294
306
|
}
|
|
295
307
|
var index_default = buildVersionPlugin;
|
package/dist/index.d.cts
CHANGED
|
@@ -22,6 +22,7 @@ interface BuildVersionPluginOptions {
|
|
|
22
22
|
}
|
|
23
23
|
type BuildVersionPlugin = Plugin & {
|
|
24
24
|
transformHtml: (code: string, ...args: unknown[]) => string | undefined;
|
|
25
|
+
headScript: () => string;
|
|
25
26
|
};
|
|
26
27
|
declare function buildVersionPlugin(options?: BuildVersionPluginOptions): BuildVersionPlugin;
|
|
27
28
|
|
package/dist/index.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ interface BuildVersionPluginOptions {
|
|
|
22
22
|
}
|
|
23
23
|
type BuildVersionPlugin = Plugin & {
|
|
24
24
|
transformHtml: (code: string, ...args: unknown[]) => string | undefined;
|
|
25
|
+
headScript: () => string;
|
|
25
26
|
};
|
|
26
27
|
declare function buildVersionPlugin(options?: BuildVersionPluginOptions): BuildVersionPlugin;
|
|
27
28
|
|
package/dist/index.mjs
CHANGED
|
@@ -117,6 +117,9 @@ function serializePayload(payload) {
|
|
|
117
117
|
function createInjectScript(globalName, payload) {
|
|
118
118
|
return `window[${JSON.stringify(globalName)}] = ${JSON.stringify(payload)};`;
|
|
119
119
|
}
|
|
120
|
+
function createInjectScriptTag(globalName, payload) {
|
|
121
|
+
return `<script>${createInjectScript(globalName, payload)}</script>`;
|
|
122
|
+
}
|
|
120
123
|
function injectScriptToHead(code, script) {
|
|
121
124
|
const scriptTag = `<script>${script}</script>`;
|
|
122
125
|
if (code.includes(script)) {
|
|
@@ -182,6 +185,12 @@ function buildVersionPlugin(options = {}) {
|
|
|
182
185
|
}
|
|
183
186
|
return injectScriptToHead(code, createInjectScript(globalName, payload));
|
|
184
187
|
};
|
|
188
|
+
const headScript = () => {
|
|
189
|
+
if (!injectToHtml || !payload) {
|
|
190
|
+
return "";
|
|
191
|
+
}
|
|
192
|
+
return createInjectScriptTag(globalName, payload);
|
|
193
|
+
};
|
|
185
194
|
const plugin = {
|
|
186
195
|
name: PLUGIN_NAME,
|
|
187
196
|
config(userConfig, env) {
|
|
@@ -265,6 +274,9 @@ function buildVersionPlugin(options = {}) {
|
|
|
265
274
|
Object.defineProperty(plugin, "transformHtml", {
|
|
266
275
|
value: transformHtml
|
|
267
276
|
});
|
|
277
|
+
Object.defineProperty(plugin, "headScript", {
|
|
278
|
+
value: headScript
|
|
279
|
+
});
|
|
268
280
|
return plugin;
|
|
269
281
|
}
|
|
270
282
|
var index_default = buildVersionPlugin;
|