vite-plugin-build-version-file 0.1.16 → 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 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 = window.confirm('检测到新版本,是否刷新页面?')
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
- - 轮询间隔不要过短,通常 1 分钟或更长
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
 
@@ -389,10 +469,13 @@ import { clearUrlCache } from 'vite-plugin-build-version-file/client'
389
469
 
390
470
  await clearUrlCache({
391
471
  urls: ['/vue-app/', '/vue-app/index.html'],
392
- redirectUrl: '/vue-app/'
472
+ redirectUrl: '/vue-app/',
473
+ time: 5
393
474
  })
394
475
  ```
395
476
 
477
+ 调用后会显示全屏 loading 和刷新倒计时。`time` 单位为秒,默认值为 `5`;缓存处理提前完成时会立即刷新,到达时限后则无论调用结果如何都会刷新。
478
+
396
479
  参数对象可省略。无参时按顺序检测以下两组 URL:
397
480
 
398
481
  1. `location.origin + location.pathname`
@@ -534,6 +617,7 @@ Those behaviors should stay in your application code.
534
617
  - Vite projects
535
618
  - Vue 3 + Vite projects
536
619
  - VitePress projects
620
+ - Nuxt projects (wire up `render:html` yourself, see below)
537
621
  - `vite >= 5`
538
622
  - Node.js `>= 18`
539
623
 
@@ -588,6 +672,33 @@ Use the same `buildVersion` instance in both places. Calling `buildVersionPlugin
588
672
 
589
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.
590
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
+
591
702
  ## Default Behavior
592
703
 
593
704
  With default options, the plugin:
@@ -816,45 +927,97 @@ Use this when you need to compare the current page against the latest deployed v
816
927
 
817
928
  ## Checking for Updates
818
929
 
819
- 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:
820
931
 
821
932
  ```js
822
933
  const CHECK_INTERVAL = 60 * 1000
934
+ const CANCEL_RETRY_DELAY = 5 * 60 * 1000
823
935
  const VERSION_URL = '/version.json'
824
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
+
825
966
  async function checkVersion() {
967
+ // 防止多个入口同时发起版本请求。
968
+ if (versionCheckState.checking) {
969
+ return
970
+ }
971
+
972
+ versionCheckState.checking = true
973
+ let nextDelay = CHECK_INTERVAL
974
+
826
975
  try {
827
976
  const response = await fetch(VERSION_URL, {
828
977
  cache: 'no-store'
829
978
  })
979
+
980
+ if (!response.ok) {
981
+ throw new Error(`version request failed: ${response.status}`)
982
+ }
983
+
830
984
  const latestPayload = await response.json()
831
985
  const currentPayload = window.__VERSION__
832
986
 
833
987
  if (currentPayload && latestPayload.time !== currentPayload.time) {
834
- const shouldReload = window.confirm('A new version is available. Reload now?')
988
+ const shouldReload = await showUpdatePrompt()
835
989
 
836
990
  if (shouldReload) {
837
991
  window.location.reload()
838
992
  return
839
993
  }
994
+
995
+ // 用户取消后,等待 5 分钟再开始下一次检查。
996
+ nextDelay = CANCEL_RETRY_DELAY
840
997
  }
841
998
  } catch (error) {
842
999
  console.warn('check version failed', error)
1000
+ } finally {
1001
+ versionCheckState.checking = false
1002
+ scheduleVersionCheck(nextDelay)
843
1003
  }
844
-
845
- window.setTimeout(checkVersion, CHECK_INTERVAL)
846
1004
  }
847
1005
 
848
- checkVersion()
1006
+ void checkVersion()
849
1007
  ```
850
1008
 
851
- Recommendations:
1009
+ Key points:
852
1010
 
853
1011
  - Compare `payload.time` in most cases.
854
1012
  - Fetch `version.json` with `cache: 'no-store'`.
855
- - Avoid very short polling intervals. One minute or longer is usually enough.
856
- - Keep prompts, notifications, silent reloads, and retry rules in application code.
857
- - Continue polling after failed requests.
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.
858
1021
 
859
1022
  ## Refreshing a Project Cache
860
1023
 
@@ -863,10 +1026,13 @@ import { clearUrlCache } from 'vite-plugin-build-version-file/client'
863
1026
 
864
1027
  await clearUrlCache({
865
1028
  urls: ['/vue-app/', '/vue-app/index.html'],
866
- redirectUrl: '/vue-app/'
1029
+ redirectUrl: '/vue-app/',
1030
+ time: 5
867
1031
  })
