vite-plugin-build-version-file 0.1.18 → 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/client.mjs CHANGED
@@ -1,117 +1,39 @@
1
1
  // src/client.ts
2
- var CACHE_BUST_PARAM = "_";
3
- var IFRAME_ID = "vite-plugin-build-version-file-cache-iframe";
4
- var LOADING_ID = "vite-plugin-build-version-file-loading";
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
- };
2
+ var DEFAULT_LOADING_FILENAME = "loading.html";
3
+ function createRandomParamKey() {
4
+ return Math.random().toString(36).slice(2);
29
5
  }
30
- function createCacheBustedUrl(url) {
31
- const targetUrl = new URL(url, window.location.href);
32
- const hash = targetUrl.hash;
33
- targetUrl.hash = "";
34
- targetUrl.searchParams.set(
35
- CACHE_BUST_PARAM,
36
- `${Date.now()}-${Math.random().toString(36).slice(2)}`
6
+ function createLoadingUrl(options) {
7
+ const loadingUrl = new URL(
8
+ options.filename ?? DEFAULT_LOADING_FILENAME,
9
+ window.location.href
37
10
  );
38
- targetUrl.hash = hash;
39
- return targetUrl.href;
40
- }
41
- function createIndexUrl(url) {
42
- const targetUrl = new URL(url);
43
- if (!targetUrl.pathname.endsWith("/")) {
44
- targetUrl.pathname += "/";
11
+ const defaultUrl = window.location.origin + window.location.pathname;
12
+ const indexUrl = defaultUrl.endsWith("/") ? `${defaultUrl}index.html` : `${defaultUrl}/index.html`;
13
+ const urls = options.urls && options.urls.length > 0 ? options.urls : [defaultUrl, indexUrl];
14
+ const payload = {
15
+ urls,
16
+ redirectUrl: options.redirectUrl ?? window.location.href
17
+ };
18
+ if (options.time !== void 0) {
19
+ payload.time = options.time;
45
20
  }
46
- targetUrl.pathname += "index.html";
47
- return targetUrl.href;
48
- }
49
- function loadIframe(url) {
50
- return new Promise((resolve) => {
51
- document.getElementById(IFRAME_ID)?.remove();
52
- const iframe = document.createElement("iframe");
53
- let completed = false;
54
- const complete = () => {
55
- if (completed) {
56
- return;
57
- }
58
- completed = true;
59
- window.clearTimeout(timeout);
60
- resolve();
61
- };
62
- const timeout = window.setTimeout(complete, IFRAME_TIMEOUT);
63
- iframe.hidden = true;
64
- iframe.id = IFRAME_ID;
65
- iframe.addEventListener("load", complete, { once: true });
66
- iframe.addEventListener("error", complete, { once: true });
67
- iframe.src = url;
68
- document.documentElement.appendChild(iframe);
69
- });
21
+ if (options.times !== void 0) {
22
+ payload.times = options.times;
23
+ }
24
+ if (options.title) {
25
+ payload.title = options.title;
26
+ }
27
+ loadingUrl.searchParams.set(createRandomParamKey(), JSON.stringify(payload));
28
+ return loadingUrl.href;
70
29
  }
71
- async function clearTargetUrlCache(url) {
72
- const targetUrl = new URL(url, window.location.href).href;
73
- console.info(`[vite-plugin-build-version] Clear cache for ${targetUrl}`);
74
- await Promise.allSettled([
75
- fetch(createCacheBustedUrl(targetUrl), { cache: "no-store" }).then(
76
- (response) => response.arrayBuffer()
77
- ),
78
- fetch(targetUrl, { cache: "reload" }).then(
79
- (response) => response.arrayBuffer()
80
- )
81
- ]);
82
- await loadIframe(createCacheBustedUrl(targetUrl));
30
+ function redirectToLoading(options = {}) {
31
+ window.location.href = createLoadingUrl(options);
83
32
  }
84
- async function clearUrlCache(options = {}) {
85
- const defaultUrl = window.location.origin + window.location.pathname;
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;
89
- const redirectUrl = new URL(
90
- options.redirectUrl ?? urls[0] ?? defaultUrl,
91
- window.location.href
92
- ).href;
93
- const hideLoading = showLoading(time);
94
- let refreshTimeout = 0;
95
- try {
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
- ]);
109
- } finally {
110
- window.clearTimeout(refreshTimeout);
111
- hideLoading();
112
- window.location.replace(createCacheBustedUrl(redirectUrl));
113
- }
33
+ function clearUrlCache(options = {}) {
34
+ redirectToLoading(options);
114
35
  }
115
36
  export {
116
- clearUrlCache
37
+ clearUrlCache,
38
+ redirectToLoading
117
39
  };
package/dist/index.cjs CHANGED
@@ -26,8 +26,216 @@ __export(index_exports, {
26
26
  module.exports = __toCommonJS(index_exports);
27
27
  var import_node_fs = require("fs");
28
28
  var import_node_path = require("path");
29
+
30
+ // src/loading.html
31
+ var loading_default = `<!doctype html>
32
+ <html lang="zh-CN">
33
+ <head>
34
+ <meta charset="UTF-8" />
35
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
36
+ <title>Loading</title>
37
+ <style>
38
+ html,
39
+ body {
40
+ margin: 0;
41
+ height: 100%;
42
+ }
43
+
44
+ #vite-build-version-loading {
45
+ position: fixed;
46
+ inset: 0;
47
+ z-index: 2147483647;
48
+ display: flex;
49
+ flex-direction: column;
50
+ align-items: center;
51
+ justify-content: center;
52
+ gap: 16px;
53
+ color: #333333;
54
+ font: 14px/1.5 sans-serif;
55
+ }
56
+
57
+ #vite-build-version-loading .spinner {
58
+ width: 36px;
59
+ height: 36px;
60
+ border: 3px solid rgba(0, 0, 0, 0.15);
61
+ border-top-color: rgba(0, 0, 0, 0.6);
62
+ border-radius: 50%;
63
+ animation: vite-build-version-loading 0.8s linear infinite;
64
+ }
65
+
66
+ @keyframes vite-build-version-loading {
67
+ to {
68
+ transform: rotate(360deg);
69
+ }
70
+ }
71
+ </style>
72
+ </head>
73
+ <body>
74
+ <div id="vite-build-version-loading">
75
+ <div class="spinner"></div>
76
+ </div>
77
+ <script>
78
+ ;(function () {
79
+ var IFRAME_ID = 'vite-plugin-build-version-file-cache-iframe'
80
+ var IFRAME_TIMEOUT = 5000
81
+ var DEFAULT_REFRESH_TIME = 5
82
+ var DEFAULT_CLEAR_TIMES = 2
83
+
84
+ var params = new URLSearchParams(window.location.search)
85
+ // \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
86
+ var config = (function () {
87
+ var entries = Array.from(params.entries())
88
+
89
+ for (var i = 0; i < entries.length; i++) {
90
+ try {
91
+ var value = JSON.parse(entries[i][1])
92
+
93
+ if (value && typeof value === 'object' && Array.isArray(value.urls)) {
94
+ return value
95
+ }
96
+ } catch (error) {
97
+ // \u975E JSON \u53C2\u6570\uFF08\u4F8B\u5982\u81EA\u5B9A\u4E49 query\uFF09\u76F4\u63A5\u8DF3\u8FC7\u3002
98
+ }
99
+ }
100
+
101
+ return {}
102
+ })()
103
+ // \u9ED8\u8BA4\u628A\u672C\u9875\u9762\u6240\u5728\u76EE\u5F55\u5F53\u4F5C\u5E94\u7528\u6839\u8DEF\u5F84\u3002
104
+ var directoryUrl = new URL('.', window.location.href).href
105
+ var urls = Array.isArray(config.urls)
106
+ ? config.urls.filter(Boolean)
107
+ : []
108
+ var time = Number(config.time)
109
+ var times = Math.floor(Number(config.times))
110
+ var redirectUrl = config.redirectUrl || directoryUrl
111
+ var title = config.title
112
+
113
+ if (urls.length === 0) {
114
+ urls = [directoryUrl, new URL('index.html', directoryUrl).href]
115
+ }
116
+
117
+ if (!Number.isFinite(time) || time <= 0) {
118
+ time = DEFAULT_REFRESH_TIME
119
+ }
120
+
121
+ if (!Number.isFinite(times) || times < 1) {
122
+ times = DEFAULT_CLEAR_TIMES
123
+ }
124
+
125
+ if (title) {
126
+ var titleElement = document.createElement('div')
127
+ titleElement.textContent = title
128
+ document.getElementById('vite-build-version-loading').appendChild(titleElement)
129
+ }
130
+
131
+ function createCacheBustKey() {
132
+ // \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
133
+ return Date.now().toString(36) + Math.random().toString(36).slice(2, 8)
134
+ }
135
+
136
+ function createCacheBustedUrl(url) {
137
+ var targetUrl = new URL(url, window.location.href)
138
+ var hash = targetUrl.hash
139
+
140
+ // 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
141
+ targetUrl.hash = ''
142
+ targetUrl.searchParams.set(
143
+ createCacheBustKey(),
144
+ Date.now() + '-' + Math.random().toString(36).slice(2)
145
+ )
146
+ targetUrl.hash = hash
147
+
148
+ return targetUrl.href
149
+ }
150
+
151
+ function loadIframe(url) {
152
+ return new Promise(function (resolve) {
153
+ // \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
154
+ var existing = document.getElementById(IFRAME_ID)
155
+
156
+ if (existing) {
157
+ existing.remove()
158
+ }
159
+
160
+ var iframe = document.createElement('iframe')
161
+ var frameTimeout = 0
162
+ var completed = false
163
+
164
+ function complete() {
165
+ if (completed) {
166
+ return
167
+ }
168
+
169
+ completed = true
170
+ window.clearTimeout(frameTimeout)
171
+ resolve()
172
+ }
173
+
174
+ frameTimeout = window.setTimeout(complete, IFRAME_TIMEOUT)
175
+
176
+ iframe.hidden = true
177
+ iframe.id = IFRAME_ID
178
+ iframe.addEventListener('load', complete, { once: true })
179
+ iframe.addEventListener('error', complete, { once: true })
180
+ iframe.src = url
181
+ document.documentElement.appendChild(iframe)
182
+ })
183
+ }
184
+
185
+ function clearTargetUrlCache(url) {
186
+ var targetUrl = new URL(url, window.location.href).href
187
+ console.info('[vite-plugin-build-version] Clear cache for ' + targetUrl)
188
+
189
+ // \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
190
+ // \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
191
+ return Promise.allSettled([
192
+ fetch(createCacheBustedUrl(targetUrl), { cache: 'no-store' }).then(
193
+ function (response) {
194
+ return response.arrayBuffer()
195
+ }
196
+ ),
197
+ fetch(targetUrl, { cache: 'reload' }).then(function (response) {
198
+ return response.arrayBuffer()
199
+ })
200
+ ]).then(function () {
201
+ // 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
202
+ return loadIframe(createCacheBustedUrl(targetUrl))
203
+ })
204
+ }
205
+
206
+ var refreshTimeoutId = 0
207
+ var clearAllDone = (async function () {
208
+ // \u6574\u5957\u6E05\u7406\u64CD\u4F5C\u6309 times \u91CD\u590D\u6267\u884C\uFF0C\u9ED8\u8BA4 2 \u6B21\u3002
209
+ for (var round = 0; round < times; round++) {
210
+ for (var i = 0; i < urls.length; i++) {
211
+ try {
212
+ await clearTargetUrlCache(urls[i])
213
+ } catch (error) {
214
+ // \u5355\u7EC4\u5931\u8D25\u4E0D\u963B\u65AD\u540E\u7EED URL\uFF0C\u4FDD\u8BC1\u914D\u7F6E\u7684\u68C0\u6D4B\u7EC4\u90FD\u80FD\u6267\u884C\u3002
215
+ }
216
+ }
217
+ }
218
+ })()
219
+ var limitReached = new Promise(function (resolve) {
220
+ // \u5230\u8FBE\u603B\u7B49\u5F85\u65F6\u9650\u540E\u76F4\u63A5\u8DF3\u8F6C\uFF0C\u4E0D\u53D7\u7F13\u5B58\u8BF7\u6C42\u72B6\u6001\u5F71\u54CD\u3002
221
+ refreshTimeoutId = window.setTimeout(resolve, time * 1000)
222
+ })
223
+
224
+ Promise.race([clearAllDone, limitReached]).then(function () {
225
+ window.clearTimeout(refreshTimeoutId)
226
+ // \u6700\u7EC8\u8DF3\u8F6C\u539F\u6837\u4F7F\u7528\u76EE\u6807\u5730\u5740\uFF0C\u4E0D\u9644\u52A0\u7F13\u5B58\u6233\u3002
227
+ window.location.replace(redirectUrl)
228
+ })
229
+ })()
230
+ </script>
231
+ </body>
232
+ </html>
233
+ `;
234
+
235
+ // src/index.ts
29
236
  var PLUGIN_NAME = "vite-plugin-build-version-file";
30
237
  var DEFAULT_FILENAME = "version.json";
238
+ var DEFAULT_LOADING_PAGE = "loading";
31
239
  var DEFAULT_GLOBAL_NAME = "__VERSION__";
32
240
  var DEFAULT_DEFINE_NAME = "__VERSION__";
33
241
  var DEFAULT_TIME_ZONE = "Asia/Shanghai";
@@ -65,6 +273,16 @@ function normalizeFileName(filename) {
65
273
  }
66
274
  return normalized;
67
275
  }
276
+ function normalizeLoadingPage(loadingPage) {
277
+ if (loadingPage === false) {
278
+ return false;
279
+ }
280
+ const normalized = (loadingPage ?? DEFAULT_LOADING_PAGE).trim().replace(/^\/+/, "");
281
+ if (!normalized) {
282
+ throw new Error(`[${PLUGIN_NAME}] "loadingPage" cannot be empty.`);
283
+ }
284
+ return normalized.endsWith(".html") ? normalized : `${normalized}.html`;
285
+ }
68
286
  function normalizeGlobalName(globalName) {
69
287
  if (!globalName.trim()) {
70
288
  throw new Error(`[${PLUGIN_NAME}] "globalName" cannot be empty.`);
@@ -167,6 +385,13 @@ function toRequestPath(base, fileName) {
167
385
  const baseWithoutTrailingSlash = withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
168
386
  return `${baseWithoutTrailingSlash}/${fileName}`.replace(/\/{2,}/g, "/");
169
387
  }
388
+ function resolveNuxtFileName(config, fileName) {
389
+ if (config.appType !== "custom" || config.build.copyPublicDir !== false) {
390
+ return fileName;
391
+ }
392
+ const assetDir = normalizeFileName(config.build.assetsDir);
393
+ return assetDir === "." || fileName.startsWith(`${assetDir}/`) ? fileName : `${assetDir}/${fileName}`;
394
+ }
170
395
  function resolveProjectRoot(root) {
171
396
  return root ? (0, import_node_path.resolve)(process.cwd(), root) : process.cwd();
172
397
  }
@@ -193,6 +418,7 @@ function createPayload(options, context, data, timeZone, packageInfo = {}) {
193
418
  }
194
419
  function buildVersionPlugin(options = {}) {
195
420
  const fileName = normalizeFileName(options.filename ?? DEFAULT_FILENAME);
421
+ const loadingPage = normalizeLoadingPage(options.loadingPage);
196
422
  const globalName = normalizeGlobalName(
197
423
  options.globalName ?? DEFAULT_GLOBAL_NAME
198
424
  );
@@ -204,6 +430,8 @@ function buildVersionPlugin(options = {}) {
204
430
  const data = normalizeData(options.data);
205
431
  let payload;
206
432
  let skipBundleEmit = false;
433
+ let bundleFileName = fileName;
434
+ let bundleLoadingFileName = loadingPage;
207
435
  const transformHtml = (code, ..._args) => {
208
436
  if (!injectToHtml || !payload) {
209
437
  return void 0;
@@ -238,6 +466,10 @@ function buildVersionPlugin(options = {}) {
238
466
  },
239
467
  configResolved(config) {
240
468
  skipBundleEmit = Boolean(config.build.ssr) && !config.build.ssrEmitAssets;
469
+ bundleFileName = resolveNuxtFileName(config, fileName);
470
+ if (loadingPage) {
471
+ bundleLoadingFileName = resolveNuxtFileName(config, loadingPage);
472
+ }
241
473
  if (payload) {
242
474
  return;
243
475
  }
@@ -250,11 +482,21 @@ function buildVersionPlugin(options = {}) {
250
482
  },
251
483
  configureServer(server) {
252
484
  const requestPath = toRequestPath(server.config.base, fileName);
485
+ const loadingRequestPath = loadingPage ? toRequestPath(server.config.base, loadingPage) : void 0;
253
486
  server.middlewares.use((request, response, next) => {
254
487
  if (request.method !== "GET" && request.method !== "HEAD") {
255
488
  next();
256
489
  return;
257
490
  }
491
+ if (loadingRequestPath && parsePathname(request.url) === loadingRequestPath) {
492
+ response.statusCode = 200;
493
+ response.setHeader("Content-Type", "text/html; charset=utf-8");
494
+ response.setHeader("Cache-Control", "no-cache");
495
+ response.end(
496
+ request.method === "HEAD" ? void 0 : loading_default
497
+ );
498
+ return;
499
+ }
258
500
  if (parsePathname(request.url) !== requestPath || !payload) {
259
501
  next();
260
502
  return;
@@ -285,9 +527,16 @@ function buildVersionPlugin(options = {}) {
285
527
  }
286
528
  this.emitFile({
287
529
  type: "asset",
288
- fileName,
530
+ fileName: bundleFileName,
289
531
  source: serializePayload(payload)
290
532
  });
533
+ if (bundleLoadingFileName) {
534
+ this.emitFile({
535
+ type: "asset",
536
+ fileName: bundleLoadingFileName,
537
+ source: loading_default
538
+ });
539
+ }
291
540
  if (options.log) {
292
541
  console.log(options.log(payload));
293
542
  } else {
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;
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;