@meng-xi/vite-plugin 0.0.8 → 0.0.9

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
@@ -1,540 +1,788 @@
1
- **中文** | [English](./README-en.md)
2
-
3
- <div align="center">
4
- <a href="https://github.com/MengXi-Studio/vite-plugin">
5
- <img alt="梦曦工作室 Logo" width="215" src="https://github.com/MengXi-Studio/vite-plugin/blob/master/packages/docs/src/public/logo.png">
6
- </a>
7
- <br>
8
- <h1>@meng-xi/vite-plugin</h1>
9
- <p>一个为 Vite 提供实用插件的工具包,同时也是一个完整的插件开发框架</p>
10
-
11
- [![license](https://img.shields.io/github/license/MengXi-Studio/vite-plugin.svg)](LICENSE) [![npm](https://img.shields.io/npm/v/@meng-xi/vite-plugin?color=blue)](https://www.npmjs.com/package/@meng-xi/vite-plugin)
12
- ![npm](https://img.shields.io/npm/dt/@meng-xi/vite-plugin?color=green)
13
-
14
- </div>
15
-
16
- ## 特性
17
-
18
- - **开箱即用** - 提供 6 个实用插件,覆盖构建进度展示、文件复制、路由生成、版本管理、图标注入、全局 Loading 状态管理等常见场景
19
- - **插件开发框架** - 导出 BasePlugin、Logger、Validator 等核心组件,快速构建符合规范的自定义 Vite 插件
20
- - **完整生命周期** - 支持初始化、配置解析、销毁等生命周期管理,自动组合钩子逻辑
21
- - **类型安全** - 完整的 TypeScript 类型定义,配置验证器确保参数正确性
22
- - **灵活配置** - 所有插件支持详细配置,满足多样化场景需求
23
- - **安全执行** - 内置错误处理策略(throw / log / ignore),统一异常管理
24
-
25
- ## 文档
26
-
27
- 查看完整文档:[https://mengxi-studio.github.io/vite-plugin/](https://mengxi-studio.github.io/vite-plugin/)
28
-
29
- ## 安装
30
-
31
- ::: code-group
32
-
33
- ```bash [npm]
34
- npm install @meng-xi/vite-plugin -D
35
- ```
36
-
37
- ```bash [yarn]
38
- yarn add @meng-xi/vite-plugin -D
39
- ```
40
-
41
- ```bash [pnpm]
42
- pnpm add @meng-xi/vite-plugin -D
43
- ```
44
-
45
- :::
46
-
47
- ## 快速开始
48
-
49
- ### 使用内置插件
50
-
51
- ```typescript
52
- import { defineConfig } from 'vite'
53
- import { buildProgress, copyFile, generateRouter, generateVersion, injectIco, injectLoading } from '@meng-xi/vite-plugin'
54
-
55
- export default defineConfig({
56
- plugins: [
57
- // 构建进度条
58
- buildProgress(),
59
-
60
- // 复制文件
61
- copyFile({
62
- sourceDir: 'src/assets',
63
- targetDir: 'dist/assets'
64
- }),
65
-
66
- // 生成路由配置(uni-app)
67
- generateRouter({
68
- pagesJsonPath: 'src/pages.json',
69
- outputPath: 'src/router.config.ts'
70
- }),
71
-
72
- // 生成版本号
73
- generateVersion({
74
- format: 'datetime',
75
- outputType: 'both'
76
- }),
77
-
78
- // 注入网站图标
79
- injectIco({
80
- base: '/assets'
81
- }),
82
-
83
- // 注入全局 Loading
84
- injectLoading({
85
- defaultVisible: true,
86
- autoHideOn: 'DOMContentLoaded'
87
- })
88
- ]
89
- })
90
- ```
91
-
92
- ### 访问插件实例
93
-
94
- 所有内置插件返回的对象包含 `pluginInstance` 属性,可访问插件内部状态:
95
-
96
- ```typescript
97
- import type { PluginWithInstance } from '@meng-xi/vite-plugin/factory'
98
- import type { GenerateRouterOptions } from '@meng-xi/vite-plugin'
99
-
100
- const routerPlugin = generateRouter({ watch: true }) as PluginWithInstance<GenerateRouterOptions>
101
-
102
- // 通过 pluginInstance 访问插件内部
103
- console.log(routerPlugin.pluginInstance?.options)
104
- ```
105
-
106
- ### 开发自定义插件
107
-
108
- ```typescript
109
- import { BasePlugin, createPluginFactory } from '@meng-xi/vite-plugin'
110
- import type { BasePluginOptions, PluginWithInstance } from '@meng-xi/vite-plugin/factory'
111
- import type { Plugin } from 'vite'
112
-
113
- interface MyPluginOptions extends BasePluginOptions {
114
- path: string
115
- }
116
-
117
- class MyPlugin extends BasePlugin<MyPluginOptions> {
118
- protected getDefaultOptions() {
119
- return { path: './default' }
120
- }
121
-
122
- protected validateOptions(): void {
123
- this.validator.field('path').required().string().validate()
124
- }
125
-
126
- protected getPluginName(): string {
127
- return 'my-plugin'
128
- }
129
-
130
- protected addPluginHooks(plugin: Plugin): void {
131
- plugin.buildStart = () => {
132
- this.logger.info(`Plugin started with path: ${this.options.path}`)
133
- }
134
- }
135
-
136
- protected destroy(): void {
137
- super.destroy()
138
- // 自定义清理逻辑,如关闭连接、停止监听等
139
- }
140
- }
141
-
142
- export const myPlugin = createPluginFactory(MyPlugin)
143
- ```
144
-
145
- ## 插件开发框架
146
-
147
- ### BasePlugin 核心概念
148
-
149
- `BasePlugin` 是所有插件的基类,提供了完整的生命周期管理和开发规范:
150
-
151
- #### 生命周期
152
-
153
- | 阶段 | 方法 | 说明 |
154
- | -------- | ------------------ | -------------------------------------- |
155
- | 初始化 | `constructor` | 合并配置、初始化日志和验证器 |
156
- | 配置解析 | `onConfigResolved` | Vite 配置解析完成时调用 |
157
- | 钩子注册 | `addPluginHooks` | 注册 Vite 插件钩子 |
158
- | 销毁 | `destroy` | `closeBundle` 时自动调用,用于清理资源 |
159
-
160
- #### 钩子自动组合
161
-
162
- `toPlugin()` 方法会自动组合以下钩子:
163
-
164
- - **configResolved** - 先执行基类的 `onConfigResolved`,再执行子类注册的钩子
165
- - **closeBundle** - 先执行子类注册的钩子,再执行基类的 `destroy`
166
-
167
- > 子类无需手动注册 `closeBundle` 钩子来清理资源,只需重写 `destroy()` 方法即可。
168
-
169
- #### 必须实现的方法
170
-
171
- | 方法 | 说明 |
172
- | ------------------------ | ------------------ |
173
- | `getPluginName()` | 返回插件名称 |
174
- | `addPluginHooks(plugin)` | 添加 Vite 插件钩子 |
175
-
176
- #### 可选重写的方法
177
-
178
- | 方法 | 默认行为 | 说明 |
179
- | -------------------------- | ----------- | ---------------------------------- |
180
- | `getDefaultOptions()` | 返回 `{}` | 提供插件默认配置 |
181
- | `validateOptions()` | 无验证 | 验证配置参数 |
182
- | `getEnforce()` | `undefined` | 插件执行时机(`'pre'` / `'post'`) |
183
- | `onConfigResolved(config)` | 存储配置 | 配置解析完成回调 |
184
- | `destroy()` | 注销日志 | 插件销毁时的清理逻辑 |
185
-
186
- #### 内置属性
187
-
188
- | 属性 | 类型 | 说明 |
189
- | ------------ | ------------------------ | ----------------- |
190
- | `options` | `Required<T>` | 合并后的完整配置 |
191
- | `logger` | `PluginLogger` | 插件日志记录器 |
192
- | `validator` | `Validator<T>` | 配置验证器 |
193
- | `viteConfig` | `ResolvedConfig \| null` | Vite 解析后的配置 |
194
-
195
- #### 错误处理策略
196
-
197
- 通过 `errorStrategy` 配置项控制错误行为:
198
-
199
- - `'throw'`(默认)- 记录错误并抛出异常,中断构建
200
- - `'log'` - 记录错误但不抛出,继续执行
201
- - `'ignore'` - 记录错误但不抛出,继续执行
202
-
203
- 使用 `safeExecute` / `safeExecuteSync` 包裹可能出错的操作:
204
-
205
- ```typescript
206
- const result = await this.safeExecute(async () => {
207
- return await someAsyncOperation()
208
- }, '执行异步操作')
209
- ```
210
-
211
- ### createPluginFactory
212
-
213
- 创建插件工厂函数,支持选项标准化器:
214
-
215
- ```typescript
216
- // 基本使用
217
- const myPlugin = createPluginFactory(MyPlugin)
218
-
219
- // 带标准化器(支持字符串简写配置)
220
- const myPlugin = createPluginFactory(MyPlugin, opt => (typeof opt === 'string' ? { path: opt } : opt))
221
-
222
- // 使用时支持简写
223
- myPlugin('./custom-path')
224
- ```
225
-
226
- ### Logger
227
-
228
- 全局单例日志管理器,为每个插件提供独立的日志控制:
229
-
230
- ```typescript
231
- import { Logger } from '@meng-xi/vite-plugin/logger'
232
-
233
- // 创建日志记录器(通常由 BasePlugin 自动调用)
234
- Logger.create({ name: 'my-plugin', enabled: true })
235
-
236
- // 注销插件日志配置(插件销毁时自动调用)
237
- Logger.unregister('my-plugin')
238
-
239
- // 销毁单例(测试场景使用)
240
- Logger.destroy()
241
- ```
242
-
243
- 日志输出格式:
244
-
245
- ```
246
- ℹ️ [@meng-xi/vite-plugin:my-plugin] Info message
247
- ✅ [@meng-xi/vite-plugin:my-plugin] Success message
248
- ⚠️ [@meng-xi/vite-plugin:my-plugin] Warning message
249
- ❌ [@meng-xi/vite-plugin:my-plugin] Error message
250
- ```
251
-
252
- ## 内置插件
253
-
254
- | 插件 | 说明 |
255
- | --------------- | ----------------------------------------------------- |
256
- | buildProgress | 终端实时构建进度条,支持 bar / spinner / minimal |
257
- | copyFile | 构建完成后复制文件或目录,支持增量复制 |
258
- | generateRouter | 根据 pages.json 自动生成路由配置(uni-app) |
259
- | generateVersion | 自动生成版本号,支持文件输出和全局变量注入 |
260
- | injectIco | 将网站图标链接注入到 HTML 文件 |
261
- | injectLoading | 注入全局 Loading 状态管理,支持请求拦截和白屏 Loading |
262
-
263
- ### buildProgress
264
-
265
- 在终端实时显示 Vite 构建进度条,支持三种显示格式。
266
-
267
- | 选项 | 类型 | 默认值 | 描述 |
268
- | --------------- | ------------------------------------- | ------- | ------------------------------ |
269
- | width | `number` | `30` | 进度条宽度(字符数) |
270
- | format | `'bar'` \| `'spinner'` \| `'minimal'` | `'bar'` | 进度条显示格式 |
271
- | completeChar | `string` | `'█'` | 已完成部分的填充字符 |
272
- | incompleteChar | `string` | `'░'` | 未完成部分的填充字符 |
273
- | clearOnComplete | `boolean` | `true` | 构建完成后是否清除进度条 |
274
- | showModuleName | `boolean` | `true` | 是否显示当前正在处理的模块名称 |
275
- | theme | `ProgressTheme` | - | 自定义颜色主题 |
276
-
277
- **ProgressTheme**
278
-
279
- | 属性 | 类型 | 描述 |
280
- | --------------- | -------------------------- | -------------- |
281
- | completeColor | `(text: string) => string` | 已完成部分颜色 |
282
- | incompleteColor | `(text: string) => string` | 未完成部分颜色 |
283
- | percentageColor | `(text: string) => string` | 百分比数字颜色 |
284
- | phaseColor | `(text: string) => string` | 阶段标签颜色 |
285
- | moduleColor | `(text: string) => string` | 模块名称颜色 |
286
-
287
- ```typescript
288
- // 默认进度条格式
289
- buildProgress()
290
-
291
- // 旋转动画格式
292
- buildProgress({ format: 'spinner' })
293
-
294
- // 精简格式
295
- buildProgress({ format: 'minimal' })
296
-
297
- // 自定义外观
298
- buildProgress({
299
- width: 40,
300
- completeChar: '■',
301
- incompleteChar: '□',
302
- clearOnComplete: false
303
- })
304
- ```
305
-
306
- ### copyFile
307
-
308
- Vite 构建完成后复制文件或目录到指定位置。
309
-
310
- | 选项 | 类型 | 默认值 | 描述 |
311
- | ----------- | --------- | ------ | -------------------- |
312
- | sourceDir | `string` | - | 源目录路径(必填) |
313
- | targetDir | `string` | - | 目标目录路径(必填) |
314
- | overwrite | `boolean` | `true` | 是否覆盖现有文件 |
315
- | recursive | `boolean` | `true` | 是否递归复制子目录 |
316
- | incremental | `boolean` | `true` | 是否启用增量复制 |
317
-
318
- ### generateRouter
319
-
320
- 根据 uni-app 项目的 `pages.json` 自动生成路由配置文件。
321
-
322
- | 选项 | 类型 | 默认值 | 描述 |
323
- | -------------------- | --------------------------------------------------------- | ------------------------ | ----------------------------- |
324
- | pagesJsonPath | `string` | `'src/pages.json'` | pages.json 文件路径 |
325
- | outputPath | `string` | `'src/router.config.ts'` | 输出文件路径 |
326
- | outputFormat | `'ts'` \| `'js'` | `'ts'` | 输出文件格式 |
327
- | nameStrategy | `'path'` \| `'camelCase'` \| `'pascalCase'` \| `'custom'` | `'camelCase'` | 路由名称策略 |
328
- | customNameGenerator | `(path: string) => string` | - | 自定义路由名称生成函数 |
329
- | includeSubPackages | `boolean` | `true` | 是否包含子包路由 |
330
- | watch | `boolean` | `true` | 是否监听变化自动重新生成 |
331
- | metaMapping | `Record<string, string>` | - | 页面 style 字段到 meta 的映射 |
332
- | exportTypes | `boolean` | `true` | 是否导出类型定义 |
333
- | preserveRouteChanges | `boolean` | `true` | 是否保留用户对 routes 的修改 |
334
-
335
- ### generateVersion
336
-
337
- 在 Vite 构建过程中自动生成版本号。
338
-
339
- | 选项 | 类型 | 默认值 | 描述 |
340
- | ------------ | --------------------------------------------------------------------------------- | ------------------- | ------------------------ |
341
- | format | `'timestamp'` \| `'date'` \| `'datetime'` \| `'semver'` \| `'hash'` \| `'custom'` | `'timestamp'` | 版本号格式 |
342
- | customFormat | `string` | - | 自定义格式模板 |
343
- | semverBase | `string` | `'1.0.0'` | 语义化版本基础值 |
344
- | outputType | `'file'` \| `'define'` \| `'both'` | `'file'` | 输出类型 |
345
- | outputFile | `string` | `'version.json'` | 输出文件路径 |
346
- | defineName | `string` | `'__APP_VERSION__'` | 注入的全局变量名 |
347
- | hashLength | `number` | `8` | 哈希长度(1-32) |
348
- | prefix | `string` | - | 版本号前缀 |
349
- | suffix | `string` | - | 版本号后缀 |
350
- | extra | `Record<string, unknown>` | - | 附加信息(仅 JSON 文件) |
351
-
352
- ### injectIco
353
-
354
- Vite 构建过程中将网站图标链接注入到 HTML 文件的 head 中。
355
-
356
- | 选项 | 类型 | 默认值 | 描述 |
357
- | ----------- | -------- | ------ | --------------------------- |
358
- | base | `string` | `'/'` | 图标文件的基础路径 |
359
- | url | `string` | - | 图标的完整 URL |
360
- | link | `string` | - | 自定义完整的 link 标签 HTML |
361
- | icons | `Icon[]` | - | 自定义图标数组 |
362
- | copyOptions | `object` | - | 图标文件复制配置 |
363
-
364
- `Icon` 接口定义:
365
-
366
- | 属性 | 类型 | 必填 | 描述 |
367
- | ----- | -------- | ---- | -------------- |
368
- | rel | `string` | 是 | 图标关系类型 |
369
- | href | `string` | 是 | 图标 URL |
370
- | sizes | `string` | 否 | 图标尺寸 |
371
- | type | `string` | 否 | 图标 MIME 类型 |
372
-
373
- `copyOptions` 接口定义:
374
-
375
- | 属性 | 类型 | 必填 | 默认值 | 描述 |
376
- | --------- | --------- | ---- | ------ | ---------------- |
377
- | sourceDir | `string` | 是 | - | 图标源文件目录 |
378
- | targetDir | `string` | 是 | - | 图标目标目录 |
379
- | overwrite | `boolean` | 否 | `true` | 是否覆盖同名文件 |
380
- | recursive | `boolean` | 否 | `true` | 是否递归复制 |
381
-
382
- ### injectLoading
383
-
384
- 注入全局 Loading 状态管理,支持 XHR/Fetch 请求拦截、白屏 Loading、自定义样式和生命周期回调。
385
-
386
- | 选项 | 类型 | 默认值 | 描述 |
387
- | -------------- | ----------------------------------------------- | ----------------------- | -------------------------------------------- |
388
- | position | `'center'` \| `'top'` \| `'bottom'` | `'center'` | Loading 显示位置 |
389
- | defaultText | `string` | `'加载中...'` | 默认显示文本 |
390
- | spinnerType | `'spinner'` \| `'dots'` \| `'pulse'` \| `'bar'` | `'spinner'` | 旋转图标类型 |
391
- | style | `LoadingStyle` | - | 自定义样式配置 |
392
- | transition | `TransitionConfig` | `{ enabled: true }` | 过渡动画配置 |
393
- | minDisplayTime | `MinDisplayTime` | `{ enabled: true }` | 最小显示时间配置 |
394
- | delayShow | `DelayShow` | `{ enabled: true }` | 延迟显示配置 |
395
- | debounceHide | `DebounceHide` | `{ enabled: false }` | 防抖隐藏配置 |
396
- | autoBind | `'fetch'` \| `'xhr'` \| `'all'` \| `'none'` | `'none'` | 自动绑定请求拦截模式 |
397
- | requestFilter | `RequestFilter` | - | 请求过滤配置 |
398
- | globalName | `string` | `'__LOADING_MANAGER__'` | 注入到浏览器的全局变量名 |
399
- | customTemplate | `string` | - | 自定义 HTML 模板(需含 `data-loading-text`) |
400
- | defaultVisible | `boolean` | `false` | 初始是否可见(白屏 Loading) |
401
- | autoHideOn | `'DOMContentLoaded'` \| `'load'` \| `'manual'` | `'DOMContentLoaded'` | 自动隐藏时机(需 `defaultVisible: true`) |
402
- | callbacks | `LoadingCallbacks` | - | 生命周期回调 |
403
-
404
- **LoadingStyle**
405
-
406
- | 属性 | 类型 | 默认值 | 描述 |
407
- | ------------------ | --------- | ------------------------- | ------------------ |
408
- | overlayColor | `string` | `'rgba(255,255,255,0.7)'` | 遮罩层背景色 |
409
- | spinnerColor | `string` | `'#4361ee'` | 图标颜色 |
410
- | spinnerSize | `string` | `'40px'` | 图标大小 |
411
- | textColor | `string` | `'#333'` | 文本颜色 |
412
- | textSize | `string` | `'14px'` | 文本大小 |
413
- | customClass | `string` | - | 自定义 CSS 类名 |
414
- | customStyle | `string` | - | 自定义内联样式 |
415
- | zIndex | `number` | `9999` | z-index 值 |
416
- | pointerEvents | `boolean` | `false` | 是否允许点击穿透 |
417
- | backdropBlur | `boolean` | `false` | 是否启用背景模糊 |
418
- | backdropBlurAmount | `number` | `4` | 背景模糊程度(px) |
419
-
420
- **TransitionConfig**
421
-
422
- | 属性 | 类型 | 默认值 | 描述 |
423
- | -------- | --------- | ------------ | ---------------- |
424
- | enabled | `boolean` | `true` | 是否启用过渡 |
425
- | duration | `number` | `200` | 过渡持续时间(ms) |
426
- | easing | `string` | `'ease-out'` | 缓动函数 |
427
-
428
- **MinDisplayTime**
429
-
430
- | 属性 | 类型 | 默认值 | 描述 |
431
- | -------- | --------- | ------ | ---------------- |
432
- | enabled | `boolean` | `true` | 是否启用 |
433
- | duration | `number` | `300` | 最小显示时间(ms) |
434
-
435
- **DelayShow**
436
-
437
- | 属性 | 类型 | 默认值 | 描述 |
438
- | -------- | --------- | ------ | ---------------------------------------- |
439
- | enabled | `boolean` | `true` | 是否启用 |
440
- | duration | `number` | `200` | 延迟时间(ms),请求在此时间内完成则不显示 |
441
-
442
- **DebounceHide**
443
-
444
- | 属性 | 类型 | 默认值 | 描述 |
445
- | -------- | --------- | ------- | ---------------- |
446
- | enabled | `boolean` | `false` | 是否启用 |
447
- | duration | `number` | `100` | 防抖等待时间(ms) |
448
-
449
- **RequestFilter**
450
-
451
- | 属性 | 类型 | 描述 |
452
- | ------------------ | ---------- | ----------------------------------------- |
453
- | excludeUrls | `RegExp[]` | 排除的 URL 正则数组 |
454
- | includeUrls | `RegExp[]` | 包含的 URL 正则数组(优先级高于 exclude) |
455
- | excludeMethods | `string[]` | 排除的 HTTP 方法数组 |
456
- | excludeUrlPrefixes | `string[]` | 排除的 URL 前缀数组(前缀匹配,更高效) |
457
-
458
- **LoadingCallbacks**
459
-
460
- 回调以**函数体字符串**形式提供(构建时注入到浏览器端,无法传递函数引用)。
461
-
462
- | 属性 | 类型 | 描述 |
463
- | ------------ | -------- | --------------------------------- |
464
- | onBeforeShow | `string` | 显示前回调,`return false` 可阻止 |
465
- | onShow | `string` | 显示后回调 |
466
- | onBeforeHide | `string` | 隐藏前回调,`return false` 可阻止 |
467
- | onHide | `string` | 隐藏后回调 |
468
- | onDestroy | `string` | 销毁时回调 |
469
-
470
- **LoadingManager API**
471
-
472
- 通过 `window.__LOADING_MANAGER__` 访问:
473
-
474
- | 方法 | 说明 |
475
- | ------------------- | ---------------------------------------- |
476
- | `show(text?)` | 显示 Loading,可传入文本 |
477
- | `hide()` | 隐藏 Loading(受最小显示时间和防抖约束) |
478
- | `forceHide()` | 强制隐藏,忽略最小显示时间和防抖 |
479
- | `destroy()` | 销毁实例,恢复原始拦截器 |
480
- | `updateText(t)` | 更新文本内容 |
481
- | `isVisible()` | 获取当前是否显示 |
482
- | `getPendingCount()` | 获取当前挂起的请求数量 |
483
-
484
- ```typescript
485
- // 白屏 Loading:页面加载即显示,DOM 就绪后自动隐藏
486
- injectLoading({ defaultVisible: true, autoHideOn: 'DOMContentLoaded' })
487
-
488
- // 自动拦截所有请求
489
- injectLoading({ autoBind: 'all' })
490
-
491
- // 自定义样式 + 请求过滤
492
- injectLoading({
493
- style: { overlayColor: 'rgba(0,0,0,0.5)', spinnerColor: '#fff' },
494
- autoBind: 'fetch',
495
- requestFilter: { excludeUrls: [/\/api\/health/] }
496
- })
497
-
498
- // 手动控制
499
- injectLoading()
500
- window.__LOADING_MANAGER__.show('正在保存...')
501
- window.__LOADING_MANAGER__.hide()
502
- ```
503
-
504
- ## 子路径导出
505
-
506
- 支持按需导入模块,减少打包体积:
507
-
508
- ```typescript
509
- // 完整导入
510
- import { buildProgress, copyFile, injectLoading, BasePlugin, Logger } from '@meng-xi/vite-plugin'
511
-
512
- // 按模块导入
513
- import { BasePlugin, createPluginFactory } from '@meng-xi/vite-plugin/factory'
514
- import { Logger } from '@meng-xi/vite-plugin/logger'
515
- import { buildProgress, copyFile, generateRouter, injectLoading } from '@meng-xi/vite-plugin/plugins'
516
- import { Validator, readFileContent, writeFileContent } from '@meng-xi/vite-plugin/common'
517
-
518
- // 类型导入(从子路径按需导入类型定义)
519
- import type { PluginWithInstance, PluginFactory, BasePluginOptions } from '@meng-xi/vite-plugin/factory'
520
- import type { BuildProgressOptions, GenerateVersionOptions, InjectIcoOptions, InjectLoadingOptions, Icon } from '@meng-xi/vite-plugin/plugins'
521
- import type { DateFormatOptions } from '@meng-xi/vite-plugin/common'
522
- ```
523
-
524
- ## 更新日志
525
-
526
- 查看 [GitHub Releases](https://github.com/MengXi-Studio/vite-plugin/releases)
527
-
528
- ## 贡献指南
529
-
530
- 欢迎贡献代码!请按以下步骤操作:
531
-
532
- 1. Fork 本项目
533
- 2. 创建功能分支:`git checkout -b feature/your-feature`
534
- 3. 提交变更:`git commit -m "feat: your feature description"`
535
- 4. 推送分支:`git push origin feature/your-feature`
536
- 5. 创建 Pull Request
537
-
538
- ## License
539
-
540
- [MIT](LICENSE)
1
+ **中文** | [English](./README-en.md)
2
+
3
+ <div align="center">
4
+ <a href="https://github.com/MengXi-Studio/vite-plugin">
5
+ <img alt="梦曦工作室 Logo" width="215" src="https://github.com/MengXi-Studio/vite-plugin/blob/master/packages/docs/src/public/logo.png">
6
+ </a>
7
+ <br>
8
+ <h1>@meng-xi/vite-plugin</h1>
9
+ <p>一个为 Vite 提供实用插件的工具包,同时也是一个完整的插件开发框架</p>
10
+
11
+ [![license](https://img.shields.io/github/license/MengXi-Studio/vite-plugin.svg)](LICENSE) [![npm](https://img.shields.io/npm/v/@meng-xi/vite-plugin?color=blue)](https://www.npmjs.com/package/@meng-xi/vite-plugin)
12
+ ![npm](https://img.shields.io/npm/dt/@meng-xi/vite-plugin?color=green)
13
+
14
+ </div>
15
+
16
+ ## 特性
17
+
18
+ - **开箱即用** - 提供 6 个实用插件,覆盖构建进度展示、文件复制、路由生成、版本管理、图标注入、全局 Loading 状态管理等常见场景
19
+ - **插件开发框架** - 导出 BasePlugin、Logger、Validator 等核心组件,快速构建符合规范的自定义 Vite 插件
20
+ - **完整生命周期** - 支持初始化、配置解析、销毁等生命周期管理,自动组合钩子逻辑
21
+ - **类型安全** - 完整的 TypeScript 类型定义,配置验证器确保参数正确性
22
+ - **灵活配置** - 所有插件支持详细配置,满足多样化场景需求
23
+ - **安全执行** - 内置错误处理策略(throw / log / ignore),统一异常管理
24
+ - **按需导入** - 支持子路径导出,减少打包体积
25
+
26
+ ## 文档
27
+
28
+ 查看完整文档:[https://mengxi-studio.github.io/vite-plugin/](https://mengxi-studio.github.io/vite-plugin/)
29
+
30
+ ## 安装
31
+
32
+ ```bash
33
+ # npm
34
+ npm install @meng-xi/vite-plugin -D
35
+
36
+ # yarn
37
+ yarn add @meng-xi/vite-plugin -D
38
+
39
+ # pnpm
40
+ pnpm add @meng-xi/vite-plugin -D
41
+ ```
42
+
43
+ ## 快速开始
44
+
45
+ ### 使用内置插件
46
+
47
+ ```typescript
48
+ import { defineConfig } from 'vite'
49
+ import { buildProgress, copyFile, generateRouter, generateVersion, injectIco, injectLoading } from '@meng-xi/vite-plugin'
50
+
51
+ export default defineConfig({
52
+ plugins: [
53
+ // 构建进度条
54
+ buildProgress(),
55
+
56
+ // 复制文件
57
+ copyFile({
58
+ sourceDir: 'src/assets',
59
+ targetDir: 'dist/assets'
60
+ }),
61
+
62
+ // 生成路由配置(uni-app)
63
+ generateRouter({
64
+ pagesJsonPath: 'src/pages.json',
65
+ outputPath: 'src/router.config.ts'
66
+ }),
67
+
68
+ // 生成版本号
69
+ generateVersion({
70
+ format: 'datetime',
71
+ outputType: 'both'
72
+ }),
73
+
74
+ // 注入网站图标(支持字符串简写)
75
+ injectIco('/assets'),
76
+
77
+ // 注入全局 Loading
78
+ injectLoading({
79
+ defaultVisible: true,
80
+ autoHideOn: 'DOMContentLoaded'
81
+ })
82
+ ]
83
+ })
84
+ ```
85
+
86
+ ### 访问插件实例
87
+
88
+ 所有内置插件返回的对象包含 `pluginInstance` 属性,可访问插件内部状态:
89
+
90
+ ```typescript
91
+ import type { PluginWithInstance } from '@meng-xi/vite-plugin/factory'
92
+ import type { GenerateRouterOptions } from '@meng-xi/vite-plugin'
93
+
94
+ const routerPlugin = generateRouter({ watch: true }) as PluginWithInstance<GenerateRouterOptions>
95
+
96
+ // 通过 pluginInstance 访问插件内部
97
+ console.log(routerPlugin.pluginInstance?.options)
98
+ ```
99
+
100
+ ### 开发自定义插件
101
+
102
+ ```typescript
103
+ import { BasePlugin, createPluginFactory } from '@meng-xi/vite-plugin'
104
+ import type { BasePluginOptions, PluginWithInstance } from '@meng-xi/vite-plugin/factory'
105
+ import type { Plugin } from 'vite'
106
+
107
+ interface MyPluginOptions extends BasePluginOptions {
108
+ path: string
109
+ }
110
+
111
+ class MyPlugin extends BasePlugin<MyPluginOptions> {
112
+ protected getDefaultOptions() {
113
+ return { path: './default' }
114
+ }
115
+
116
+ protected validateOptions(): void {
117
+ this.validator.field('path').required().string().validate()
118
+ }
119
+
120
+ protected getPluginName(): string {
121
+ return 'my-plugin'
122
+ }
123
+
124
+ protected addPluginHooks(plugin: Plugin): void {
125
+ plugin.buildStart = () => {
126
+ this.logger.info(`Plugin started with path: ${this.options.path}`)
127
+ }
128
+ }
129
+
130
+ protected destroy(): void {
131
+ super.destroy()
132
+ // 自定义清理逻辑,如关闭连接、停止监听等
133
+ }
134
+ }
135
+
136
+ // 基本使用
137
+ export const myPlugin = createPluginFactory(MyPlugin)
138
+
139
+ // 带标准化器(支持字符串简写配置)
140
+ export const myPluginWithNormalizer = createPluginFactory(MyPlugin, opt => (typeof opt === 'string' ? { path: opt } : opt))
141
+ // 使用时支持简写:myPluginWithNormalizer('./custom-path')
142
+ ```
143
+
144
+ ## 插件开发框架
145
+
146
+ ### BasePlugin 核心概念
147
+
148
+ `BasePlugin` 是所有插件的基类,提供了完整的生命周期管理和开发规范:
149
+
150
+ #### 生命周期
151
+
152
+ | 阶段 | 方法 | 说明 |
153
+ | -------- | ------------------ | -------------------------------------- |
154
+ | 初始化 | `constructor` | 合并配置、初始化日志和验证器 |
155
+ | 配置解析 | `onConfigResolved` | Vite 配置解析完成时调用 |
156
+ | 钩子注册 | `addPluginHooks` | 注册 Vite 插件钩子 |
157
+ | 销毁 | `destroy` | `closeBundle` 时自动调用,用于清理资源 |
158
+
159
+ #### 钩子自动组合
160
+
161
+ `toPlugin()` 方法会自动组合以下钩子:
162
+
163
+ - **configResolved** - 先执行基类的 `onConfigResolved`,再执行子类注册的钩子
164
+ - **closeBundle** - 先执行子类注册的钩子,再执行基类的 `destroy`
165
+
166
+ > 子类无需手动注册 `closeBundle` 钩子来清理资源,只需重写 `destroy()` 方法即可。
167
+
168
+ #### 必须实现的方法
169
+
170
+ | 方法 | 说明 |
171
+ | ------------------------ | ------------------ |
172
+ | `getPluginName()` | 返回插件名称 |
173
+ | `addPluginHooks(plugin)` | 添加 Vite 插件钩子 |
174
+
175
+ #### 可选重写的方法
176
+
177
+ | 方法 | 默认行为 | 说明 |
178
+ | -------------------------- | ----------- | ---------------------------------- |
179
+ | `getDefaultOptions()` | 返回 `{}` | 提供插件默认配置 |
180
+ | `validateOptions()` | 无验证 | 验证配置参数 |
181
+ | `getEnforce()` | `undefined` | 插件执行时机(`'pre'` / `'post'`) |
182
+ | `onConfigResolved(config)` | 存储配置 | 配置解析完成回调 |
183
+ | `destroy()` | 注销日志 | 插件销毁时的清理逻辑 |
184
+
185
+ #### 内置属性
186
+
187
+ | 属性 | 类型 | 说明 |
188
+ | ------------ | ------------------------ | ----------------- |
189
+ | `options` | `Required<T>` | 合并后的完整配置 |
190
+ | `logger` | `PluginLogger` | 插件日志记录器 |
191
+ | `validator` | `Validator<T>` | 配置验证器 |
192
+ | `viteConfig` | `ResolvedConfig \| null` | Vite 解析后的配置 |
193
+
194
+ #### 错误处理策略
195
+
196
+ 通过 `errorStrategy` 配置项控制错误行为:
197
+
198
+ - `'throw'`(默认)- 记录错误并抛出异常,中断构建
199
+ - `'log'` - 记录错误但不抛出,继续执行
200
+ - `'ignore'` - 记录错误但不抛出,继续执行
201
+
202
+ 使用 `safeExecute` / `safeExecuteSync` 包裹可能出错的操作:
203
+
204
+ ```typescript
205
+ // 异步安全执行
206
+ const result = await this.safeExecute(async () => {
207
+ return await someAsyncOperation()
208
+ }, '执行异步操作')
209
+
210
+ // 同步安全执行
211
+ const value = this.safeExecuteSync(() => {
212
+ return someSyncOperation()
213
+ }, '执行同步操作')
214
+ ```
215
+
216
+ ### createPluginFactory
217
+
218
+ 创建插件工厂函数,支持选项标准化器:
219
+
220
+ ```typescript
221
+ // 基本使用
222
+ const myPlugin = createPluginFactory(MyPlugin)
223
+
224
+ // 带标准化器(支持字符串简写配置)
225
+ const myPlugin = createPluginFactory(MyPlugin, opt => (typeof opt === 'string' ? { path: opt } : opt))
226
+
227
+ // 使用时支持简写
228
+ myPlugin('./custom-path')
229
+ ```
230
+
231
+ ### Validator
232
+
233
+ 流畅的配置验证器,支持链式调用:
234
+
235
+ ```typescript
236
+ import { Validator } from '@meng-xi/vite-plugin/common'
237
+
238
+ const validator = new Validator(options)
239
+ validator
240
+ .field('sourceDir')
241
+ .required()
242
+ .string()
243
+ .field('targetDir')
244
+ .required()
245
+ .string()
246
+ .field('overwrite')
247
+ .boolean()
248
+ .default(true)
249
+ .field('port')
250
+ .number()
251
+ .field('list')
252
+ .array()
253
+ .field('config')
254
+ .object()
255
+ .field('name')
256
+ .custom(val => val.length > 0, 'name 不能为空')
257
+ .validate()
258
+ ```
259
+
260
+ | 方法 | 说明 |
261
+ | ------------ | -------------------------------------------------- |
262
+ | `field()` | 指定要验证的字段 |
263
+ | `required()` | 标记字段为必填 |
264
+ | `string()` | 验证字段值是否为字符串类型 |
265
+ | `boolean()` | 验证字段值是否为布尔类型 |
266
+ | `number()` | 验证字段值是否为数字类型 |
267
+ | `array()` | 验证字段值是否为数组类型 |
268
+ | `object()` | 验证字段值是否为对象类型 |
269
+ | `default()` | 为字段设置默认值(仅当值为 undefined/null 时生效) |
270
+ | `custom()` | 使用自定义函数验证字段值 |
271
+ | `validate()` | 执行验证,失败时抛出错误 |
272
+
273
+ ### Logger
274
+
275
+ 全局单例日志管理器,为每个插件提供独立的日志控制:
276
+
277
+ ```typescript
278
+ import { Logger } from '@meng-xi/vite-plugin/logger'
279
+
280
+ // 创建日志记录器(通常由 BasePlugin 自动调用)
281
+ Logger.create({ name: 'my-plugin', enabled: true })
282
+
283
+ // 注销插件日志配置(插件销毁时自动调用)
284
+ Logger.unregister('my-plugin')
285
+
286
+ // 销毁单例(测试场景使用)
287
+ Logger.destroy()
288
+ ```
289
+
290
+ 日志输出格式:
291
+
292
+ ```
293
+ ℹ️ [@meng-xi/vite-plugin:my-plugin] Info message
294
+ [@meng-xi/vite-plugin:my-plugin] Success message
295
+ ⚠️ [@meng-xi/vite-plugin:my-plugin] Warning message
296
+ ❌ [@meng-xi/vite-plugin:my-plugin] Error message
297
+ ```
298
+
299
+ ### 通用工具函数
300
+
301
+ 通过 `@meng-xi/vite-plugin/common` 导出,可在自定义插件中复用:
302
+
303
+ ```typescript
304
+ import { deepMerge, formatDate, parseTemplate, toCamelCase, toPascalCase, stripJsonComments, generateRandomHash, Validator } from '@meng-xi/vite-plugin/common'
305
+ import { readFileContent, writeFileContent, fileExists, copySourceToTarget } from '@meng-xi/vite-plugin/common'
306
+ ```
307
+
308
+ | 函数 | 说明 |
309
+ | ---------------------- | -------------------------------------------------- |
310
+ | `deepMerge()` | 深度合并对象(undefined 不覆盖,数组直接覆盖) |
311
+ | `formatDate()` | 格式化日期,支持 `{YYYY}`, `{MM}`, `{DD}` 等占位符 |
312
+ | `parseTemplate()` | 解析模板字符串,替换占位符 |
313
+ | `toCamelCase()` | 转换为驼峰命名(camelCase) |
314
+ | `toPascalCase()` | 转换为帕斯卡命名(PascalCase) |
315
+ | `stripJsonComments()` | 移除 JSON 字符串中的注释 |
316
+ | `generateRandomHash()` | 生成随机哈希字符串(1-64 位) |
317
+ | `readFileContent()` | 异步读取文件内容 |
318
+ | `writeFileContent()` | 异步写入文件内容 |
319
+ | `fileExists()` | 异步检查文件是否存在 |
320
+ | `copySourceToTarget()` | 复制文件或目录,支持增量复制和并发控制 |
321
+
322
+ ## 内置插件
323
+
324
+ | 插件 | 说明 |
325
+ | --------------- | ----------------------------------------------------- |
326
+ | buildProgress | 终端实时构建进度条,支持 bar / spinner / minimal |
327
+ | copyFile | 构建完成后复制文件或目录,支持增量复制 |
328
+ | generateRouter | 根据 pages.json 自动生成路由配置(uni-app) |
329
+ | generateVersion | 自动生成版本号,支持文件输出和全局变量注入 |
330
+ | injectIco | 将网站图标链接注入到 HTML 文件,支持字符串简写配置 |
331
+ | injectLoading | 注入全局 Loading 状态管理,支持请求拦截和白屏 Loading |
332
+
333
+ ### buildProgress
334
+
335
+ 在终端实时显示 Vite 构建进度条,支持三种显示格式。
336
+
337
+ **进度计算逻辑:**
338
+
339
+ 1. config 阶段(5%)→ resolve 阶段(10%)→ transform 阶段(15%-85%,按模块转换比例)→ bundle 阶段(+10%)→ write 阶段(+5%)→ 完成(100%)
340
+ 2. TTY 终端环境(如 CI/CD)自动降级为日志输出模式
341
+
342
+ | 选项 | 类型 | 默认值 | 描述 |
343
+ | --------------- | ------------------------------------- | ------- | ------------------------------ |
344
+ | width | `number` | `30` | 进度条宽度(字符数) |
345
+ | format | `'bar'` \| `'spinner'` \| `'minimal'` | `'bar'` | 进度条显示格式 |
346
+ | completeChar | `string` | `''` | 已完成部分的填充字符 |
347
+ | incompleteChar | `string` | `'░'` | 未完成部分的填充字符 |
348
+ | clearOnComplete | `boolean` | `true` | 构建完成后是否清除进度条 |
349
+ | showModuleName | `boolean` | `true` | 是否显示当前正在处理的模块名称 |
350
+ | theme | `ProgressTheme` | - | 自定义颜色主题 |
351
+
352
+ **ProgressTheme**
353
+
354
+ | 属性 | 类型 | 描述 |
355
+ | --------------- | -------------------------- | -------------- |
356
+ | completeColor | `(text: string) => string` | 已完成部分颜色 |
357
+ | incompleteColor | `(text: string) => string` | 未完成部分颜色 |
358
+ | percentageColor | `(text: string) => string` | 百分比数字颜色 |
359
+ | phaseColor | `(text: string) => string` | 阶段标签颜色 |
360
+ | moduleColor | `(text: string) => string` | 模块名称颜色 |
361
+
362
+ ```typescript
363
+ // 默认进度条格式
364
+ buildProgress()
365
+
366
+ // 旋转动画格式
367
+ buildProgress({ format: 'spinner' })
368
+
369
+ // 精简格式
370
+ buildProgress({ format: 'minimal' })
371
+
372
+ // 自定义外观
373
+ buildProgress({
374
+ width: 40,
375
+ completeChar: '■',
376
+ incompleteChar: '□',
377
+ clearOnComplete: false
378
+ })
379
+
380
+ // 自定义颜色主题
381
+ buildProgress({
382
+ theme: {
383
+ completeColor: t => `\x1b[32m${t}\x1b[39m`,
384
+ incompleteColor: t => `\x1b[90m${t}\x1b[39m`,
385
+ percentageColor: t => `\x1b[1m${t}\x1b[22m`,
386
+ phaseColor: t => `\x1b[36m${t}\x1b[39m`,
387
+ moduleColor: t => `\x1b[90m${t}\x1b[39m`
388
+ }
389
+ })
390
+ ```
391
+
392
+ ### copyFile
393
+
394
+ Vite 构建完成后复制文件或目录到指定位置,执行时机为 `enforce: 'post'`。
395
+
396
+ | 选项 | 类型 | 默认值 | 描述 |
397
+ | ----------- | --------- | ------ | -------------------- |
398
+ | sourceDir | `string` | - | 源目录路径(必填) |
399
+ | targetDir | `string` | - | 目标目录路径(必填) |
400
+ | overwrite | `boolean` | `true` | 是否覆盖现有文件 |
401
+ | recursive | `boolean` | `true` | 是否递归复制子目录 |
402
+ | incremental | `boolean` | `true` | 是否启用增量复制 |
403
+
404
+ ```typescript
405
+ // 基本使用
406
+ copyFile({
407
+ sourceDir: 'src/assets',
408
+ targetDir: 'dist/assets'
409
+ })
410
+
411
+ // 禁用覆盖和增量复制
412
+ copyFile({
413
+ sourceDir: 'src/static',
414
+ targetDir: 'dist/static',
415
+ overwrite: false,
416
+ incremental: false
417
+ })
418
+ ```
419
+
420
+ ### generateRouter
421
+
422
+ 根据 uni-app 项目的 `pages.json` 自动生成路由配置文件。
423
+
424
+ | 选项 | 类型 | 默认值 | 描述 |
425
+ | -------------------- | --------------------------------------------------------- | ------------------------ | ----------------------------- |
426
+ | pagesJsonPath | `string` | `'src/pages.json'` | pages.json 文件路径 |
427
+ | outputPath | `string` | `'src/router.config.ts'` | 输出文件路径 |
428
+ | outputFormat | `'ts'` \| `'js'` | `'ts'` | 输出文件格式 |
429
+ | nameStrategy | `'path'` \| `'camelCase'` \| `'pascalCase'` \| `'custom'` | `'camelCase'` | 路由名称策略 |
430
+ | customNameGenerator | `(path: string) => string` | - | 自定义路由名称生成函数 |
431
+ | includeSubPackages | `boolean` | `true` | 是否包含子包路由 |
432
+ | watch | `boolean` | `true` | 是否监听变化自动重新生成 |
433
+ | metaMapping | `Record<string, string>` | - | 页面 style 字段到 meta 的映射 |
434
+ | exportTypes | `boolean` | `true` | 是否导出类型定义 |
435
+ | preserveRouteChanges | `boolean` | `true` | 是否保留用户对 routes 的修改 |
436
+
437
+ > 默认 `metaMapping` `{ navigationBarTitleText: 'title', requireAuth: 'requireAuth' }`,自动将页面样式字段映射到路由元信息。当 `nameStrategy` 为 `'custom'` 时,必须提供 `customNameGenerator`。
438
+
439
+ ```typescript
440
+ // 基本使用
441
+ generateRouter()
442
+
443
+ // 自定义 pages.json 路径
444
+ generateRouter({ pagesJsonPath: 'pages.json' })
445
+
446
+ // 输出 JavaScript 文件
447
+ generateRouter({ outputFormat: 'js', outputPath: 'src/router.config.js' })
448
+
449
+ // 使用帕斯卡命名策略
450
+ generateRouter({ nameStrategy: 'pascalCase' })
451
+
452
+ // 自定义路由名称生成
453
+ generateRouter({
454
+ nameStrategy: 'custom',
455
+ customNameGenerator: path => `route_${path.replace(/\//g, '_')}`
456
+ })
457
+
458
+ // 自定义元信息映射
459
+ generateRouter({
460
+ metaMapping: {
461
+ navigationBarTitleText: 'title',
462
+ requireAuth: 'requireAuth',
463
+ customField: 'custom'
464
+ }
465
+ })
466
+ ```
467
+
468
+ ### generateVersion
469
+
470
+ Vite 构建过程中自动生成版本号。
471
+
472
+ | 选项 | 类型 | 默认值 | 描述 |
473
+ | ------------ | --------------------------------------------------------------------------------- | ------------------- | ------------------------ |
474
+ | format | `'timestamp'` \| `'date'` \| `'datetime'` \| `'semver'` \| `'hash'` \| `'custom'` | `'timestamp'` | 版本号格式 |
475
+ | customFormat | `string` | - | 自定义格式模板 |
476
+ | semverBase | `string` | `'1.0.0'` | 语义化版本基础值 |
477
+ | outputType | `'file'` \| `'define'` \| `'both'` | `'file'` | 输出类型 |
478
+ | outputFile | `string` | `'version.json'` | 输出文件路径 |
479
+ | defineName | `string` | `'__APP_VERSION__'` | 注入的全局变量名 |
480
+ | hashLength | `number` | `8` | 哈希长度(1-32) |
481
+ | prefix | `string` | - | 版本号前缀 |
482
+ | suffix | `string` | - | 版本号后缀 |
483
+ | extra | `Record<string, unknown>` | - | 附加信息(仅 JSON 文件) |
484
+
485
+ **customFormat 支持的占位符:**
486
+
487
+ | 占位符 | 说明 | 示例 |
488
+ | ------------- | --------------------------- | --------------- |
489
+ | `{YYYY}` | 四位年份 | `2026` |
490
+ | `{YY}` | 两位年份 | `26` |
491
+ | `{MM}` | 两位月份 | `05` |
492
+ | `{DD}` | 两位日期 | `22` |
493
+ | `{HH}` | 两位小时(24h) | `15` |
494
+ | `{mm}` | 两位分钟 | `30` |
495
+ | `{ss}` | 两位秒数 | `00` |
496
+ | `{SSS}` | 三位毫秒 | `123` |
497
+ | `{timestamp}` | 时间戳(毫秒) | `1779464601000` |
498
+ | `{hash}` | 随机哈希 | `a1b2c3d4` |
499
+ | `{major}` | 主版本号(需 semverBase) | `1` |
500
+ | `{minor}` | 次版本号(需 semverBase) | `0` |
501
+ | `{patch}` | 补丁版本号(需 semverBase) | `0` |
502
+
503
+ > 当 `format` 为 `'custom'` 时,必须提供 `customFormat`。当 `outputType` 为 `'define'` 或 `'both'` 时,同时注入 `{defineName}_INFO` 全局变量,包含版本号、构建时间、时间戳等完整信息。
504
+
505
+ ```typescript
506
+ // 时间戳格式(默认)
507
+ generateVersion()
508
+
509
+ // 日期格式
510
+ generateVersion({ format: 'date' })
511
+
512
+ // 语义化版本格式
513
+ generateVersion({ format: 'semver', semverBase: '2.0.0', prefix: 'v' })
514
+
515
+ // 自定义格式
516
+ generateVersion({
517
+ format: 'custom',
518
+ customFormat: '{YYYY}.{MM}.{DD}-{hash}',
519
+ hashLength: 6
520
+ })
521
+
522
+ // 注入到代码中
523
+ generateVersion({ outputType: 'define', defineName: '__VERSION__' })
524
+
525
+ // 同时输出文件和注入代码
526
+ generateVersion({
527
+ outputType: 'both',
528
+ outputFile: 'build-info.json',
529
+ defineName: '__BUILD_VERSION__',
530
+ extra: { environment: 'production' }
531
+ })
532
+ ```
533
+
534
+ ### injectIco
535
+
536
+ Vite 构建过程中将网站图标链接注入到 HTML 文件的 head 中。支持字符串简写配置。
537
+
538
+ | 选项 | 类型 | 默认值 | 描述 |
539
+ | ----------- | -------- | ------ | --------------------------- |
540
+ | base | `string` | `'/'` | 图标文件的基础路径 |
541
+ | url | `string` | - | 图标的完整 URL |
542
+ | link | `string` | - | 自定义完整的 link 标签 HTML |
543
+ | icons | `Icon[]` | - | 自定义图标数组 |
544
+ | copyOptions | `object` | - | 图标文件复制配置 |
545
+
546
+ > 优先级:`link` > `url` > `base`。当提供 `link` 时,直接注入自定义 HTML;当提供 `url` 时,使用完整 URL;否则使用 `base + '/favicon.ico'`。
547
+
548
+ `Icon` 接口定义:
549
+
550
+ | 属性 | 类型 | 必填 | 描述 |
551
+ | ----- | -------- | ---- | -------------- |
552
+ | rel | `string` | 是 | 图标关系类型 |
553
+ | href | `string` | 是 | 图标 URL |
554
+ | sizes | `string` | 否 | 图标尺寸 |
555
+ | type | `string` | 否 | 图标 MIME 类型 |
556
+
557
+ `copyOptions` 接口定义:
558
+
559
+ | 属性 | 类型 | 必填 | 默认值 | 描述 |
560
+ | --------- | --------- | ---- | ------ | ---------------- |
561
+ | sourceDir | `string` | 是 | - | 图标源文件目录 |
562
+ | targetDir | `string` | 是 | - | 图标目标目录 |
563
+ | overwrite | `boolean` | 否 | `true` | 是否覆盖同名文件 |
564
+ | recursive | `boolean` | 否 | `true` | 是否递归复制 |
565
+
566
+ ```typescript
567
+ // 使用默认配置
568
+ injectIco()
569
+
570
+ // 字符串简写(设置 base 路径)
571
+ injectIco('/assets')
572
+
573
+ // 自定义图标数组
574
+ injectIco({
575
+ base: '/assets',
576
+ icons: [
577
+ { rel: 'icon', href: '/favicon.svg', type: 'image/svg+xml' },
578
+ { rel: 'icon', href: '/favicon-32x32.png', sizes: '32x32', type: 'image/png' },
579
+ { rel: 'apple-touch-icon', href: '/apple-touch-icon.png', sizes: '180x180' }
580
+ ]
581
+ })
582
+
583
+ // 自定义完整 link 标签
584
+ injectIco({
585
+ link: '<link rel="icon" href="/favicon.svg" type="image/svg+xml" />'
586
+ })
587
+
588
+ // 带文件复制
589
+ injectIco({
590
+ base: '/assets',
591
+ copyOptions: {
592
+ sourceDir: 'src/assets/icons',
593
+ targetDir: 'dist/assets/icons'
594
+ }
595
+ })
596
+ ```
597
+
598
+ ### injectLoading
599
+
600
+ 注入全局 Loading 状态管理,支持 XHR/Fetch 请求拦截、白屏 Loading、自定义样式和生命周期回调。
601
+
602
+ **注入策略:**
603
+
604
+ - `defaultVisible: false`(默认):所有代码(CSS + HTML + JS)通过 JS 动态注入到 `</body>` 前
605
+ - `defaultVisible: true`:CSS + HTML 以静态标签注入到 `</head>` 前(白屏即可见),JS 注入到 `</body>` 前
606
+
607
+ | 选项 | 类型 | 默认值 | 描述 |
608
+ | -------------- | ----------------------------------------------- | ----------------------- | -------------------------------------------- |
609
+ | position | `'center'` \| `'top'` \| `'bottom'` | `'center'` | Loading 显示位置 |
610
+ | defaultText | `string` | `'加载中...'` | 默认显示文本 |
611
+ | spinnerType | `'spinner'` \| `'dots'` \| `'pulse'` \| `'bar'` | `'spinner'` | 旋转图标类型 |
612
+ | style | `LoadingStyle` | - | 自定义样式配置 |
613
+ | transition | `TransitionConfig` | `{ enabled: true }` | 过渡动画配置 |
614
+ | minDisplayTime | `MinDisplayTime` | `{ enabled: true }` | 最小显示时间配置 |
615
+ | delayShow | `DelayShow` | `{ enabled: true }` | 延迟显示配置 |
616
+ | debounceHide | `DebounceHide` | `{ enabled: false }` | 防抖隐藏配置 |
617
+ | autoBind | `'fetch'` \| `'xhr'` \| `'all'` \| `'none'` | `'none'` | 自动绑定请求拦截模式 |
618
+ | requestFilter | `RequestFilter` | - | 请求过滤配置 |
619
+ | globalName | `string` | `'__LOADING_MANAGER__'` | 注入到浏览器的全局变量名 |
620
+ | customTemplate | `string` | - | 自定义 HTML 模板(需含 `data-loading-text`) |
621
+ | defaultVisible | `boolean` | `false` | 初始是否可见(白屏 Loading) |
622
+ | autoHideOn | `'DOMContentLoaded'` \| `'load'` \| `'manual'` | `'DOMContentLoaded'` | 自动隐藏时机(需 `defaultVisible: true`) |
623
+ | callbacks | `LoadingCallbacks` | - | 生命周期回调 |
624
+
625
+ **LoadingStyle**
626
+
627
+ | 属性 | 类型 | 默认值 | 描述 |
628
+ | ------------------ | --------- | ------------------------- | ---------------------- |
629
+ | overlayColor | `string` | `'rgba(255,255,255,0.7)'` | 遮罩层背景色 |
630
+ | spinnerColor | `string` | `'#4361ee'` | 图标颜色 |
631
+ | spinnerSize | `string` | `'40px'` | 图标大小 |
632
+ | textColor | `string` | `'#333'` | 文本颜色 |
633
+ | textSize | `string` | `'14px'` | 文本大小 |
634
+ | customClass | `string` | - | 自定义 CSS 类名 |
635
+ | customStyle | `string` | - | 自定义内联样式 |
636
+ | zIndex | `number` | `9999` | z-index 值 |
637
+ | pointerEvents | `boolean` | `true` | 是否启用遮罩层指针事件 |
638
+ | backdropBlur | `boolean` | `false` | 是否启用背景模糊 |
639
+ | backdropBlurAmount | `number` | `4` | 背景模糊程度(px) |
640
+
641
+ **TransitionConfig**
642
+
643
+ | 属性 | 类型 | 默认值 | 描述 |
644
+ | -------- | --------- | ------------ | ---------------- |
645
+ | enabled | `boolean` | `true` | 是否启用过渡 |
646
+ | duration | `number` | `200` | 过渡持续时间(ms) |
647
+ | easing | `string` | `'ease-out'` | 缓动函数 |
648
+
649
+ **MinDisplayTime**
650
+
651
+ | 属性 | 类型 | 默认值 | 描述 |
652
+ | -------- | --------- | ------ | ------------------------------------- |
653
+ | enabled | `boolean` | `true` | 是否启用 |
654
+ | duration | `number` | `300` | 最小显示时间(ms),确保 Loading 不闪烁 |
655
+
656
+ **DelayShow**
657
+
658
+ | 属性 | 类型 | 默认值 | 描述 |
659
+ | -------- | --------- | ------ | ---------------------------------------- |
660
+ | enabled | `boolean` | `true` | 是否启用 |
661
+ | duration | `number` | `200` | 延迟时间(ms),请求在此时间内完成则不显示 |
662
+
663
+ **DebounceHide**
664
+
665
+ | 属性 | 类型 | 默认值 | 描述 |
666
+ | -------- | --------- | ------- | ---------------- |
667
+ | enabled | `boolean` | `false` | 是否启用 |
668
+ | duration | `number` | `100` | 防抖等待时间(ms) |
669
+
670
+ **RequestFilter**
671
+
672
+ | 属性 | 类型 | 描述 |
673
+ | ------------------ | ---------- | ----------------------------------------- |
674
+ | excludeUrls | `RegExp[]` | 排除的 URL 正则数组 |
675
+ | includeUrls | `RegExp[]` | 包含的 URL 正则数组(优先级高于 exclude) |
676
+ | excludeMethods | `string[]` | 排除的 HTTP 方法数组 |
677
+ | excludeUrlPrefixes | `string[]` | 排除的 URL 前缀数组(前缀匹配,更高效) |
678
+
679
+ **LoadingCallbacks**
680
+
681
+ 回调以**函数体字符串**形式提供(构建时注入到浏览器端,无法传递函数引用)。
682
+
683
+ | 属性 | 类型 | 描述 |
684
+ | ------------ | -------- | --------------------------------- |
685
+ | onBeforeShow | `string` | 显示前回调,`return false` 可阻止 |
686
+ | onShow | `string` | 显示后回调 |
687
+ | onBeforeHide | `string` | 隐藏前回调,`return false` 可阻止 |
688
+ | onHide | `string` | 隐藏后回调 |
689
+ | onDestroy | `string` | 销毁时回调 |
690
+
691
+ **LoadingManager API**
692
+
693
+ 通过 `window.__LOADING_MANAGER__` 访问:
694
+
695
+ | 方法 | 说明 |
696
+ | -------------------------- | ------------------------------------------ |
697
+ | `show(text?)` | 显示 Loading,可传入文本 |
698
+ | `hide()` | 隐藏 Loading(受最小显示时间和防抖约束) |
699
+ | `forceHide()` | 强制隐藏,忽略最小显示时间和防抖 |
700
+ | `toggle(text?)` | 切换 Loading 显示/隐藏状态 |
701
+ | `updateText(text)` | 更新文本内容 |
702
+ | `isVisible()` | 获取当前是否显示 |
703
+ | `isPointerEventsEnabled()` | 获取当前是否启用了指针事件 |
704
+ | `enablePointerEvents()` | 启用遮罩层指针事件,拦截所有点击和滚动操作 |
705
+ | `disablePointerEvents()` | 禁用遮罩层指针事件,允许交互穿透 |
706
+ | `togglePointerEvents()` | 切换遮罩层指针事件状态 |
707
+ | `getPendingCount()` | 获取当前挂起的请求数量 |
708
+ | `destroy()` | 销毁实例,清理 DOM 并恢复原始拦截器 |
709
+
710
+ ```typescript
711
+ // 白屏 Loading:页面加载即显示,DOM 就绪后自动隐藏
712
+ injectLoading({ defaultVisible: true, autoHideOn: 'DOMContentLoaded' })
713
+
714
+ // 白屏 Loading:所有资源加载完成后隐藏
715
+ injectLoading({ defaultVisible: true, autoHideOn: 'load' })
716
+
717
+ // Vue/React SPA:白屏即显示,框架渲染完成后手动隐藏
718
+ injectLoading({ defaultVisible: true, autoHideOn: 'manual' })
719
+ // 在应用入口处:window.__LOADING_MANAGER__.hide()
720
+
721
+ // 自动拦截所有请求
722
+ injectLoading({ autoBind: 'all' })
723
+
724
+ // 自定义样式 + 请求过滤
725
+ injectLoading({
726
+ style: { overlayColor: 'rgba(0,0,0,0.5)', spinnerColor: '#fff', backdropBlur: true },
727
+ autoBind: 'fetch',
728
+ requestFilter: { excludeUrls: [/\/api\/health/], excludeUrlPrefixes: ['http://localhost'] }
729
+ })
730
+
731
+ // 防抖隐藏(避免快速闪烁)
732
+ injectLoading({ debounceHide: { enabled: true, duration: 100 } })
733
+
734
+ // 生命周期回调
735
+ injectLoading({
736
+ callbacks: {
737
+ onBeforeShow: 'if (shouldSkip) return false;',
738
+ onShow: 'console.log("loading shown")',
739
+ onBeforeHide: 'if (shouldKeepVisible) return false;',
740
+ onHide: 'console.log("loading hidden")'
741
+ }
742
+ })
743
+
744
+ // 手动控制
745
+ injectLoading()
746
+ window.__LOADING_MANAGER__.show('正在保存...')
747
+ window.__LOADING_MANAGER__.hide()
748
+ window.__LOADING_MANAGER__.toggle()
749
+ window.__LOADING_MANAGER__.disablePointerEvents()
750
+ ```
751
+
752
+ ## 子路径导出
753
+
754
+ 支持按需导入模块,减少打包体积:
755
+
756
+ ```typescript
757
+ // 完整导入
758
+ import { buildProgress, copyFile, injectLoading, BasePlugin, Logger } from '@meng-xi/vite-plugin'
759
+
760
+ // 按模块导入
761
+ import { BasePlugin, createPluginFactory } from '@meng-xi/vite-plugin/factory'
762
+ import { Logger } from '@meng-xi/vite-plugin/logger'
763
+ import { buildProgress, copyFile, generateRouter, injectLoading } from '@meng-xi/vite-plugin/plugins'
764
+ import { Validator, readFileContent, writeFileContent } from '@meng-xi/vite-plugin/common'
765
+
766
+ // 类型导入(从子路径按需导入类型定义)
767
+ import type { PluginWithInstance, PluginFactory, BasePluginOptions } from '@meng-xi/vite-plugin/factory'
768
+ import type { BuildProgressOptions, GenerateVersionOptions, InjectIcoOptions, InjectLoadingOptions, Icon } from '@meng-xi/vite-plugin/plugins'
769
+ import type { DateFormatOptions } from '@meng-xi/vite-plugin/common'
770
+ ```
771
+
772
+ ## 更新日志
773
+
774
+ 查看 [GitHub Releases](https://github.com/MengXi-Studio/vite-plugin/releases)
775
+
776
+ ## 贡献指南
777
+
778
+ 欢迎贡献代码!请按以下步骤操作:
779
+
780
+ 1. Fork 本项目
781
+ 2. 创建功能分支:`git checkout -b feature/your-feature`
782
+ 3. 提交变更:`git commit -m "feat: your feature description"`
783
+ 4. 推送分支:`git push origin feature/your-feature`
784
+ 5. 创建 Pull Request
785
+
786
+ ## License
787
+
788
+ [MIT](LICENSE)