unplugin-version-injector 2.2.0 → 2.2.1
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 +118 -15
- package/README.zh-CN.md +119 -15
- package/dist/index.js +45 -26
- package/dist/index.mjs +45 -26
- package/dist/rolldown.js +45 -26
- package/dist/rolldown.mjs +45 -26
- package/dist/rollup.js +45 -26
- package/dist/rollup.mjs +45 -26
- package/dist/rspack.js +45 -26
- package/dist/rspack.mjs +45 -26
- package/dist/vite.js +45 -26
- package/dist/vite.mjs +45 -26
- package/dist/webpack.js +45 -26
- package/dist/webpack.mjs +45 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
✅ Supports Vite, Webpack 4/5, Rspack, Rollup, Rolldown
|
|
18
18
|
✅ Fully compatible with Multi-Page Applications (MPA)
|
|
19
19
|
✅ Customizable version, project name, date format, and theme-based console styling
|
|
20
|
+
✅ Date format supports dayjs-style patterns (`YYYY-MM-DD HH:mm:ss`) with zero extra dependencies
|
|
21
|
+
✅ Optional: attach a version header (`X-Client-Version`) to `fetch` / `XMLHttpRequest` requests to identify clients in backend logs
|
|
20
22
|
|
|
21
23
|
---
|
|
22
24
|
|
|
@@ -125,47 +127,148 @@ In your final HTML output:
|
|
|
125
127
|
| `version` | `string` | Custom version number | Read from package.json |
|
|
126
128
|
| `name` | `string` | Custom project name | Read from package.json |
|
|
127
129
|
| `log` | `boolean` | Whether to inject the console log script | `true` |
|
|
128
|
-
| `formatDate` | `(date: Date) => string`
|
|
130
|
+
| `formatDate` | `string \| ((date: Date) => string)` | Custom build time format: a dayjs-style pattern (e.g. `'YYYY-MM-DD HH:mm:ss'`) or a function | ISO 8601 format |
|
|
129
131
|
| `requestHeaders` | `boolean \| RequestHeadersOptions` | Attach version/build-time headers to outgoing requests | `false` |
|
|
130
132
|
|
|
131
133
|
> `version` and `name` can be provided independently — whichever is missing falls back to the nearest `package.json`.
|
|
132
134
|
|
|
133
135
|
---
|
|
134
136
|
|
|
137
|
+
## 📅 Formatting build time (`formatDate`)
|
|
138
|
+
|
|
139
|
+
`formatDate` accepts two forms, and applies to both the **console banner** and the **`X-Client-Build-Time` header**:
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
// 1) dayjs-style pattern (built-in lightweight impl, no dayjs needed)
|
|
143
|
+
versionInjector({ formatDate: 'YYYY-MM-DD HH:mm:ss' }); // 2024-04-01 12:30:45
|
|
144
|
+
|
|
145
|
+
// 2) custom function
|
|
146
|
+
versionInjector({ formatDate: (date) => date.getTime().toString() }); // timestamp
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Supported tokens: `YYYY YY MMMM MMM MM M DD D dddd ddd dd d HH H hh h mm m ss s SSS SS S A a`.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
135
153
|
## 📡 Request Headers (identify clients in backend logs)
|
|
136
154
|
|
|
137
|
-
Enable `requestHeaders` to patch `window.fetch` and `XMLHttpRequest` so
|
|
155
|
+
Enable `requestHeaders` to patch `window.fetch` and `XMLHttpRequest` so requests carry the client version and build time — making it trivial to tell which client build produced a request in backend/API logs. Two headers are injected by default:
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
X-Client-Version: my-app/1.2.3
|
|
159
|
+
X-Client-Build-Time: 2024-04-01 12:00:00
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
> Because it patches `fetch` / `XMLHttpRequest`, **axios and most HTTP clients work automatically**. `navigator.sendBeacon` and WebSocket connections cannot carry custom headers and are not covered.
|
|
163
|
+
|
|
164
|
+
### `RequestHeadersOptions`
|
|
165
|
+
|
|
166
|
+
| Option | Type | Description | Default |
|
|
167
|
+
|---|---|---|---|
|
|
168
|
+
| `versionHeaderName` | `string` | Version header name; value is `${name}/${version}` | `'X-Client-Version'` |
|
|
169
|
+
| `buildTimeHeaderName` | `string` | Build-time header name; value is the `formatDate` output | `'X-Client-Build-Time'` |
|
|
170
|
+
| `include` | `(string \| RegExp)[]` | Extra **cross-origin** allowlist: string = URL prefix, RegExp = full-URL test. **Same-origin requests are always injected** | `[]` |
|
|
171
|
+
|
|
172
|
+
### Scenario 1: same-origin (simplest)
|
|
173
|
+
|
|
174
|
+
Page and API share an origin, or you use a dev-server proxy (requests hit `/api`, which the browser treats as same-origin):
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
versionInjector({ requestHeaders: true }); // true = defaults, same-origin only
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Scenario 2: custom header names
|
|
138
181
|
|
|
139
182
|
```ts
|
|
140
183
|
versionInjector({
|
|
141
|
-
requestHeaders:
|
|
184
|
+
requestHeaders: {
|
|
185
|
+
versionHeaderName: 'X-App-Version',
|
|
186
|
+
buildTimeHeaderName: 'X-App-Build',
|
|
187
|
+
},
|
|
142
188
|
});
|
|
143
189
|
```
|
|
144
190
|
|
|
145
|
-
|
|
191
|
+
### Scenario 3: a single cross-origin API (most common in prod)
|
|
146
192
|
|
|
193
|
+
Front end and API are on different origins — add the API origin to `include` (string = URL prefix):
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
versionInjector({
|
|
197
|
+
requestHeaders: { include: ['https://api.example.com'] },
|
|
198
|
+
});
|
|
147
199
|
```
|
|
148
|
-
|
|
149
|
-
|
|
200
|
+
|
|
201
|
+
### Scenario 4: multiple cross-origin APIs / RegExp
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
versionInjector({
|
|
205
|
+
requestHeaders: {
|
|
206
|
+
include: [
|
|
207
|
+
'https://api.example.com',
|
|
208
|
+
'https://auth.example.com',
|
|
209
|
+
/^https:\/\/[^/]*\.example\.com\//, // any *.example.com subdomain
|
|
210
|
+
],
|
|
211
|
+
},
|
|
212
|
+
});
|
|
150
213
|
```
|
|
151
214
|
|
|
152
|
-
|
|
215
|
+
### Scenario 5: Monorepo + all cross-origin (the trickiest) ⭐
|
|
216
|
+
|
|
217
|
+
Many packages share one build config, and each app talks to cross-origin APIs that differ per environment (dev / sandbox / prod). Two keys:
|
|
218
|
+
|
|
219
|
+
**1. Write `include` once in the shared root config** — every package inherits it (put the plugin in the shared `configureWebpack` / `vite` config).
|
|
220
|
+
|
|
221
|
+
**2. Don't hardcode origins — build them from env vars.** Each app's `.env.*` usually already defines its API origins:
|
|
222
|
+
|
|
223
|
+
```js
|
|
224
|
+
// shared root build config (webpack example)
|
|
225
|
+
const versionInjector = require('unplugin-version-injector/webpack');
|
|
226
|
+
|
|
227
|
+
// provided by each app / each environment's .env
|
|
228
|
+
const apiOrigins = [
|
|
229
|
+
process.env.VUE_APP_API_ORIGIN,
|
|
230
|
+
process.env.VUE_APP_SDK_API_ORIGIN,
|
|
231
|
+
process.env.VUE_APP_USER_API_ORIGIN,
|
|
232
|
+
].filter(Boolean);
|
|
233
|
+
|
|
234
|
+
module.exports = {
|
|
235
|
+
configureWebpack: {
|
|
236
|
+
plugins: [
|
|
237
|
+
versionInjector({ requestHeaders: { include: apiOrigins } }),
|
|
238
|
+
],
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
dev / sandbox / prod each get the right origins automatically — no giant hardcoded list.
|
|
244
|
+
|
|
245
|
+
If all APIs live under a few fixed base domains, a single RegExp also works (new subdomains match automatically):
|
|
153
246
|
|
|
154
247
|
```ts
|
|
155
248
|
versionInjector({
|
|
156
249
|
requestHeaders: {
|
|
157
|
-
|
|
158
|
-
buildTimeHeaderName: 'X-Client-Build-Time', // default
|
|
159
|
-
// Cross-origin URLs to include (string = URL prefix, RegExp = full URL test).
|
|
160
|
-
// Same-origin requests are always included.
|
|
161
|
-
include: ['https://api.example.com/', /\.internal\.example\.com/],
|
|
250
|
+
include: [/^https:\/\/[^/]*\.(example\.io|example\.dev|sandbox-example\.com)\//],
|
|
162
251
|
},
|
|
163
252
|
});
|
|
164
253
|
```
|
|
165
254
|
|
|
166
|
-
>
|
|
167
|
-
|
|
168
|
-
|
|
255
|
+
> Only include API origins you actually `fetch`/`XHR` **and** whose CORS you control. Don't add CDNs or third-party SDK script hosts — that only triggers preflights and can break asset loading.
|
|
256
|
+
|
|
257
|
+
### ⚠️ Cross-origin requires backend cooperation (CORS preflight)
|
|
258
|
+
|
|
259
|
+
Custom headers on cross-origin requests trigger an `OPTIONS` preflight. **Every** API service matched by `include` must allow the two headers, or the browser blocks the request:
|
|
260
|
+
|
|
261
|
+
```
|
|
262
|
+
Access-Control-Allow-Headers: X-Client-Version, X-Client-Build-Time
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
This is exactly why cross-origin injection is opt-in via `include` rather than on by default.
|
|
266
|
+
|
|
267
|
+
### 🔍 Same-origin or cross-origin?
|
|
268
|
+
|
|
269
|
+
Open the browser Network tab and look at the request's **real URL**:
|
|
270
|
+
- `http://localhost:9040/api/...` (via dev proxy) → **same-origin**, `requestHeaders: true` is enough, no `include`.
|
|
271
|
+
- `https://api.xxx.com/...` (direct) → **cross-origin**, must be in `include` + backend must allow the headers.
|
|
169
272
|
|
|
170
273
|
---
|
|
171
274
|
|
package/README.zh-CN.md
CHANGED
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
✅ 完美兼容多页面应用(MPA)
|
|
17
17
|
✅ 支持自定义版本号、项目名、时间格式,默认读取 `package.json`
|
|
18
18
|
✅ 控制台输出支持自动适配深/浅主题配色
|
|
19
|
+
✅ 时间格式支持 dayjs 风格字符串(`YYYY-MM-DD HH:mm:ss`),零额外依赖
|
|
20
|
+
✅ 可选:给 `fetch` / `XMLHttpRequest` 请求自动注入版本请求头(`X-Client-Version`),在后端日志中定位客户端版本
|
|
19
21
|
|
|
20
22
|
---
|
|
21
23
|
|
|
@@ -131,46 +133,148 @@ export default {
|
|
|
131
133
|
| `version` | `string` | 自定义版本号 | 自动读取 package.json |
|
|
132
134
|
| `name` | `string` | 自定义项目名 | 自动读取 package.json |
|
|
133
135
|
| `log` | `boolean` | 是否输出控制台日志 | `true` |
|
|
134
|
-
| `formatDate` | `(date: Date) => string` |
|
|
136
|
+
| `formatDate` | `string \| ((date: Date) => string)` | 自定义构建时间格式:支持 dayjs 风格字符串(如 `'YYYY-MM-DD HH:mm:ss'`)或函数 | ISO 格式 |
|
|
135
137
|
| `requestHeaders` | `boolean \| RequestHeadersOptions` | 给发出的请求自动附加版本/构建时间请求头 | `false` |
|
|
136
138
|
|
|
137
139
|
> `version` 和 `name` 可以单独传入,缺失的一项会自动从最近的 `package.json` 读取。
|
|
138
140
|
|
|
139
141
|
---
|
|
140
142
|
|
|
141
|
-
##
|
|
143
|
+
## **📅 构建时间格式化 `formatDate`**
|
|
142
144
|
|
|
143
|
-
|
|
145
|
+
`formatDate` 支持两种写法,同时作用于**控制台构建时间**和 **`X-Client-Build-Time` 请求头**:
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
// 1) dayjs 风格字符串(内置轻量实现,无需安装 dayjs)
|
|
149
|
+
versionInjector({ formatDate: 'YYYY-MM-DD HH:mm:ss' }); // 2024-04-01 12:30:45
|
|
150
|
+
|
|
151
|
+
// 2) 自定义函数
|
|
152
|
+
versionInjector({ formatDate: (date) => date.getTime().toString() }); // 时间戳
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
支持的 token:`YYYY YY MMMM MMM MM M DD D dddd ddd dd d HH H hh h mm m ss s SSS SS S A a`。
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## **📡 请求头注入(在后端日志中定位客户端版本)**
|
|
160
|
+
|
|
161
|
+
开启 `requestHeaders` 后,插件会在页面最前面 patch `window.fetch` 和 `XMLHttpRequest`,让请求自动带上版本与构建时间——排查前后端日志时,一眼就能看出是哪个客户端、哪个版本发出的请求。默认注入两个头:
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
X-Client-Version: my-app/1.2.3
|
|
165
|
+
X-Client-Build-Time: 2024-04-01 12:00:00
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
> 底层是 patch `fetch` / `XMLHttpRequest`,所以 **axios、umi-request 等主流库都自动生效**;`navigator.sendBeacon` 和 WebSocket 无法携带自定义头,不在覆盖范围。
|
|
169
|
+
|
|
170
|
+
### 子配置 `RequestHeadersOptions`
|
|
171
|
+
|
|
172
|
+
| 选项 | 类型 | 说明 | 默认值 |
|
|
173
|
+
|---|---|---|---|
|
|
174
|
+
| `versionHeaderName` | `string` | 版本头名称,值为 `${name}/${version}` | `'X-Client-Version'` |
|
|
175
|
+
| `buildTimeHeaderName` | `string` | 构建时间头名称,值为 `formatDate` 的输出 | `'X-Client-Build-Time'` |
|
|
176
|
+
| `include` | `(string \| RegExp)[]` | 额外注入的**跨域**地址白名单:字符串按 URL 前缀匹配,正则按完整 URL 测试。**同源请求始终注入** | `[]` |
|
|
177
|
+
|
|
178
|
+
### 场景 1:同源请求(最简单)
|
|
179
|
+
|
|
180
|
+
页面与 API 同域,或本地走 dev-server 代理(请求发到 `/api`,浏览器视角是同源):
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
versionInjector({ requestHeaders: true }); // true = 默认配置,仅同源
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### 场景 2:自定义请求头名称
|
|
144
187
|
|
|
145
188
|
```ts
|
|
146
189
|
versionInjector({
|
|
147
|
-
requestHeaders:
|
|
190
|
+
requestHeaders: {
|
|
191
|
+
versionHeaderName: 'X-App-Version',
|
|
192
|
+
buildTimeHeaderName: 'X-App-Build',
|
|
193
|
+
},
|
|
148
194
|
});
|
|
149
195
|
```
|
|
150
196
|
|
|
151
|
-
|
|
197
|
+
### 场景 3:单个跨域 API(生产最常见)
|
|
152
198
|
|
|
199
|
+
前端和 API 不同源,把 API 域名加进 `include`(字符串 = URL 前缀):
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
versionInjector({
|
|
203
|
+
requestHeaders: { include: ['https://api.example.com'] },
|
|
204
|
+
});
|
|
153
205
|
```
|
|
154
|
-
|
|
155
|
-
|
|
206
|
+
|
|
207
|
+
### 场景 4:多个跨域 API / 正则批量匹配
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
versionInjector({
|
|
211
|
+
requestHeaders: {
|
|
212
|
+
include: [
|
|
213
|
+
'https://api.example.com',
|
|
214
|
+
'https://auth.example.com',
|
|
215
|
+
/^https:\/\/[^/]*\.example\.com\//, // 匹配 *.example.com 所有子域
|
|
216
|
+
],
|
|
217
|
+
},
|
|
218
|
+
});
|
|
156
219
|
```
|
|
157
220
|
|
|
158
|
-
|
|
221
|
+
### 场景 5:Monorepo + 全跨域(最容易踩坑)⭐
|
|
222
|
+
|
|
223
|
+
多包共用一份构建配置、每个子应用又连不同环境(dev / sandbox / prod)的跨域 API——这是最难配的场景。两个关键点:
|
|
224
|
+
|
|
225
|
+
**① `include` 只需在共享的根配置里写一次**,所有子包继承即可(把插件放进共享的 `configureWebpack` / `vite` 配置)。
|
|
226
|
+
|
|
227
|
+
**② 域名随环境变化,别硬编码——用环境变量动态拼**。每个子应用的 `.env.*` 里通常已有 API 域名变量,直接读:
|
|
228
|
+
|
|
229
|
+
```js
|
|
230
|
+
// 共享的根构建配置(以 webpack 为例)
|
|
231
|
+
const versionInjector = require('unplugin-version-injector/webpack');
|
|
232
|
+
|
|
233
|
+
// 这些变量由各子应用 / 各环境的 .env 提供
|
|
234
|
+
const apiOrigins = [
|
|
235
|
+
process.env.VUE_APP_API_ORIGIN,
|
|
236
|
+
process.env.VUE_APP_SDK_API_ORIGIN,
|
|
237
|
+
process.env.VUE_APP_USER_API_ORIGIN,
|
|
238
|
+
].filter(Boolean); // 去掉未定义的
|
|
239
|
+
|
|
240
|
+
module.exports = {
|
|
241
|
+
configureWebpack: {
|
|
242
|
+
plugins: [
|
|
243
|
+
versionInjector({ requestHeaders: { include: apiOrigins } }),
|
|
244
|
+
],
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
这样 dev / sandbox / prod 各自带对应域名,不用维护一份大清单。
|
|
250
|
+
|
|
251
|
+
若所有 API 都在固定的几个主域名下,也可以直接用一条正则(新增子域自动命中):
|
|
159
252
|
|
|
160
253
|
```ts
|
|
161
254
|
versionInjector({
|
|
162
255
|
requestHeaders: {
|
|
163
|
-
|
|
164
|
-
buildTimeHeaderName: 'X-Client-Build-Time', // 默认值
|
|
165
|
-
// 跨域白名单:字符串按 URL 前缀匹配,正则按完整 URL 测试;同源请求始终注入
|
|
166
|
-
include: ['https://api.example.com/', /\.internal\.example\.com/],
|
|
256
|
+
include: [/^https:\/\/[^/]*\.(example\.io|example\.dev|sandbox-example\.com)\//],
|
|
167
257
|
},
|
|
168
258
|
});
|
|
169
259
|
```
|
|
170
260
|
|
|
171
|
-
>
|
|
172
|
-
|
|
173
|
-
|
|
261
|
+
> 只把**你自己发 fetch/XHR、且能改 CORS 的 API 域名**放进去。CDN、第三方 SDK 脚本域(你控制不了 CORS)不要加,否则只会触发预检导致资源加载失败。
|
|
262
|
+
|
|
263
|
+
### ⚠️ 跨域必读:后端要放行(CORS 预检)
|
|
264
|
+
|
|
265
|
+
给跨域请求加自定义头,浏览器会先发一个 `OPTIONS` 预检。`include` 命中的**每一个** API 服务都必须在响应里放行这两个头,否则请求会被浏览器拦掉:
|
|
266
|
+
|
|
267
|
+
```
|
|
268
|
+
Access-Control-Allow-Headers: X-Client-Version, X-Client-Build-Time
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
多后端场景要逐个确认。这也是跨域注入必须通过 `include` 显式开启、而非默认全开的原因。
|
|
272
|
+
|
|
273
|
+
### 🔍 怎么判断请求是同源还是跨域?
|
|
274
|
+
|
|
275
|
+
打开浏览器 Network,看请求的**真实 URL**:
|
|
276
|
+
- `http://localhost:9040/api/...`(走 dev 代理)→ **同源**,`requestHeaders: true` 就够,不用 `include`;
|
|
277
|
+
- `https://api.xxx.com/...`(直连)→ **跨域**,必须加进 `include` + 后端放行。
|
|
174
278
|
|
|
175
279
|
---
|
|
176
280
|
|
package/dist/index.js
CHANGED
|
@@ -102,18 +102,27 @@ function toScriptString(value) {
|
|
|
102
102
|
var INJECTED_MARK = 'data-injected="unplugin-version-injector"';
|
|
103
103
|
var HEADERS_INJECTED_MARK = 'data-injected="unplugin-version-injector-headers"';
|
|
104
104
|
var HEADER_NAME_RE = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
105
|
+
var DEFAULT_VERSION_HEADER = "X-Client-Version";
|
|
106
|
+
var DEFAULT_BUILD_TIME_HEADER = "X-Client-Build-Time";
|
|
105
107
|
function normalizeRequestHeaders(opt) {
|
|
106
|
-
var _a, _b, _c;
|
|
107
108
|
if (!opt) return null;
|
|
108
109
|
const o = typeof opt === "object" ? opt : {};
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
110
|
+
const pickHeaderName = (value, fallback) => {
|
|
111
|
+
if (value == null) return fallback;
|
|
112
|
+
if (HEADER_NAME_RE.test(value)) return value;
|
|
113
|
+
console.warn(
|
|
114
|
+
`[VersionInjector] invalid request header name "${value}", falling back to "${fallback}"`
|
|
115
|
+
);
|
|
116
|
+
return fallback;
|
|
117
|
+
};
|
|
118
|
+
const include = (Array.isArray(o.include) ? o.include : []).filter(
|
|
119
|
+
(p) => typeof p === "string" || p instanceof RegExp
|
|
120
|
+
);
|
|
121
|
+
return {
|
|
122
|
+
versionHeaderName: pickHeaderName(o.versionHeaderName, DEFAULT_VERSION_HEADER),
|
|
123
|
+
buildTimeHeaderName: pickHeaderName(o.buildTimeHeaderName, DEFAULT_BUILD_TIME_HEADER),
|
|
124
|
+
include
|
|
125
|
+
};
|
|
117
126
|
}
|
|
118
127
|
function sanitizeHeaderValue(value) {
|
|
119
128
|
return value.replace(/[^\t\x20-\x7E]/g, "").trim();
|
|
@@ -137,11 +146,13 @@ function createVersionInjector(options = {}) {
|
|
|
137
146
|
const buildLogScript = (buildTime) => `
|
|
138
147
|
<script ${INJECTED_MARK}>
|
|
139
148
|
(function () {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
149
|
+
try {
|
|
150
|
+
var isDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
151
|
+
var bg = isDark ? '#ffffff' : '#1e1e1e';
|
|
152
|
+
var base = 'background: ' + bg + '; border-radius: 4px; padding: 4px; font-size: 12px;';
|
|
153
|
+
console.log('%c' + ${toScriptString(` ${name}@${version} `)}, base + ' color: #00c853;');
|
|
154
|
+
console.log('%c' + ${toScriptString(` Build Time: ${buildTime} `)}, base + ' color: #ffab00;');
|
|
155
|
+
} catch (e) {}
|
|
145
156
|
})();
|
|
146
157
|
</script>`;
|
|
147
158
|
const buildHeaderScript = (buildTime, cfg) => {
|
|
@@ -150,6 +161,7 @@ function createVersionInjector(options = {}) {
|
|
|
150
161
|
return `
|
|
151
162
|
<script ${HEADERS_INJECTED_MARK}>
|
|
152
163
|
(function () {
|
|
164
|
+
try {
|
|
153
165
|
if (window.__UVI_HEADERS_PATCHED__) return;
|
|
154
166
|
window.__UVI_HEADERS_PATCHED__ = true;
|
|
155
167
|
var VERSION_HEADER = ${toScriptString(cfg.versionHeaderName)};
|
|
@@ -228,27 +240,34 @@ function createVersionInjector(options = {}) {
|
|
|
228
240
|
};
|
|
229
241
|
}
|
|
230
242
|
}
|
|
243
|
+
} catch (e) {}
|
|
231
244
|
})();
|
|
232
245
|
</script>`;
|
|
233
246
|
};
|
|
234
247
|
const processHtml = function processHtml2(html) {
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
248
|
+
const original = html;
|
|
249
|
+
try {
|
|
250
|
+
const buildTime = formatDate2(/* @__PURE__ */ new Date());
|
|
251
|
+
if (!html.includes('<meta name="version"')) {
|
|
252
|
+
html = html.replace(/<head[^>]*>/i, (match) => `${match}
|
|
238
253
|
${metaTag}`);
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
254
|
+
}
|
|
255
|
+
if (headersConfig && !html.includes(HEADERS_INJECTED_MARK)) {
|
|
256
|
+
const headerScript = buildHeaderScript(buildTime, headersConfig);
|
|
257
|
+
html = html.replace(/<head[^>]*>/i, (match) => `${match}
|
|
243
258
|
${headerScript}
|
|
244
259
|
`);
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
260
|
+
}
|
|
261
|
+
if (options.log !== false && !html.includes(INJECTED_MARK)) {
|
|
262
|
+
const logScript = buildLogScript(buildTime);
|
|
263
|
+
html = html.replace(/<\/body>/i, () => ` ${logScript}
|
|
249
264
|
</body>`);
|
|
265
|
+
}
|
|
266
|
+
return html;
|
|
267
|
+
} catch (err) {
|
|
268
|
+
console.warn("[VersionInjector] injection failed, returning original HTML unchanged:", err);
|
|
269
|
+
return original;
|
|
250
270
|
}
|
|
251
|
-
return html;
|
|
252
271
|
};
|
|
253
272
|
processHtml.resetBuildTime = () => {
|
|
254
273
|
};
|
package/dist/index.mjs
CHANGED
|
@@ -93,18 +93,27 @@ function toScriptString(value) {
|
|
|
93
93
|
var INJECTED_MARK = 'data-injected="unplugin-version-injector"';
|
|
94
94
|
var HEADERS_INJECTED_MARK = 'data-injected="unplugin-version-injector-headers"';
|
|
95
95
|
var HEADER_NAME_RE = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
96
|
+
var DEFAULT_VERSION_HEADER = "X-Client-Version";
|
|
97
|
+
var DEFAULT_BUILD_TIME_HEADER = "X-Client-Build-Time";
|
|
96
98
|
function normalizeRequestHeaders(opt) {
|
|
97
|
-
var _a, _b, _c;
|
|
98
99
|
if (!opt) return null;
|
|
99
100
|
const o = typeof opt === "object" ? opt : {};
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
101
|
+
const pickHeaderName = (value, fallback) => {
|
|
102
|
+
if (value == null) return fallback;
|
|
103
|
+
if (HEADER_NAME_RE.test(value)) return value;
|
|
104
|
+
console.warn(
|
|
105
|
+
`[VersionInjector] invalid request header name "${value}", falling back to "${fallback}"`
|
|
106
|
+
);
|
|
107
|
+
return fallback;
|
|
108
|
+
};
|
|
109
|
+
const include = (Array.isArray(o.include) ? o.include : []).filter(
|
|
110
|
+
(p) => typeof p === "string" || p instanceof RegExp
|
|
111
|
+
);
|
|
112
|
+
return {
|
|
113
|
+
versionHeaderName: pickHeaderName(o.versionHeaderName, DEFAULT_VERSION_HEADER),
|
|
114
|
+
buildTimeHeaderName: pickHeaderName(o.buildTimeHeaderName, DEFAULT_BUILD_TIME_HEADER),
|
|
115
|
+
include
|
|
116
|
+
};
|
|
108
117
|
}
|
|
109
118
|
function sanitizeHeaderValue(value) {
|
|
110
119
|
return value.replace(/[^\t\x20-\x7E]/g, "").trim();
|
|
@@ -128,11 +137,13 @@ function createVersionInjector(options = {}) {
|
|
|
128
137
|
const buildLogScript = (buildTime) => `
|
|
129
138
|
<script ${INJECTED_MARK}>
|
|
130
139
|
(function () {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
140
|
+
try {
|
|
141
|
+
var isDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
142
|
+
var bg = isDark ? '#ffffff' : '#1e1e1e';
|
|
143
|
+
var base = 'background: ' + bg + '; border-radius: 4px; padding: 4px; font-size: 12px;';
|
|
144
|
+
console.log('%c' + ${toScriptString(` ${name}@${version} `)}, base + ' color: #00c853;');
|
|
145
|
+
console.log('%c' + ${toScriptString(` Build Time: ${buildTime} `)}, base + ' color: #ffab00;');
|
|
146
|
+
} catch (e) {}
|
|
136
147
|
})();
|
|
137
148
|
</script>`;
|
|
138
149
|
const buildHeaderScript = (buildTime, cfg) => {
|
|
@@ -141,6 +152,7 @@ function createVersionInjector(options = {}) {
|
|
|
141
152
|
return `
|
|
142
153
|
<script ${HEADERS_INJECTED_MARK}>
|
|
143
154
|
(function () {
|
|
155
|
+
try {
|
|
144
156
|
if (window.__UVI_HEADERS_PATCHED__) return;
|
|
145
157
|
window.__UVI_HEADERS_PATCHED__ = true;
|
|
146
158
|
var VERSION_HEADER = ${toScriptString(cfg.versionHeaderName)};
|
|
@@ -219,27 +231,34 @@ function createVersionInjector(options = {}) {
|
|
|
219
231
|
};
|
|
220
232
|
}
|
|
221
233
|
}
|
|
234
|
+
} catch (e) {}
|
|
222
235
|
})();
|
|
223
236
|
</script>`;
|
|
224
237
|
};
|
|
225
238
|
const processHtml = function processHtml2(html) {
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
239
|
+
const original = html;
|
|
240
|
+
try {
|
|
241
|
+
const buildTime = formatDate2(/* @__PURE__ */ new Date());
|
|
242
|
+
if (!html.includes('<meta name="version"')) {
|
|
243
|
+
html = html.replace(/<head[^>]*>/i, (match) => `${match}
|
|
229
244
|
${metaTag}`);
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
245
|
+
}
|
|
246
|
+
if (headersConfig && !html.includes(HEADERS_INJECTED_MARK)) {
|
|
247
|
+
const headerScript = buildHeaderScript(buildTime, headersConfig);
|
|
248
|
+
html = html.replace(/<head[^>]*>/i, (match) => `${match}
|
|
234
249
|
${headerScript}
|
|
235
250
|
`);
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
251
|
+
}
|
|
252
|
+
if (options.log !== false && !html.includes(INJECTED_MARK)) {
|
|
253
|
+
const logScript = buildLogScript(buildTime);
|
|
254
|
+
html = html.replace(/<\/body>/i, () => ` ${logScript}
|
|
240
255
|
</body>`);
|
|
256
|
+
}
|
|
257
|
+
return html;
|
|
258
|
+
} catch (err) {
|
|
259
|
+
console.warn("[VersionInjector] injection failed, returning original HTML unchanged:", err);
|
|
260
|
+
return original;
|
|
241
261
|
}
|
|
242
|
-
return html;
|
|
243
262
|
};
|
|
244
263
|
processHtml.resetBuildTime = () => {
|
|
245
264
|
};
|