868
1032
  ```
869
1033
 
1034
+ 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.
1035
+
870
1036
  The options object is optional. Without arguments, the following URL groups are checked in order:
871
1037
 
872
1038
  1. `location.origin + location.pathname`
package/dist/client.cjs CHANGED
@@ -25,7 +25,32 @@ __export(client_exports, {
25
25
  module.exports = __toCommonJS(client_exports);
26
26
  var CACHE_BUST_PARAM = "_";
27
27
  var IFRAME_ID = "vite-plugin-build-version-file-cache-iframe";
28
+ var LOADING_ID = "vite-plugin-build-version-file-loading";
28
29
  var IFRAME_TIMEOUT = 5e3;
30
+ var DEFAULT_REFRESH_TIME = 5;
31
+ function showLoading(time) {
32
+ document.getElementById(LOADING_ID)?.remove();
33
+ const loading = document.createElement("div");
34
+ const spinner = document.createElement("div");
35
+ const text = document.createElement("div");
36
+ const deadline = Date.now() + time * 1e3;
37
+ loading.id = LOADING_ID;
38
+ loading.style.cssText = "position:fixed;inset:0;z-index:2147483647;display:flex;flex-direction:column;align-items:center;justify-content:center;background:rgba(0,0,0,.65);color:#fff;font:14px/1.5 sans-serif;";
39
+ spinner.style.cssText = "width:36px;height:36px;margin-bottom:16px;border:3px solid rgba(255,255,255,.35);border-top-color:#fff;border-radius:50%;animation:vite-build-version-loading .8s linear infinite;";
40
+ text.textContent = `\u8BF7\u7A0D\u7B49\uFF0C\u6B63\u5728\u68C0\u6D4B\u66F4\u65B0...\uFF08${Math.ceil(time)}\u79D2\uFF09`;
41
+ const style = document.createElement("style");
42
+ style.textContent = "@keyframes vite-build-version-loading{to{transform:rotate(360deg)}}";
43
+ loading.append(style, spinner, text);
44
+ document.documentElement.appendChild(loading);
45
+ const countdown = window.setInterval(() => {
46
+ const seconds = Math.max(0, Math.ceil((deadline - Date.now()) / 1e3));
47
+ text.textContent = `\u8BF7\u7A0D\u7B49\uFF0C\u6B63\u5728\u68C0\u6D4B\u66F4\u65B0...\uFF08${seconds}\u79D2\uFF09`;
48
+ }, 1e3);
49
+ return () => {
50
+ window.clearInterval(countdown);
51
+ loading.remove();
52
+ };
53
+ }
29
54
  function createCacheBustedUrl(url) {
30
55
  const targetUrl = new URL(url, window.location.href);
31
56
  const hash = targetUrl.hash;
@@ -83,18 +108,31 @@ async function clearTargetUrlCache(url) {
83
108
  async function clearUrlCache(options = {}) {
84
109
  const defaultUrl = window.location.origin + window.location.pathname;
85
110
  const urls = options.urls ?? [defaultUrl, createIndexUrl(defaultUrl)];
111
+ const configuredTime = options.time;
112
+ const time = typeof configuredTime === "number" && Number.isFinite(configuredTime) && configuredTime > 0 ? configuredTime : DEFAULT_REFRESH_TIME;
86
113
  const redirectUrl = new URL(
87
114
  options.redirectUrl ?? urls[0] ?? defaultUrl,
88
115
  window.location.href
89
116
  ).href;
117
+ const hideLoading = showLoading(time);
118
+ let refreshTimeout = 0;
90
119
  try {
91
- for (const url of urls) {
92
- try {
93
- await clearTargetUrlCache(url);
94
- } catch {
95
- }
96
- }
120
+ await Promise.race([
121
+ (async () => {
122
+ for (const url of urls) {
123
+ try {
124
+ await clearTargetUrlCache(url);
125
+ } catch {
126
+ }
127
+ }
128
+ })(),
129
+ new Promise((resolve) => {
130
+ refreshTimeout = window.setTimeout(resolve, time * 1e3);
131
+ })
132
+ ]);
97
133
  } finally {
134
+ window.clearTimeout(refreshTimeout);
135
+ hideLoading();
98
136
  window.location.replace(createCacheBustedUrl(redirectUrl));
99
137
  }
100
138
  }
package/dist/client.d.cts CHANGED
@@ -1,6 +1,7 @@
1
1
  interface ClearUrlCacheOptions {
2
2
  urls?: string[];
3
3
  redirectUrl?: string;
4
+ time?: number;
4
5
  }
5
6
  declare function clearUrlCache(options?: ClearUrlCacheOptions): Promise<void>;
6
7
 
package/dist/client.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  interface ClearUrlCacheOptions {
2
2
  urls?: string[];
3
3
  redirectUrl?: string;
4
+ time?: number;
4
5
  }
5
6
  declare function clearUrlCache(options?: ClearUrlCacheOptions): Promise<void>;
6
7
 
package/dist/client.mjs CHANGED
@@ -1,7 +1,32 @@
1
1
  // src/client.ts
2
2
  var CACHE_BUST_PARAM = "_";
3
3
  var IFRAME_ID = "vite-plugin-build-version-file-cache-iframe";
4
+ var LOADING_ID = "vite-plugin-build-version-file-loading";
4
5
  var IFRAME_TIMEOUT = 5e3;
6
+ var DEFAULT_REFRESH_TIME = 5;
7
+ function showLoading(time) {
8
+ document.getElementById(LOADING_ID)?.remove();
9
+ const loading = document.createElement("div");
10
+ const spinner = document.createElement("div");
11
+ const text = document.createElement("div");
12
+ const deadline = Date.now() + time * 1e3;
13
+ loading.id = LOADING_ID;
14
+ loading.style.cssText = "position:fixed;inset:0;z-index:2147483647;display:flex;flex-direction:column;align-items:center;justify-content:center;background:rgba(0,0,0,.65);color:#fff;font:14px/1.5 sans-serif;";
15
+ spinner.style.cssText = "width:36px;height:36px;margin-bottom:16px;border:3px solid rgba(255,255,255,.35);border-top-color:#fff;border-radius:50%;animation:vite-build-version-loading .8s linear infinite;";
16
+ text.textContent = `\u8BF7\u7A0D\u7B49\uFF0C\u6B63\u5728\u68C0\u6D4B\u66F4\u65B0...\uFF08${Math.ceil(time)}\u79D2\uFF09`;
17
+ const style = document.createElement("style");
18
+ style.textContent = "@keyframes vite-build-version-loading{to{transform:rotate(360deg)}}";
19
+ loading.append(style, spinner, text);
20
+ document.documentElement.appendChild(loading);
21
+ const countdown = window.setInterval(() => {
22
+ const seconds = Math.max(0, Math.ceil((deadline - Date.now()) / 1e3));
23
+ text.textContent = `\u8BF7\u7A0D\u7B49\uFF0C\u6B63\u5728\u68C0\u6D4B\u66F4\u65B0...\uFF08${seconds}\u79D2\uFF09`;
24
+ }, 1e3);
25
+ return () => {
26
+ window.clearInterval(countdown);
27
+ loading.remove();
28
+ };
29
+ }
5
30
  function createCacheBustedUrl(url) {
6
31
  const targetUrl = new URL(url, window.location.href);
7
32
  const hash = targetUrl.hash;
@@ -59,18 +84,31 @@ async function clearTargetUrlCache(url) {
59
84
  async function clearUrlCache(options = {}) {
60
85
  const defaultUrl = window.location.origin + window.location.pathname;
61
86
  const urls = options.urls ?? [defaultUrl, createIndexUrl(defaultUrl)];
87
+ const configuredTime = options.time;
88
+ const time = typeof configuredTime === "number" && Number.isFinite(configuredTime) && configuredTime > 0 ? configuredTime : DEFAULT_REFRESH_TIME;
62
89
  const redirectUrl = new URL(
63
90
  options.redirectUrl ?? urls[0] ?? defaultUrl,
64
91
  window.location.href
65
92
  ).href;
93
+ const hideLoading = showLoading(time);
94
+ let refreshTimeout = 0;
66
95
  try {
67
- for (const url of urls) {
68
- try {
69
- await clearTargetUrlCache(url);
70
- } catch {
71
- }
72
- }
96
+ await Promise.race([
97
+ (async () => {
98
+ for (const url of urls) {
99
+ try {
100
+ await clearTargetUrlCache(url);
101
+ } catch {
102
+ }
103
+ }
104
+ })(),
105
+ new Promise((resolve) => {
106
+ refreshTimeout = window.setTimeout(resolve, time * 1e3);
107
+ })
108
+ ]);
73
109
  } finally {
110
+ window.clearTimeout(refreshTimeout);
111
+ hideLoading();
74
112
  window.location.replace(createCacheBustedUrl(redirectUrl));
75
113
  }
76
114
  }
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-build-version-file",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "description": "A Vite plugin that emits version.json and injects version metadata into HTML.",
5
5
  "license": "MIT",
6
6
  "type": "module",