vite-plugin-build-version-file 0.1.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/LICENSE +21 -0
- package/README.md +362 -0
- package/dist/index.cjs +164 -0
- package/dist/index.d.cts +19 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.mjs +139 -0
- package/package.json +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
# vite-plugin-build-version
|
|
2
|
+
|
|
3
|
+
一个面向 `Vue 3 + Vite` 的构建版本插件。
|
|
4
|
+
|
|
5
|
+
它会在项目运行和构建时提供统一的版本信息:
|
|
6
|
+
|
|
7
|
+
- 构建产物中生成 `version.json`
|
|
8
|
+
- 可选地向 HTML 注入 `window["__VERSION__"]`
|
|
9
|
+
- 支持自定义版本号、文件名和全局变量名
|
|
10
|
+
|
|
11
|
+
适合用在这些场景:
|
|
12
|
+
|
|
13
|
+
- 页面显示当前前端版本
|
|
14
|
+
- 检测线上是否有新版本并提示刷新
|
|
15
|
+
- 灰度发布时对比本地版本和服务端版本
|
|
16
|
+
|
|
17
|
+
## 适用范围
|
|
18
|
+
|
|
19
|
+
- 仅支持 `Vite`
|
|
20
|
+
- 主要面向 `Vue 3` 项目使用
|
|
21
|
+
- `peerDependencies` 要求 `vite >= 5`
|
|
22
|
+
- 运行环境要求 `node >= 18`
|
|
23
|
+
|
|
24
|
+
## 安装
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pnpm add vite-plugin-build-version
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
也可以使用:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install vite-plugin-build-version
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 快速开始
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { defineConfig } from 'vite'
|
|
40
|
+
import vue from '@vitejs/plugin-vue'
|
|
41
|
+
import buildVersionPlugin from 'vite-plugin-build-version'
|
|
42
|
+
|
|
43
|
+
export default defineConfig({
|
|
44
|
+
plugins: [vue(), buildVersionPlugin()]
|
|
45
|
+
})
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 默认行为
|
|
49
|
+
|
|
50
|
+
在默认配置下,插件会执行以下行为:
|
|
51
|
+
|
|
52
|
+
- 开发态访问 `/version.json` 时返回版本信息
|
|
53
|
+
- 构建后在输出目录生成 `version.json`
|
|
54
|
+
- 构建后的 HTML 中注入 `window["__VERSION__"]`
|
|
55
|
+
- 开发态默认版本号为 `0`
|
|
56
|
+
- 构建态默认版本号为当前时间戳,格式为 `YYYYMMDDHHmmss`
|
|
57
|
+
|
|
58
|
+
默认生成的版本文件内容如下:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"version": "20260401153045",
|
|
63
|
+
"production": true
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
开发态默认返回:
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"version": 0,
|
|
72
|
+
"production": false
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## 配置项
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
type BuildVersionContext = {
|
|
80
|
+
command: 'serve' | 'build'
|
|
81
|
+
mode: string
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
type BuildVersionPluginOptions = {
|
|
85
|
+
filename?: string
|
|
86
|
+
globalName?: string
|
|
87
|
+
injectToHtml?: boolean
|
|
88
|
+
version?: string | number | ((ctx: BuildVersionContext) => string | number)
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### `filename`
|
|
93
|
+
|
|
94
|
+
- 类型:`string`
|
|
95
|
+
- 默认值:`"version.json"`
|
|
96
|
+
- 作用:控制输出文件名
|
|
97
|
+
|
|
98
|
+
说明:
|
|
99
|
+
|
|
100
|
+
- 支持相对路径,例如 `meta/version.json`
|
|
101
|
+
- 构建时会输出到 `dist/meta/version.json`
|
|
102
|
+
- 开发态访问路径也会同步变成对应地址
|
|
103
|
+
|
|
104
|
+
### `globalName`
|
|
105
|
+
|
|
106
|
+
- 类型:`string`
|
|
107
|
+
- 默认值:`"__VERSION__"`
|
|
108
|
+
- 作用:控制注入到 `window` 上的属性名
|
|
109
|
+
|
|
110
|
+
默认注入效果:
|
|
111
|
+
|
|
112
|
+
```html
|
|
113
|
+
<script>
|
|
114
|
+
window["__VERSION__"] = "20260401153045";
|
|
115
|
+
</script>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### `injectToHtml`
|
|
119
|
+
|
|
120
|
+
- 类型:`boolean`
|
|
121
|
+
- 默认值:`true`
|
|
122
|
+
- 作用:是否向 HTML 注入 `window` 全局变量
|
|
123
|
+
|
|
124
|
+
当设置为 `false` 时:
|
|
125
|
+
|
|
126
|
+
- 仍然会生成 `version.json`
|
|
127
|
+
- 不会注入 `window["__VERSION__"]`
|
|
128
|
+
|
|
129
|
+
### `version`
|
|
130
|
+
|
|
131
|
+
- 类型:`string | number | ((ctx) => string | number)`
|
|
132
|
+
- 默认值:开发态为 `0`,构建态为当前时间戳
|
|
133
|
+
- 作用:自定义版本号生成逻辑
|
|
134
|
+
|
|
135
|
+
如果传入函数,函数会收到:
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
{
|
|
139
|
+
command: 'serve' | 'build',
|
|
140
|
+
mode: string
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## 使用示例
|
|
145
|
+
|
|
146
|
+
### 1. 使用默认配置
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import { defineConfig } from 'vite'
|
|
150
|
+
import vue from '@vitejs/plugin-vue'
|
|
151
|
+
import buildVersionPlugin from 'vite-plugin-build-version'
|
|
152
|
+
|
|
153
|
+
export default defineConfig({
|
|
154
|
+
plugins: [vue(), buildVersionPlugin()]
|
|
155
|
+
})
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### 2. 自定义全局变量名
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
import { defineConfig } from 'vite'
|
|
162
|
+
import vue from '@vitejs/plugin-vue'
|
|
163
|
+
import buildVersionPlugin from 'vite-plugin-build-version'
|
|
164
|
+
|
|
165
|
+
export default defineConfig({
|
|
166
|
+
plugins: [
|
|
167
|
+
vue(),
|
|
168
|
+
buildVersionPlugin({
|
|
169
|
+
globalName: '__APP_VERSION__'
|
|
170
|
+
})
|
|
171
|
+
]
|
|
172
|
+
})
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
注入后可以这样读取:
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
const version = window['__APP_VERSION__']
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### 3. 只保留 `version.json`,不注入 `window`
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
import { defineConfig } from 'vite'
|
|
185
|
+
import vue from '@vitejs/plugin-vue'
|
|
186
|
+
import buildVersionPlugin from 'vite-plugin-build-version'
|
|
187
|
+
|
|
188
|
+
export default defineConfig({
|
|
189
|
+
plugins: [
|
|
190
|
+
vue(),
|
|
191
|
+
buildVersionPlugin({
|
|
192
|
+
injectToHtml: false
|
|
193
|
+
})
|
|
194
|
+
]
|
|
195
|
+
})
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### 4. 自定义输出路径
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
import { defineConfig } from 'vite'
|
|
202
|
+
import vue from '@vitejs/plugin-vue'
|
|
203
|
+
import buildVersionPlugin from 'vite-plugin-build-version'
|
|
204
|
+
|
|
205
|
+
export default defineConfig({
|
|
206
|
+
plugins: [
|
|
207
|
+
vue(),
|
|
208
|
+
buildVersionPlugin({
|
|
209
|
+
filename: 'meta/version.json'
|
|
210
|
+
})
|
|
211
|
+
]
|
|
212
|
+
})
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
构建后输出:
|
|
216
|
+
|
|
217
|
+
```text
|
|
218
|
+
dist/meta/version.json
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### 5. 自定义版本号生成逻辑
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
import { defineConfig } from 'vite'
|
|
225
|
+
import vue from '@vitejs/plugin-vue'
|
|
226
|
+
import buildVersionPlugin from 'vite-plugin-build-version'
|
|
227
|
+
|
|
228
|
+
export default defineConfig({
|
|
229
|
+
plugins: [
|
|
230
|
+
vue(),
|
|
231
|
+
buildVersionPlugin({
|
|
232
|
+
version: ({ command, mode }) => {
|
|
233
|
+
if (command === 'serve') {
|
|
234
|
+
return 0
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return `${mode}-20260401153045`
|
|
238
|
+
}
|
|
239
|
+
})
|
|
240
|
+
]
|
|
241
|
+
})
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## 在项目中读取版本
|
|
245
|
+
|
|
246
|
+
### 读取注入的全局变量
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
const version = window['__VERSION__']
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### 读取 `version.json`
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
const response = await fetch('/version.json')
|
|
256
|
+
const payload = await response.json()
|
|
257
|
+
|
|
258
|
+
console.log(payload.version)
|
|
259
|
+
console.log(payload.production)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
如果你配置了自定义 `filename`,这里的请求路径也需要改成对应地址。
|
|
263
|
+
|
|
264
|
+
## 开发态行为说明
|
|
265
|
+
|
|
266
|
+
开发态下,插件不会往项目的 `public/` 目录写文件。
|
|
267
|
+
|
|
268
|
+
它会直接通过 Vite dev server 返回版本 JSON,因此:
|
|
269
|
+
|
|
270
|
+
- 不会污染业务项目目录
|
|
271
|
+
- 更适合本地调试
|
|
272
|
+
- 修改配置后重新启动 dev server 即可看到结果
|
|
273
|
+
|
|
274
|
+
如果项目配置了 `base`,版本文件访问路径会自动跟随 `base`。
|
|
275
|
+
|
|
276
|
+
## 本地开发与联调
|
|
277
|
+
|
|
278
|
+
仓库中已经提供了一个专门用于调试的 `Vite + Vue 3` 测试项目:
|
|
279
|
+
|
|
280
|
+
- 路径:`debug/fixture-app`
|
|
281
|
+
- 引用方式:直接引用根目录 `dist/index.mjs`
|
|
282
|
+
|
|
283
|
+
这意味着它测试的是“打包后的真实产物”,而不是源码直连。
|
|
284
|
+
|
|
285
|
+
### 安装依赖
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
pnpm install
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### 构建插件
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
pnpm build
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### 启动测试项目
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
pnpm dev:fixture
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
启动后可以在页面里直接看到:
|
|
304
|
+
|
|
305
|
+
- `window["__VERSION__"]`
|
|
306
|
+
- `/version.json` 的返回结果
|
|
307
|
+
|
|
308
|
+
### 构建测试项目
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
pnpm build:fixture
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
这个命令会先构建插件,再构建测试项目,用来验证 `dist` 产物能否被正常消费。
|
|
315
|
+
|
|
316
|
+
## 发布到 npm
|
|
317
|
+
|
|
318
|
+
### 1. 登录 npm
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
npm login
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### 2. 构建并验证
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
pnpm build
|
|
328
|
+
pnpm build:fixture
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### 3. 发布公开包
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
npm publish --access public
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## 发布前检查建议
|
|
338
|
+
|
|
339
|
+
发布前建议至少确认以下几点:
|
|
340
|
+
|
|
341
|
+
- `dist/index.mjs`、`dist/index.cjs`、`dist/index.d.ts` 已生成
|
|
342
|
+
- `debug/fixture-app` 可以正常运行或构建
|
|
343
|
+
- `version.json` 输出正常
|
|
344
|
+
- HTML 注入结果符合预期
|
|
345
|
+
- `package.json` 中的 `name`、`version`、`exports`、`publishConfig` 正确
|
|
346
|
+
|
|
347
|
+
如果你要进一步检查最终发布内容,可以执行:
|
|
348
|
+
|
|
349
|
+
```bash
|
|
350
|
+
npm pack
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
正常情况下,发布包中应只包含:
|
|
354
|
+
|
|
355
|
+
- `dist`
|
|
356
|
+
- `README.md`
|
|
357
|
+
- `LICENSE`
|
|
358
|
+
- `package.json`
|
|
359
|
+
|
|
360
|
+
## License
|
|
361
|
+
|
|
362
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
buildVersionPlugin: () => buildVersionPlugin,
|
|
24
|
+
default: () => index_default
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
var PLUGIN_NAME = "vite-plugin-build-version";
|
|
28
|
+
var DEFAULT_FILENAME = "version.json";
|
|
29
|
+
var DEFAULT_GLOBAL_NAME = "__VERSION__";
|
|
30
|
+
function formatBuildTimestamp(date) {
|
|
31
|
+
const pad = (value) => value.toString().padStart(2, "0");
|
|
32
|
+
return [
|
|
33
|
+
date.getFullYear(),
|
|
34
|
+
pad(date.getMonth() + 1),
|
|
35
|
+
pad(date.getDate()),
|
|
36
|
+
pad(date.getHours()),
|
|
37
|
+
pad(date.getMinutes()),
|
|
38
|
+
pad(date.getSeconds())
|
|
39
|
+
].join("");
|
|
40
|
+
}
|
|
41
|
+
function createDefaultVersion(command) {
|
|
42
|
+
if (command === "serve") {
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
return formatBuildTimestamp(/* @__PURE__ */ new Date());
|
|
46
|
+
}
|
|
47
|
+
function normalizeFileName(filename) {
|
|
48
|
+
const normalized = filename.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
49
|
+
if (!normalized || normalized.endsWith("/")) {
|
|
50
|
+
throw new Error(`[${PLUGIN_NAME}] "filename" must point to a file path.`);
|
|
51
|
+
}
|
|
52
|
+
return normalized;
|
|
53
|
+
}
|
|
54
|
+
function normalizeGlobalName(globalName) {
|
|
55
|
+
if (!globalName.trim()) {
|
|
56
|
+
throw new Error(`[${PLUGIN_NAME}] "globalName" cannot be empty.`);
|
|
57
|
+
}
|
|
58
|
+
return globalName;
|
|
59
|
+
}
|
|
60
|
+
function ensureVersionValue(value) {
|
|
61
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
throw new Error(
|
|
65
|
+
`[${PLUGIN_NAME}] "version" must resolve to a string or number.`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
function resolveVersionValue(versionOption, context) {
|
|
69
|
+
if (versionOption === void 0) {
|
|
70
|
+
return createDefaultVersion(context.command);
|
|
71
|
+
}
|
|
72
|
+
if (typeof versionOption !== "function") {
|
|
73
|
+
return ensureVersionValue(versionOption);
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
return ensureVersionValue(versionOption(context));
|
|
77
|
+
} catch (error) {
|
|
78
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
79
|
+
throw new Error(`[${PLUGIN_NAME}] Failed to resolve version: ${message}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function serializePayload(payload) {
|
|
83
|
+
return `${JSON.stringify(payload, null, 2)}
|
|
84
|
+
`;
|
|
85
|
+
}
|
|
86
|
+
function parsePathname(requestUrl) {
|
|
87
|
+
if (!requestUrl) {
|
|
88
|
+
return "/";
|
|
89
|
+
}
|
|
90
|
+
return new URL(requestUrl, "http://localhost").pathname;
|
|
91
|
+
}
|
|
92
|
+
function toRequestPath(base, fileName) {
|
|
93
|
+
const normalizedBase = base === "./" ? "/" : base || "/";
|
|
94
|
+
const withLeadingSlash = normalizedBase.startsWith("/") ? normalizedBase : `/${normalizedBase}`;
|
|
95
|
+
const baseWithoutTrailingSlash = withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
|
|
96
|
+
return `${baseWithoutTrailingSlash}/${fileName}`.replace(/\/{2,}/g, "/");
|
|
97
|
+
}
|
|
98
|
+
function buildVersionPlugin(options = {}) {
|
|
99
|
+
const fileName = normalizeFileName(options.filename ?? DEFAULT_FILENAME);
|
|
100
|
+
const globalName = normalizeGlobalName(
|
|
101
|
+
options.globalName ?? DEFAULT_GLOBAL_NAME
|
|
102
|
+
);
|
|
103
|
+
const injectToHtml = options.injectToHtml ?? true;
|
|
104
|
+
let payload;
|
|
105
|
+
return {
|
|
106
|
+
name: PLUGIN_NAME,
|
|
107
|
+
configResolved(config) {
|
|
108
|
+
const context = {
|
|
109
|
+
command: config.command,
|
|
110
|
+
mode: config.mode
|
|
111
|
+
};
|
|
112
|
+
payload = {
|
|
113
|
+
version: resolveVersionValue(options.version, context),
|
|
114
|
+
production: config.command === "build"
|
|
115
|
+
};
|
|
116
|
+
},
|
|
117
|
+
configureServer(server) {
|
|
118
|
+
const requestPath = toRequestPath(server.config.base, fileName);
|
|
119
|
+
server.middlewares.use((request, response, next) => {
|
|
120
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
121
|
+
next();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (parsePathname(request.url) !== requestPath || !payload) {
|
|
125
|
+
next();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const source = serializePayload(payload);
|
|
129
|
+
response.statusCode = 200;
|
|
130
|
+
response.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
131
|
+
response.setHeader("Cache-Control", "no-cache");
|
|
132
|
+
response.end(request.method === "HEAD" ? void 0 : source);
|
|
133
|
+
});
|
|
134
|
+
},
|
|
135
|
+
transformIndexHtml() {
|
|
136
|
+
if (!injectToHtml || !payload) {
|
|
137
|
+
return void 0;
|
|
138
|
+
}
|
|
139
|
+
const tags = [
|
|
140
|
+
{
|
|
141
|
+
tag: "script",
|
|
142
|
+
injectTo: "head",
|
|
143
|
+
children: `window[${JSON.stringify(globalName)}] = ${JSON.stringify(payload.version)};`
|
|
144
|
+
}
|
|
145
|
+
];
|
|
146
|
+
return tags;
|
|
147
|
+
},
|
|
148
|
+
generateBundle() {
|
|
149
|
+
if (!payload) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
this.emitFile({
|
|
153
|
+
type: "asset",
|
|
154
|
+
fileName,
|
|
155
|
+
source: serializePayload(payload)
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
var index_default = buildVersionPlugin;
|
|
161
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
162
|
+
0 && (module.exports = {
|
|
163
|
+
buildVersionPlugin
|
|
164
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
interface BuildVersionContext {
|
|
4
|
+
command: 'serve' | 'build';
|
|
5
|
+
mode: string;
|
|
6
|
+
}
|
|
7
|
+
interface BuildVersionPayload {
|
|
8
|
+
version: string | number;
|
|
9
|
+
production: boolean;
|
|
10
|
+
}
|
|
11
|
+
interface BuildVersionPluginOptions {
|
|
12
|
+
filename?: string;
|
|
13
|
+
globalName?: string;
|
|
14
|
+
injectToHtml?: boolean;
|
|
15
|
+
version?: string | number | ((ctx: BuildVersionContext) => string | number);
|
|
16
|
+
}
|
|
17
|
+
declare function buildVersionPlugin(options?: BuildVersionPluginOptions): Plugin;
|
|
18
|
+
|
|
19
|
+
export { type BuildVersionContext, type BuildVersionPayload, type BuildVersionPluginOptions, buildVersionPlugin, buildVersionPlugin as default };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
interface BuildVersionContext {
|
|
4
|
+
command: 'serve' | 'build';
|
|
5
|
+
mode: string;
|
|
6
|
+
}
|
|
7
|
+
interface BuildVersionPayload {
|
|
8
|
+
version: string | number;
|
|
9
|
+
production: boolean;
|
|
10
|
+
}
|
|
11
|
+
interface BuildVersionPluginOptions {
|
|
12
|
+
filename?: string;
|
|
13
|
+
globalName?: string;
|
|
14
|
+
injectToHtml?: boolean;
|
|
15
|
+
version?: string | number | ((ctx: BuildVersionContext) => string | number);
|
|
16
|
+
}
|
|
17
|
+
declare function buildVersionPlugin(options?: BuildVersionPluginOptions): Plugin;
|
|
18
|
+
|
|
19
|
+
export { type BuildVersionContext, type BuildVersionPayload, type BuildVersionPluginOptions, buildVersionPlugin, buildVersionPlugin as default };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var PLUGIN_NAME = "vite-plugin-build-version";
|
|
3
|
+
var DEFAULT_FILENAME = "version.json";
|
|
4
|
+
var DEFAULT_GLOBAL_NAME = "__VERSION__";
|
|
5
|
+
function formatBuildTimestamp(date) {
|
|
6
|
+
const pad = (value) => value.toString().padStart(2, "0");
|
|
7
|
+
return [
|
|
8
|
+
date.getFullYear(),
|
|
9
|
+
pad(date.getMonth() + 1),
|
|
10
|
+
pad(date.getDate()),
|
|
11
|
+
pad(date.getHours()),
|
|
12
|
+
pad(date.getMinutes()),
|
|
13
|
+
pad(date.getSeconds())
|
|
14
|
+
].join("");
|
|
15
|
+
}
|
|
16
|
+
function createDefaultVersion(command) {
|
|
17
|
+
if (command === "serve") {
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
|
20
|
+
return formatBuildTimestamp(/* @__PURE__ */ new Date());
|
|
21
|
+
}
|
|
22
|
+
function normalizeFileName(filename) {
|
|
23
|
+
const normalized = filename.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
24
|
+
if (!normalized || normalized.endsWith("/")) {
|
|
25
|
+
throw new Error(`[${PLUGIN_NAME}] "filename" must point to a file path.`);
|
|
26
|
+
}
|
|
27
|
+
return normalized;
|
|
28
|
+
}
|
|
29
|
+
function normalizeGlobalName(globalName) {
|
|
30
|
+
if (!globalName.trim()) {
|
|
31
|
+
throw new Error(`[${PLUGIN_NAME}] "globalName" cannot be empty.`);
|
|
32
|
+
}
|
|
33
|
+
return globalName;
|
|
34
|
+
}
|
|
35
|
+
function ensureVersionValue(value) {
|
|
36
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
throw new Error(
|
|
40
|
+
`[${PLUGIN_NAME}] "version" must resolve to a string or number.`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
function resolveVersionValue(versionOption, context) {
|
|
44
|
+
if (versionOption === void 0) {
|
|
45
|
+
return createDefaultVersion(context.command);
|
|
46
|
+
}
|
|
47
|
+
if (typeof versionOption !== "function") {
|
|
48
|
+
return ensureVersionValue(versionOption);
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
return ensureVersionValue(versionOption(context));
|
|
52
|
+
} catch (error) {
|
|
53
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
54
|
+
throw new Error(`[${PLUGIN_NAME}] Failed to resolve version: ${message}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function serializePayload(payload) {
|
|
58
|
+
return `${JSON.stringify(payload, null, 2)}
|
|
59
|
+
`;
|
|
60
|
+
}
|
|
61
|
+
function parsePathname(requestUrl) {
|
|
62
|
+
if (!requestUrl) {
|
|
63
|
+
return "/";
|
|
64
|
+
}
|
|
65
|
+
return new URL(requestUrl, "http://localhost").pathname;
|
|
66
|
+
}
|
|
67
|
+
function toRequestPath(base, fileName) {
|
|
68
|
+
const normalizedBase = base === "./" ? "/" : base || "/";
|
|
69
|
+
const withLeadingSlash = normalizedBase.startsWith("/") ? normalizedBase : `/${normalizedBase}`;
|
|
70
|
+
const baseWithoutTrailingSlash = withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
|
|
71
|
+
return `${baseWithoutTrailingSlash}/${fileName}`.replace(/\/{2,}/g, "/");
|
|
72
|
+
}
|
|
73
|
+
function buildVersionPlugin(options = {}) {
|
|
74
|
+
const fileName = normalizeFileName(options.filename ?? DEFAULT_FILENAME);
|
|
75
|
+
const globalName = normalizeGlobalName(
|
|
76
|
+
options.globalName ?? DEFAULT_GLOBAL_NAME
|
|
77
|
+
);
|
|
78
|
+
const injectToHtml = options.injectToHtml ?? true;
|
|
79
|
+
let payload;
|
|
80
|
+
return {
|
|
81
|
+
name: PLUGIN_NAME,
|
|
82
|
+
configResolved(config) {
|
|
83
|
+
const context = {
|
|
84
|
+
command: config.command,
|
|
85
|
+
mode: config.mode
|
|
86
|
+
};
|
|
87
|
+
payload = {
|
|
88
|
+
version: resolveVersionValue(options.version, context),
|
|
89
|
+
production: config.command === "build"
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
configureServer(server) {
|
|
93
|
+
const requestPath = toRequestPath(server.config.base, fileName);
|
|
94
|
+
server.middlewares.use((request, response, next) => {
|
|
95
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
96
|
+
next();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (parsePathname(request.url) !== requestPath || !payload) {
|
|
100
|
+
next();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const source = serializePayload(payload);
|
|
104
|
+
response.statusCode = 200;
|
|
105
|
+
response.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
106
|
+
response.setHeader("Cache-Control", "no-cache");
|
|
107
|
+
response.end(request.method === "HEAD" ? void 0 : source);
|
|
108
|
+
});
|
|
109
|
+
},
|
|
110
|
+
transformIndexHtml() {
|
|
111
|
+
if (!injectToHtml || !payload) {
|
|
112
|
+
return void 0;
|
|
113
|
+
}
|
|
114
|
+
const tags = [
|
|
115
|
+
{
|
|
116
|
+
tag: "script",
|
|
117
|
+
injectTo: "head",
|
|
118
|
+
children: `window[${JSON.stringify(globalName)}] = ${JSON.stringify(payload.version)};`
|
|
119
|
+
}
|
|
120
|
+
];
|
|
121
|
+
return tags;
|
|
122
|
+
},
|
|
123
|
+
generateBundle() {
|
|
124
|
+
if (!payload) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
this.emitFile({
|
|
128
|
+
type: "asset",
|
|
129
|
+
fileName,
|
|
130
|
+
source: serializePayload(payload)
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
var index_default = buildVersionPlugin;
|
|
136
|
+
export {
|
|
137
|
+
buildVersionPlugin,
|
|
138
|
+
index_default as default
|
|
139
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vite-plugin-build-version-file",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A Vite plugin that emits version.json and injects version metadata into HTML.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"main": "./dist/index.cjs",
|
|
9
|
+
"module": "./dist/index.mjs",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.mjs",
|
|
15
|
+
"require": "./dist/index.cjs"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md",
|
|
21
|
+
"LICENSE"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsup --config tsup.config.ts",
|
|
25
|
+
"build:fixture": "pnpm build && vite build --config debug/fixture-app/vite.config.ts",
|
|
26
|
+
"dev:fixture": "pnpm build && vite --config debug/fixture-app/vite.config.ts",
|
|
27
|
+
"prepublishOnly": "pnpm build:fixture"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"vite",
|
|
31
|
+
"vite-plugin",
|
|
32
|
+
"vue3",
|
|
33
|
+
"build-version",
|
|
34
|
+
"version-json"
|
|
35
|
+
],
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=18"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"vite": ">=5"
|
|
41
|
+
},
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"packageManager": "pnpm@8.15.9",
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^22.0.0",
|
|
48
|
+
"@vitejs/plugin-vue": "^5.0.0",
|
|
49
|
+
"tsup": "^8.0.0",
|
|
50
|
+
"typescript": "^5.0.0",
|
|
51
|
+
"vite": "^5.0.0",
|
|
52
|
+
"vue": "^3.4.0"
|
|
53
|
+
}
|
|
54
|
+
}
|