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/dist/index.d.cts CHANGED
@@ -11,6 +11,7 @@ interface BuildVersionPayload {
11
11
  }
12
12
  interface BuildVersionPluginOptions {
13
13
  filename?: string;
14
+ loadingPage?: string | false;
14
15
  globalName?: string;
15
16
  defineName?: string | false;
16
17
  injectToHtml?: boolean;
@@ -22,6 +23,7 @@ interface BuildVersionPluginOptions {
22
23
  }
23
24
  type BuildVersionPlugin = Plugin & {
24
25
  transformHtml: (code: string, ...args: unknown[]) => string | undefined;
26
+ headScript: () => string;
25
27
  };
26
28
  declare function buildVersionPlugin(options?: BuildVersionPluginOptions): BuildVersionPlugin;
27
29
 
package/dist/index.d.ts CHANGED
@@ -11,6 +11,7 @@ interface BuildVersionPayload {
11
11
  }
12
12
  interface BuildVersionPluginOptions {
13
13
  filename?: string;
14
+ loadingPage?: string | false;
14
15
  globalName?: string;
15
16
  defineName?: string | false;
16
17
  injectToHtml?: boolean;
@@ -22,6 +23,7 @@ interface BuildVersionPluginOptions {
22
23
  }
23
24
  type BuildVersionPlugin = Plugin & {
24
25
  transformHtml: (code: string, ...args: unknown[]) => string | undefined;
26
+ headScript: () => string;
25
27
  };
26
28
  declare function buildVersionPlugin(options?: BuildVersionPluginOptions): BuildVersionPlugin;
27
29
 
package/dist/index.mjs CHANGED
@@ -1,8 +1,216 @@
1
1
  // src/index.ts
2
2
  import { existsSync, readFileSync } from "fs";
3
3
  import { dirname, join, resolve } from "path";
4
+
5
+ // src/loading.html
6
+ var loading_default = `<!doctype html>
7
+ <html lang="zh-CN">
8
+ <head>
9
+ <meta charset="UTF-8" />
10
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
11
+ <title>Loading</title>
12
+ <style>
13
+ html,
14
+ body {
15
+ margin: 0;
16
+ height: 100%;
17
+ }
18
+
19
+ #vite-build-version-loading {
20
+ position: fixed;
21
+ inset: 0;
22
+ z-index: 2147483647;
23
+ display: flex;
24
+ flex-direction: column;
25
+ align-items: center;
26
+ justify-content: center;
27
+ gap: 16px;
28
+ color: #333333;
29
+ font: 14px/1.5 sans-serif;
30
+ }
31
+
32
+ #vite-build-version-loading .spinner {
33
+ width: 36px;
34
+ height: 36px;
35
+ border: 3px solid rgba(0, 0, 0, 0.15);
36
+ border-top-color: rgba(0, 0, 0, 0.6);
37
+ border-radius: 50%;
38
+ animation: vite-build-version-loading 0.8s linear infinite;
39
+ }
40
+
41
+ @keyframes vite-build-version-loading {
42
+ to {
43
+ transform: rotate(360deg);
44
+ }
45
+ }
46
+ </style>
47
+ </head>
48
+ <body>
49
+ <div id="vite-build-version-loading">
50
+ <div class="spinner"></div>
51
+ </div>
52
+ <script>
53
+ ;(function () {
54
+ var IFRAME_ID = 'vite-plugin-build-version-file-cache-iframe'
55
+ var IFRAME_TIMEOUT = 5000
56
+ var DEFAULT_REFRESH_TIME = 5
57
+ var DEFAULT_CLEAR_TIMES = 2
58
+
59
+ var params = new URLSearchParams(window.location.search)
60
+ // \u53C2\u6570\u540D\u968F\u673A\u751F\u6210\uFF0Cloading \u9875\u6309\u503C\u89E3\u6790\uFF1A\u4EFB\u4F55\u503C\u662F JSON \u7684\u53C2\u6570\u90FD\u89C6\u4E3A\u6E05\u7406\u914D\u7F6E\u3002
61
+ var config = (function () {
62
+ var entries = Array.from(params.entries())
63
+
64
+ for (var i = 0; i < entries.length; i++) {
65
+ try {
66
+ var value = JSON.parse(entries[i][1])
67
+
68
+ if (value && typeof value === 'object' && Array.isArray(value.urls)) {
69
+ return value
70
+ }
71
+ } catch (error) {
72
+ // \u975E JSON \u53C2\u6570\uFF08\u4F8B\u5982\u81EA\u5B9A\u4E49 query\uFF09\u76F4\u63A5\u8DF3\u8FC7\u3002
73
+ }
74
+ }
75
+
76
+ return {}
77
+ })()
78
+ // \u9ED8\u8BA4\u628A\u672C\u9875\u9762\u6240\u5728\u76EE\u5F55\u5F53\u4F5C\u5E94\u7528\u6839\u8DEF\u5F84\u3002
79
+ var directoryUrl = new URL('.', window.location.href).href
80
+ var urls = Array.isArray(config.urls)
81
+ ? config.urls.filter(Boolean)
82
+ : []
83
+ var time = Number(config.time)
84
+ var times = Math.floor(Number(config.times))
85
+ var redirectUrl = config.redirectUrl || directoryUrl
86
+ var title = config.title
87
+
88
+ if (urls.length === 0) {
89
+ urls = [directoryUrl, new URL('index.html', directoryUrl).href]
90
+ }
91
+
92
+ if (!Number.isFinite(time) || time <= 0) {
93
+ time = DEFAULT_REFRESH_TIME
94
+ }
95
+
96
+ if (!Number.isFinite(times) || times < 1) {
97
+ times = DEFAULT_CLEAR_TIMES
98
+ }
99
+
100
+ if (title) {
101
+ var titleElement = document.createElement('div')
102
+ titleElement.textContent = title
103
+ document.getElementById('vite-build-version-loading').appendChild(titleElement)
104
+ }
105
+
106
+ function createCacheBustKey() {
107
+ // \u53C2\u6570\u540D\u968F\u673A\uFF0C\u907F\u514D\u670D\u52A1\u5668\u6216 CDN \u9488\u5BF9\u56FA\u5B9A\u53C2\u6570\u540D\uFF08\u5982 _\u3001t\uFF09\u505A\u7F13\u5B58\u5904\u7406\u3002
108
+ return Date.now().toString(36) + Math.random().toString(36).slice(2, 8)
109
+ }
110
+
111
+ function createCacheBustedUrl(url) {
112
+ var targetUrl = new URL(url, window.location.href)
113
+ var hash = targetUrl.hash
114
+
115
+ // hash \u4E0D\u53C2\u4E0E HTTP \u8BF7\u6C42\uFF0C\u5148\u4FDD\u7559\u4E1A\u52A1\u8DEF\u7531\uFF0C\u518D\u5411\u771F\u5B9E\u8BF7\u6C42 URL \u5199\u5165\u7F13\u5B58\u6233\u3002
116
+ targetUrl.hash = ''
117
+ targetUrl.searchParams.set(
118
+ createCacheBustKey(),
119
+ Date.now() + '-' + Math.random().toString(36).slice(2)
120
+ )
121
+ targetUrl.hash = hash
122
+
123
+ return targetUrl.href
124
+ }
125
+
126
+ function loadIframe(url) {
127
+ return new Promise(function (resolve) {
128
+ // \u540C\u4E00\u9875\u9762\u53EA\u4FDD\u7559\u4E00\u4E2A\u7F13\u5B58\u5237\u65B0 iframe\uFF0C\u521B\u5EFA\u524D\u5148\u9500\u6BC1\u65E7\u5B9E\u4F8B\u3002
129
+ var existing = document.getElementById(IFRAME_ID)
130
+
131
+ if (existing) {
132
+ existing.remove()
133
+ }
134
+
135
+ var iframe = document.createElement('iframe')
136
+ var frameTimeout = 0
137
+ var completed = false
138
+
139
+ function complete() {
140
+ if (completed) {
141
+ return
142
+ }
143
+
144
+ completed = true
145
+ window.clearTimeout(frameTimeout)
146
+ resolve()
147
+ }
148
+
149
+ frameTimeout = window.setTimeout(complete, IFRAME_TIMEOUT)
150
+
151
+ iframe.hidden = true
152
+ iframe.id = IFRAME_ID
153
+ iframe.addEventListener('load', complete, { once: true })
154
+ iframe.addEventListener('error', complete, { once: true })
155
+ iframe.src = url
156
+ document.documentElement.appendChild(iframe)
157
+ })
158
+ }
159
+
160
+ function clearTargetUrlCache(url) {
161
+ var targetUrl = new URL(url, window.location.href).href
162
+ console.info('[vite-plugin-build-version] Clear cache for ' + targetUrl)
163
+
164
+ // \u968F\u673A URL \u7ED5\u8FC7\u65E7\u7F13\u5B58\uFF0C\u539F URL \u4F7F\u7528 reload \u6A21\u5F0F\u5237\u65B0\u5BF9\u5E94\u7F13\u5B58\u6761\u76EE\u3002
165
+ // \u7B49\u5F85\u54CD\u5E94\u4F53\u5B8C\u6574\u8BFB\u53D6\uFF0C\u907F\u514D\u540E\u7EED\u8DF3\u8F6C\u63D0\u524D\u53D6\u6D88\u7F13\u5B58\u5237\u65B0\u8BF7\u6C42\u3002
166
+ return Promise.allSettled([
167
+ fetch(createCacheBustedUrl(targetUrl), { cache: 'no-store' }).then(
168
+ function (response) {
169
+ return response.arrayBuffer()
170
+ }
171
+ ),
172
+ fetch(targetUrl, { cache: 'reload' }).then(function (response) {
173
+ return response.arrayBuffer()
174
+ })
175
+ ]).then(function () {
176
+ // iframe \u518D\u89E6\u53D1\u4E00\u6B21\u9875\u9762\u8D44\u6E90\u52A0\u8F7D\uFF0C\u8D85\u65F6\u6216\u5931\u8D25\u7531\u5916\u5C42\u7EE7\u7EED\u5904\u7406\u540E\u7EED URL\u3002
177
+ return loadIframe(createCacheBustedUrl(targetUrl))
178
+ })
179
+ }
180
+
181
+ var refreshTimeoutId = 0
182
+ var clearAllDone = (async function () {
183
+ // \u6574\u5957\u6E05\u7406\u64CD\u4F5C\u6309 times \u91CD\u590D\u6267\u884C\uFF0C\u9ED8\u8BA4 2 \u6B21\u3002
184
+ for (var round = 0; round < times; round++) {
185
+ for (var i = 0; i < urls.length; i++) {
186
+ try {
187
+ await clearTargetUrlCache(urls[i])
188
+ } catch (error) {
189
+ // \u5355\u7EC4\u5931\u8D25\u4E0D\u963B\u65AD\u540E\u7EED URL\uFF0C\u4FDD\u8BC1\u914D\u7F6E\u7684\u68C0\u6D4B\u7EC4\u90FD\u80FD\u6267\u884C\u3002
190
+ }
191
+ }
192
+ }
193
+ })()
194
+ var limitReached = new Promise(function (resolve) {
195
+ // \u5230\u8FBE\u603B\u7B49\u5F85\u65F6\u9650\u540E\u76F4\u63A5\u8DF3\u8F6C\uFF0C\u4E0D\u53D7\u7F13\u5B58\u8BF7\u6C42\u72B6\u6001\u5F71\u54CD\u3002
196
+ refreshTimeoutId = window.setTimeout(resolve, time * 1000)
197
+ })
198
+
199
+ Promise.race([clearAllDone, limitReached]).then(function () {
200
+ window.clearTimeout(refreshTimeoutId)
201
+ // \u6700\u7EC8\u8DF3\u8F6C\u539F\u6837\u4F7F\u7528\u76EE\u6807\u5730\u5740\uFF0C\u4E0D\u9644\u52A0\u7F13\u5B58\u6233\u3002
202
+ window.location.replace(redirectUrl)
203
+ })
204
+ })()
205
+ </script>
206
+ </body>
207
+ </html>
208
+ `;
209
+
210
+ // src/index.ts
4
211
  var PLUGIN_NAME = "vite-plugin-build-version-file";
5
212
  var DEFAULT_FILENAME = "version.json";
213
+ var DEFAULT_LOADING_PAGE = "loading";
6
214
  var DEFAULT_GLOBAL_NAME = "__VERSION__";
7
215
  var DEFAULT_DEFINE_NAME = "__VERSION__";
8
216
  var DEFAULT_TIME_ZONE = "Asia/Shanghai";
@@ -40,6 +248,16 @@ function normalizeFileName(filename) {
40
248
  }
41
249
  return normalized;
42
250
  }
251
+ function normalizeLoadingPage(loadingPage) {
252
+ if (loadingPage === false) {
253
+ return false;
254
+ }
255
+ const normalized = (loadingPage ?? DEFAULT_LOADING_PAGE).trim().replace(/^\/+/, "");
256
+ if (!normalized) {
257
+ throw new Error(`[${PLUGIN_NAME}] "loadingPage" cannot be empty.`);
258
+ }
259
+ return normalized.endsWith(".html") ? normalized : `${normalized}.html`;
260
+ }
43
261
  function normalizeGlobalName(globalName) {
44
262
  if (!globalName.trim()) {
45
263
  throw new Error(`[${PLUGIN_NAME}] "globalName" cannot be empty.`);
@@ -117,6 +335,9 @@ function serializePayload(payload) {
117
335
  function createInjectScript(globalName, payload) {
118
336
  return `window[${JSON.stringify(globalName)}] = ${JSON.stringify(payload)};`;
119
337
  }
338
+ function createInjectScriptTag(globalName, payload) {
339
+ return `<script>${createInjectScript(globalName, payload)}</script>`;
340
+ }
120
341
  function injectScriptToHead(code, script) {
121
342
  const scriptTag = `<script>${script}</script>`;
122
343
  if (code.includes(script)) {
@@ -139,6 +360,13 @@ function toRequestPath(base, fileName) {
139
360
  const baseWithoutTrailingSlash = withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
140
361
  return `${baseWithoutTrailingSlash}/${fileName}`.replace(/\/{2,}/g, "/");
141
362
  }
363
+ function resolveNuxtFileName(config, fileName) {
364
+ if (config.appType !== "custom" || config.build.copyPublicDir !== false) {
365
+ return fileName;
366
+ }
367
+ const assetDir = normalizeFileName(config.build.assetsDir);
368
+ return assetDir === "." || fileName.startsWith(`${assetDir}/`) ? fileName : `${assetDir}/${fileName}`;
369
+ }
142
370
  function resolveProjectRoot(root) {
143
371
  return root ? resolve(process.cwd(), root) : process.cwd();
144
372
  }
@@ -165,6 +393,7 @@ function createPayload(options, context, data, timeZone, packageInfo = {}) {
165
393
  }
166
394
  function buildVersionPlugin(options = {}) {
167
395
  const fileName = normalizeFileName(options.filename ?? DEFAULT_FILENAME);
396
+ const loadingPage = normalizeLoadingPage(options.loadingPage);
168
397
  const globalName = normalizeGlobalName(
169
398
  options.globalName ?? DEFAULT_GLOBAL_NAME
170
399
  );
@@ -176,12 +405,20 @@ function buildVersionPlugin(options = {}) {
176
405
  const data = normalizeData(options.data);
177
406
  let payload;
178
407
  let skipBundleEmit = false;
408
+ let bundleFileName = fileName;
409
+ let bundleLoadingFileName = loadingPage;
179
410
  const transformHtml = (code, ..._args) => {
180
411
  if (!injectToHtml || !payload) {
181
412
  return void 0;
182
413
  }
183
414
  return injectScriptToHead(code, createInjectScript(globalName, payload));
184
415
  };
416
+ const headScript = () => {
417
+ if (!injectToHtml || !payload) {
418
+ return "";
419
+ }
420
+ return createInjectScriptTag(globalName, payload);
421
+ };
185
422
  const plugin = {
186
423
  name: PLUGIN_NAME,
187
424
  config(userConfig, env) {
@@ -204,6 +441,10 @@ function buildVersionPlugin(options = {}) {
204
441
  },
205
442
  configResolved(config) {
206
443
  skipBundleEmit = Boolean(config.build.ssr) && !config.build.ssrEmitAssets;
444
+ bundleFileName = resolveNuxtFileName(config, fileName);
445
+ if (loadingPage) {
446
+ bundleLoadingFileName = resolveNuxtFileName(config, loadingPage);
447
+ }
207
448
  if (payload) {
208
449
  return;
209
450
  }
@@ -216,11 +457,21 @@ function buildVersionPlugin(options = {}) {
216
457
  },
217
458
  configureServer(server) {
218
459
  const requestPath = toRequestPath(server.config.base, fileName);
460
+ const loadingRequestPath = loadingPage ? toRequestPath(server.config.base, loadingPage) : void 0;
219
461
  server.middlewares.use((request, response, next) => {
220
462
  if (request.method !== "GET" && request.method !== "HEAD") {
221
463
  next();
222
464
  return;
223
465
  }
466
+ if (loadingRequestPath && parsePathname(request.url) === loadingRequestPath) {
467
+ response.statusCode = 200;
468
+ response.setHeader("Content-Type", "text/html; charset=utf-8");
469
+ response.setHeader("Cache-Control", "no-cache");
470
+ response.end(
471
+ request.method === "HEAD" ? void 0 : loading_default
472
+ );
473
+ return;
474
+ }
224
475
  if (parsePathname(request.url) !== requestPath || !payload) {
225
476
  next();
226
477
  return;
@@ -251,9 +502,16 @@ function buildVersionPlugin(options = {}) {
251
502
  }
252
503
  this.emitFile({
253
504
  type: "asset",
254
- fileName,
505
+ fileName: bundleFileName,
255
506
  source: serializePayload(payload)
256
507
  });
508
+ if (bundleLoadingFileName) {
509
+ this.emitFile({
510
+ type: "asset",
511
+ fileName: bundleLoadingFileName,
512
+ source: loading_default
513
+ });
514
+ }
257
515
  if (options.log) {
258
516
  console.log(options.log(payload));
259
517
  } else {
@@ -265,6 +523,9 @@ function buildVersionPlugin(options = {}) {
265
523
  Object.defineProperty(plugin, "transformHtml", {
266
524
  value: transformHtml
267
525
  });
526
+ Object.defineProperty(plugin, "headScript", {
527
+ value: headScript
528
+ });
268
529
  return plugin;
269
530
  }
270
531
  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.17",
3
+ "version": "0.2.0",
4
4
  "description": "A Vite plugin that emits version.json and injects version metadata into HTML.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -25,14 +25,6 @@
25
25
  "README.md",
26
26
  "LICENSE"
27
27
  ],
28
- "scripts": {
29
- "build": "tsup --config tsup.config.ts",
30
- "build:fixture": "pnpm build && vite build --config debug/fixture-app/vite.config.ts",
31
- "build:vitepress": "pnpm build && vitepress build debug/vitepress-app",
32
- "dev:fixture": "pnpm build && vite --config debug/fixture-app/vite.config.ts",
33
- "dev:vitepress": "pnpm build && vitepress dev debug/vitepress-app",
34
- "prepublishOnly": "pnpm build:fixture"
35
- },
36
28
  "keywords": [
37
29
  "vite",
38
30
  "vite-plugin",
@@ -58,5 +50,12 @@
58
50
  "vite": "^5.0.0",
59
51
  "vitepress": "^1.6.4",
60
52
  "vue": "^3.4.0"
53
+ },
54
+ "scripts": {
55
+ "build": "tsup --config tsup.config.ts",
56
+ "build:fixture": "pnpm build && vite build --config debug/fixture-app/vite.config.ts",
57
+ "build:vitepress": "pnpm build && vitepress build debug/vitepress-app",
58
+ "dev:fixture": "pnpm build && vite --config debug/fixture-app/vite.config.ts",
59
+ "dev:vitepress": "pnpm build && vitepress dev debug/vitepress-app"
61
60
  }
62
- }
61
+ }