@meng-xi/vite-plugin 0.0.8 → 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/README-en.md CHANGED
@@ -1,540 +1,867 @@
1
- **English** | [中文](./README.md)
2
-
3
- <div align="center">
4
- <a href="https://github.com/MengXi-Studio/vite-plugin">
5
- <img alt="MengXi Studio 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>A toolkit providing practical plugins for Vite, also a complete plugin development framework</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
- ## Features
17
-
18
- - **Ready to Use** - Provides 6 practical plugins covering build progress display, file copying, router generation, version management, icon injection, and global Loading state management
19
- - **Plugin Development Framework** - Exports core components like BasePlugin, Logger, Validator for building custom Vite plugins
20
- - **Complete Lifecycle** - Supports initialization, config resolution, destroy lifecycle management with automatic hook composition
21
- - **Type Safe** - Complete TypeScript type definitions with configuration validators ensuring parameter correctness
22
- - **Flexible Configuration** - All plugins support detailed configuration to meet diverse scenario requirements
23
- - **Safe Execution** - Built-in error handling strategies (throw / log / ignore) for unified exception management
24
-
25
- ## Documentation
26
-
27
- View full documentation: [https://mengxi-studio.github.io/vite-plugin/](https://mengxi-studio.github.io/vite-plugin/)
28
-
29
- ## Installation
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
- ## Quick Start
48
-
49
- ### Using Built-in Plugins
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
- // Build progress bar
58
- buildProgress(),
59
-
60
- // Copy files
61
- copyFile({
62
- sourceDir: 'src/assets',
63
- targetDir: 'dist/assets'
64
- }),
65
-
66
- // Generate router config (uni-app)
67
- generateRouter({
68
- pagesJsonPath: 'src/pages.json',
69
- outputPath: 'src/router.config.ts'
70
- }),
71
-
72
- // Generate version
73
- generateVersion({
74
- format: 'datetime',
75
- outputType: 'both'
76
- }),
77
-
78
- // Inject website icon
79
- injectIco({
80
- base: '/assets'
81
- }),
82
-
83
- // Inject global Loading
84
- injectLoading({
85
- defaultVisible: true,
86
- autoHideOn: 'DOMContentLoaded'
87
- })
88
- ]
89
- })
90
- ```
91
-
92
- ### Accessing Plugin Instance
93
-
94
- All built-in plugins return an object with a `pluginInstance` property for accessing internal state:
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
- // Access plugin internals via pluginInstance
103
- console.log(routerPlugin.pluginInstance?.options)
104
- ```
105
-
106
- ### Developing Custom Plugins
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
- // Custom cleanup logic, e.g. close connections, stop watchers
139
- }
140
- }
141
-
142
- export const myPlugin = createPluginFactory(MyPlugin)
143
- ```
144
-
145
- ## Plugin Development Framework
146
-
147
- ### BasePlugin Core Concepts
148
-
149
- `BasePlugin` is the base class for all plugins, providing complete lifecycle management and development conventions:
150
-
151
- #### Lifecycle
152
-
153
- | Phase | Method | Description |
154
- | ----------------- | ------------------ | -------------------------------------------------------------- |
155
- | Initialization | `constructor` | Merge options, initialize logger and validator |
156
- | Config Resolution | `onConfigResolved` | Called when Vite config is resolved |
157
- | Hook Registration | `addPluginHooks` | Register Vite plugin hooks |
158
- | Destroy | `destroy` | Automatically called during `closeBundle` for resource cleanup |
159
-
160
- #### Automatic Hook Composition
161
-
162
- The `toPlugin()` method automatically composes the following hooks:
163
-
164
- - **configResolved** - Base class `onConfigResolved` runs first, then subclass hook
165
- - **closeBundle** - Subclass hook runs first, then base class `destroy`
166
-
167
- > Subclasses don't need to manually register `closeBundle` hooks for cleanup — just override the `destroy()` method.
168
-
169
- #### Required Methods
170
-
171
- | Method | Description |
172
- | ------------------------ | --------------------- |
173
- | `getPluginName()` | Return plugin name |
174
- | `addPluginHooks(plugin)` | Add Vite plugin hooks |
175
-
176
- #### Optional Methods
177
-
178
- | Method | Default Behavior | Description |
179
- | -------------------------- | ----------------- | ------------------------------------------- |
180
- | `getDefaultOptions()` | Returns `{}` | Provide plugin default options |
181
- | `validateOptions()` | No validation | Validate configuration parameters |
182
- | `getEnforce()` | `undefined` | Plugin execution order (`'pre'` / `'post'`) |
183
- | `onConfigResolved(config)` | Store config | Config resolved callback |
184
- | `destroy()` | Unregister logger | Cleanup logic when plugin is destroyed |
185
-
186
- #### Built-in Properties
187
-
188
- | Property | Type | Description |
189
- | ------------ | ------------------------ | ----------------------------- |
190
- | `options` | `Required<T>` | Merged complete configuration |
191
- | `logger` | `PluginLogger` | Plugin logger |
192
- | `validator` | `Validator<T>` | Configuration validator |
193
- | `viteConfig` | `ResolvedConfig \| null` | Resolved Vite configuration |
194
-
195
- #### Error Handling Strategy
196
-
197
- Control error behavior via the `errorStrategy` configuration option:
198
-
199
- - `'throw'` (default) - Log error and throw exception, halting the build
200
- - `'log'` - Log error but don't throw, continue execution
201
- - `'ignore'` - Log error but don't throw, continue execution
202
-
203
- Wrap error-prone operations with `safeExecute` / `safeExecuteSync`:
204
-
205
- ```typescript
206
- const result = await this.safeExecute(async () => {
207
- return await someAsyncOperation()
208
- }, 'Execute async operation')
209
- ```
210
-
211
- ### createPluginFactory
212
-
213
- Create plugin factory functions with optional normalizer support:
214
-
215
- ```typescript
216
- // Basic usage
217
- const myPlugin = createPluginFactory(MyPlugin)
218
-
219
- // With normalizer (supports shorthand string config)
220
- const myPlugin = createPluginFactory(MyPlugin, opt => (typeof opt === 'string' ? { path: opt } : opt))
221
-
222
- // Usage with shorthand
223
- myPlugin('./custom-path')
224
- ```
225
-
226
- ### Logger
227
-
228
- Global singleton log manager providing independent log control for each plugin:
229
-
230
- ```typescript
231
- import { Logger } from '@meng-xi/vite-plugin/logger'
232
-
233
- // Create logger (usually called automatically by BasePlugin)
234
- Logger.create({ name: 'my-plugin', enabled: true })
235
-
236
- // Unregister plugin log config (automatically called on plugin destroy)
237
- Logger.unregister('my-plugin')
238
-
239
- // Destroy singleton (for test scenarios)
240
- Logger.destroy()
241
- ```
242
-
243
- Log output format:
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
- ## Built-in Plugins
253
-
254
- | Plugin | Description |
255
- | --------------- | ----------------------------------------------------------------------------------------- |
256
- | buildProgress | Real-time build progress bar in terminal, supports bar / spinner / minimal |
257
- | copyFile | Copy files or directories after build, supports incremental copying |
258
- | generateRouter | Auto-generate router config from pages.json (uni-app) |
259
- | generateVersion | Auto-generate version numbers, supports file output and global variable injection |
260
- | injectIco | Inject website icon links into HTML files |
261
- | injectLoading | Inject global Loading state management with request interception and white-screen Loading |
262
-
263
- ### buildProgress
264
-
265
- Display real-time build progress bar in terminal during Vite build, supporting three display formats.
266
-
267
- | Option | Type | Default | Description |
268
- | --------------- | ------------------------------------- | ------- | ---------------------------------------------------- |
269
- | width | `number` | `30` | Progress bar width (characters) |
270
- | format | `'bar'` \| `'spinner'` \| `'minimal'` | `'bar'` | Progress bar display format |
271
- | completeChar | `string` | `'█'` | Fill character for completed portion |
272
- | incompleteChar | `string` | `'░'` | Fill character for incomplete portion |
273
- | clearOnComplete | `boolean` | `true` | Whether to clear progress bar on build completion |
274
- | showModuleName | `boolean` | `true` | Whether to show the currently processing module name |
275
- | theme | `ProgressTheme` | - | Custom color theme |
276
-
277
- **ProgressTheme**
278
-
279
- | Property | Type | Description |
280
- | --------------- | -------------------------- | ------------------------ |
281
- | completeColor | `(text: string) => string` | Completed portion color |
282
- | incompleteColor | `(text: string) => string` | Incomplete portion color |
283
- | percentageColor | `(text: string) => string` | Percentage number color |
284
- | phaseColor | `(text: string) => string` | Phase label color |
285
- | moduleColor | `(text: string) => string` | Module name color |
286
-
287
- ```typescript
288
- // Default bar format
289
- buildProgress()
290
-
291
- // Spinner format
292
- buildProgress({ format: 'spinner' })
293
-
294
- // Minimal format
295
- buildProgress({ format: 'minimal' })
296
-
297
- // Custom appearance
298
- buildProgress({
299
- width: 40,
300
- completeChar: '■',
301
- incompleteChar: '',
302
- clearOnComplete: false
303
- })
304
- ```
305
-
306
- ### copyFile
307
-
308
- Copy files or directories to specified locations after Vite build is completed.
309
-
310
- | Option | Type | Default | Description |
311
- | ----------- | --------- | ------- | ------------------------------------------ |
312
- | sourceDir | `string` | - | Source directory path (required) |
313
- | targetDir | `string` | - | Target directory path (required) |
314
- | overwrite | `boolean` | `true` | Whether to overwrite existing files |
315
- | recursive | `boolean` | `true` | Whether to recursively copy subdirectories |
316
- | incremental | `boolean` | `true` | Whether to enable incremental copying |
317
-
318
- ### generateRouter
319
-
320
- Automatically generate router configuration files based on uni-app project's `pages.json`.
321
-
322
- | Option | Type | Default | Description |
323
- | -------------------- | --------------------------------------------------------- | ------------------------ | ------------------------------------------------ |
324
- | pagesJsonPath | `string` | `'src/pages.json'` | Path to pages.json file |
325
- | outputPath | `string` | `'src/router.config.ts'` | Output file path |
326
- | outputFormat | `'ts'` \| `'js'` | `'ts'` | Output file format |
327
- | nameStrategy | `'path'` \| `'camelCase'` \| `'pascalCase'` \| `'custom'` | `'camelCase'` | Route name strategy |
328
- | customNameGenerator | `(path: string) => string` | - | Custom route name generator function |
329
- | includeSubPackages | `boolean` | `true` | Whether to include sub-package routes |
330
- | watch | `boolean` | `true` | Whether to watch changes and auto-regenerate |
331
- | metaMapping | `Record<string, string>` | - | Mapping from page style fields to meta |
332
- | exportTypes | `boolean` | `true` | Whether to export type definitions |
333
- | preserveRouteChanges | `boolean` | `true` | Whether to preserve user modifications to routes |
334
-
335
- ### generateVersion
336
-
337
- Automatically generate version numbers during the Vite build process.
338
-
339
- | Option | Type | Default | Description |
340
- | ------------ | --------------------------------------------------------------------------------- | ------------------- | ------------------------------ |
341
- | format | `'timestamp'` \| `'date'` \| `'datetime'` \| `'semver'` \| `'hash'` \| `'custom'` | `'timestamp'` | Version format |
342
- | customFormat | `string` | - | Custom format template |
343
- | semverBase | `string` | `'1.0.0'` | Semantic version base |
344
- | outputType | `'file'` \| `'define'` \| `'both'` | `'file'` | Output type |
345
- | outputFile | `string` | `'version.json'` | Output file path |
346
- | defineName | `string` | `'__APP_VERSION__'` | Global variable name to inject |
347
- | hashLength | `number` | `8` | Hash length (1-32) |
348
- | prefix | `string` | - | Version number prefix |
349
- | suffix | `string` | - | Version number suffix |
350
- | extra | `Record<string, unknown>` | - | Extra info (JSON file only) |
351
-
352
- ### injectIco
353
-
354
- Inject website icon links into the head of HTML files during the Vite build process.
355
-
356
- | Option | Type | Default | Description |
357
- | ----------- | -------- | ------- | ------------------------------- |
358
- | base | `string` | `'/'` | Base path for icon files |
359
- | url | `string` | - | Complete URL for the icon |
360
- | link | `string` | - | Custom complete link tag HTML |
361
- | icons | `Icon[]` | - | Custom icon array |
362
- | copyOptions | `object` | - | Icon file copying configuration |
363
-
364
- `Icon` interface definition:
365
-
366
- | Property | Type | Required | Description |
367
- | -------- | -------- | -------- | ------------------ |
368
- | rel | `string` | Yes | Icon relation type |
369
- | href | `string` | Yes | Icon URL |
370
- | sizes | `string` | No | Icon sizes |
371
- | type | `string` | No | Icon MIME type |
372
-
373
- `copyOptions` interface definition:
374
-
375
- | Property | Type | Required | Default | Description |
376
- | --------- | --------- | -------- | ------- | --------------------------- |
377
- | sourceDir | `string` | Yes | - | Icon source directory |
378
- | targetDir | `string` | Yes | - | Icon target directory |
379
- | overwrite | `boolean` | No | `true` | Whether to overwrite files |
380
- | recursive | `boolean` | No | `true` | Whether to copy recursively |
381
-
382
- ### injectLoading
383
-
384
- Inject global Loading state management with XHR/Fetch request interception, white-screen Loading, custom styles, and lifecycle callbacks.
385
-
386
- | Option | Type | Default | Description |
387
- | -------------- | ----------------------------------------------- | ----------------------- | ------------------------------------------------------- |
388
- | position | `'center'` \| `'top'` \| `'bottom'` | `'center'` | Loading display position |
389
- | defaultText | `string` | `'Loading...'` | Default display text |
390
- | spinnerType | `'spinner'` \| `'dots'` \| `'pulse'` \| `'bar'` | `'spinner'` | Spinner icon type |
391
- | style | `LoadingStyle` | - | Custom style configuration |
392
- | transition | `TransitionConfig` | `{ enabled: true }` | Transition animation configuration |
393
- | minDisplayTime | `MinDisplayTime` | `{ enabled: true }` | Minimum display time configuration |
394
- | delayShow | `DelayShow` | `{ enabled: true }` | Delayed show configuration |
395
- | debounceHide | `DebounceHide` | `{ enabled: false }` | Debounced hide configuration |
396
- | autoBind | `'fetch'` \| `'xhr'` \| `'all'` \| `'none'` | `'none'` | Auto-bind request interception mode |
397
- | requestFilter | `RequestFilter` | - | Request filter configuration |
398
- | globalName | `string` | `'__LOADING_MANAGER__'` | Global variable name injected into browser |
399
- | customTemplate | `string` | - | Custom HTML template (must include `data-loading-text`) |
400
- | defaultVisible | `boolean` | `false` | Whether initially visible (white-screen Loading) |
401
- | autoHideOn | `'DOMContentLoaded'` \| `'load'` \| `'manual'` | `'DOMContentLoaded'` | Auto-hide timing (requires `defaultVisible: true`) |
402
- | callbacks | `LoadingCallbacks` | - | Lifecycle callbacks |
403
-
404
- **LoadingStyle**
405
-
406
- | Property | Type | Default | Description |
407
- | ------------------ | --------- | ------------------------- | ------------------------------- |
408
- | overlayColor | `string` | `'rgba(255,255,255,0.7)'` | Overlay background color |
409
- | spinnerColor | `string` | `'#4361ee'` | Spinner color |
410
- | spinnerSize | `string` | `'40px'` | Spinner size |
411
- | textColor | `string` | `'#333'` | Text color |
412
- | textSize | `string` | `'14px'` | Text size |
413
- | customClass | `string` | - | Custom CSS class name |
414
- | customStyle | `string` | - | Custom inline style string |
415
- | zIndex | `number` | `9999` | z-index value |
416
- | pointerEvents | `boolean` | `false` | Whether to allow click-through |
417
- | backdropBlur | `boolean` | `false` | Whether to enable backdrop blur |
418
- | backdropBlurAmount | `number` | `4` | Backdrop blur amount (px) |
419
-
420
- **TransitionConfig**
421
-
422
- | Property | Type | Default | Description |
423
- | -------- | --------- | ------------ | ---------------------------- |
424
- | enabled | `boolean` | `true` | Whether to enable transition |
425
- | duration | `number` | `200` | Transition duration (ms) |
426
- | easing | `string` | `'ease-out'` | Easing function |
427
-
428
- **MinDisplayTime**
429
-
430
- | Property | Type | Default | Description |
431
- | -------- | --------- | ------- | ------------------------- |
432
- | enabled | `boolean` | `true` | Whether to enable |
433
- | duration | `number` | `300` | Minimum display time (ms) |
434
-
435
- **DelayShow**
436
-
437
- | Property | Type | Default | Description |
438
- | -------- | --------- | ------- | ------------------------------------------------------------------------------ |
439
- | enabled | `boolean` | `true` | Whether to enable |
440
- | duration | `number` | `200` | Delay duration (ms); if request completes within this time, Loading won't show |
441
-
442
- **DebounceHide**
443
-
444
- | Property | Type | Default | Description |
445
- | -------- | --------- | ------- | ----------------------- |
446
- | enabled | `boolean` | `false` | Whether to enable |
447
- | duration | `number` | `100` | Debounce wait time (ms) |
448
-
449
- **RequestFilter**
450
-
451
- | Property | Type | Description |
452
- | ------------------ | ---------- | --------------------------------------------------------------------- |
453
- | excludeUrls | `RegExp[]` | Array of URL regex patterns to exclude |
454
- | includeUrls | `RegExp[]` | Array of URL regex patterns to include (higher priority than exclude) |
455
- | excludeMethods | `string[]` | Array of HTTP methods to exclude |
456
- | excludeUrlPrefixes | `string[]` | Array of URL prefixes to exclude (prefix matching, more efficient) |
457
-
458
- **LoadingCallbacks**
459
-
460
- Callbacks are provided as **function body strings** (injected into browser at build time, function references cannot be passed).
461
-
462
- | Property | Type | Description |
463
- | ------------ | -------- | ----------------------------------------------- |
464
- | onBeforeShow | `string` | Before show callback, `return false` to prevent |
465
- | onShow | `string` | After show callback |
466
- | onBeforeHide | `string` | Before hide callback, `return false` to prevent |
467
- | onHide | `string` | After hide callback |
468
- | onDestroy | `string` | On destroy callback |
469
-
470
- **LoadingManager API**
471
-
472
- Access via `window.__LOADING_MANAGER__`:
473
-
474
- | Method | Description |
475
- | ------------------- | ------------------------------------------------------------------- |
476
- | `show(text?)` | Show Loading, optionally pass text |
477
- | `hide()` | Hide Loading (subject to min display time and debounce constraints) |
478
- | `forceHide()` | Force hide, ignoring min display time and debounce |
479
- | `destroy()` | Destroy instance and restore original interceptors |
480
- | `updateText(t)` | Update text content |
481
- | `isVisible()` | Get whether Loading is currently visible |
482
- | `getPendingCount()` | Get the number of pending requests |
483
-
484
- ```typescript
485
- // White-screen Loading: visible on page load, auto-hide on DOMContentLoaded
486
- injectLoading({ defaultVisible: true, autoHideOn: 'DOMContentLoaded' })
487
-
488
- // Auto-intercept all requests
489
- injectLoading({ autoBind: 'all' })
490
-
491
- // Custom styles + request filtering
492
- injectLoading({
493
- style: { overlayColor: 'rgba(0,0,0,0.5)', spinnerColor: '#fff' },
494
- autoBind: 'fetch',
495
- requestFilter: { excludeUrls: [/\/api\/health/] }
496
- })
497
-
498
- // Manual control
499
- injectLoading()
500
- window.__LOADING_MANAGER__.show('Saving...')
501
- window.__LOADING_MANAGER__.hide()
502
- ```
503
-
504
- ## Sub-path Exports
505
-
506
- Support importing modules on demand to reduce bundle size:
507
-
508
- ```typescript
509
- // Full import
510
- import { buildProgress, copyFile, injectLoading, BasePlugin, Logger } from '@meng-xi/vite-plugin'
511
-
512
- // Module-level import
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
- // Type imports (on-demand type definitions from sub-paths)
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
- ## Changelog
525
-
526
- See [GitHub Releases](https://github.com/MengXi-Studio/vite-plugin/releases)
527
-
528
- ## Contributing
529
-
530
- Contributions are welcome! Please follow these steps:
531
-
532
- 1. Fork the repository
533
- 2. Create a feature branch: `git checkout -b feature/your-feature`
534
- 3. Commit changes: `git commit -m "feat: your feature description"`
535
- 4. Push to branch: `git push origin feature/your-feature`
536
- 5. Create a Pull Request
537
-
538
- ## License
539
-
540
- [MIT](LICENSE)
1
+ **English** | [中文](./README.md)
2
+
3
+ <div align="center">
4
+ <a href="https://github.com/MengXi-Studio/vite-plugin">
5
+ <img alt="MengXi Studio 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>A toolkit providing practical plugins for Vite, also a complete plugin development framework</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
+ ## Features
17
+
18
+ - **Ready to Use** - Provides 7 practical plugins covering build progress display, file copying, router generation, version management, version update checking, icon injection, and global Loading state management
19
+ - **Plugin Development Framework** - Exports core components like BasePlugin, Logger, Validator for building custom Vite plugins
20
+ - **Complete Lifecycle** - Supports initialization, config resolution, destroy lifecycle management with automatic hook composition
21
+ - **Type Safe** - Complete TypeScript type definitions with configuration validators ensuring parameter correctness
22
+ - **Flexible Configuration** - All plugins support detailed configuration to meet diverse scenario requirements
23
+ - **Safe Execution** - Built-in error handling strategies (throw / log / ignore) for unified exception management
24
+ - **On-demand Import** - Supports sub-path exports to reduce bundle size
25
+
26
+ ## Documentation
27
+
28
+ View full documentation: [https://mengxi-studio.github.io/vite-plugin/](https://mengxi-studio.github.io/vite-plugin/)
29
+
30
+ ## Installation
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
+ ## Quick Start
44
+
45
+ ### Using Built-in Plugins
46
+
47
+ ```typescript
48
+ import { defineConfig } from 'vite'
49
+ import { buildProgress, copyFile, generateRouter, generateVersion, versionUpdateChecker, faviconManager, loadingManager } from '@meng-xi/vite-plugin'
50
+
51
+ export default defineConfig({
52
+ plugins: [
53
+ // Build progress bar
54
+ buildProgress(),
55
+
56
+ // Copy files
57
+ copyFile({
58
+ sourceDir: 'src/assets',
59
+ targetDir: 'dist/assets'
60
+ }),
61
+
62
+ // Generate router config (uni-app)
63
+ generateRouter({
64
+ pagesJsonPath: 'src/pages.json',
65
+ outputPath: 'src/router.config.ts'
66
+ }),
67
+
68
+ // Generate version
69
+ generateVersion({
70
+ format: 'datetime',
71
+ outputType: 'both'
72
+ }),
73
+
74
+ // Version update checker (works with generateVersion)
75
+ versionUpdateChecker(),
76
+
77
+ // Inject website icon (supports string shorthand)
78
+ faviconManager('/assets'),
79
+
80
+ // Global Loading state management
81
+ loadingManager({
82
+ defaultVisible: true,
83
+ autoHideOn: 'DOMContentLoaded'
84
+ })
85
+ ]
86
+ })
87
+ ```
88
+
89
+ ### Accessing Plugin Instance
90
+
91
+ All built-in plugins return an object with a `pluginInstance` property for accessing internal state:
92
+
93
+ ```typescript
94
+ import type { PluginWithInstance } from '@meng-xi/vite-plugin/factory'
95
+ import type { GenerateRouterOptions } from '@meng-xi/vite-plugin'
96
+
97
+ const routerPlugin = generateRouter({ watch: true }) as PluginWithInstance<GenerateRouterOptions>
98
+
99
+ // Access plugin internals via pluginInstance
100
+ console.log(routerPlugin.pluginInstance?.options)
101
+ ```
102
+
103
+ ## Built-in Plugins
104
+
105
+ | Plugin | Description |
106
+ | -------------------- | ---------------------------------------------------------------------------------------- |
107
+ | buildProgress | Real-time build progress bar in terminal, supports bar / spinner / minimal |
108
+ | copyFile | Copy files or directories after build, supports incremental copying |
109
+ | generateRouter | Auto-generate router config from pages.json (uni-app) |
110
+ | generateVersion | Auto-generate version numbers, supports file output and global variable injection |
111
+ | versionUpdateChecker | Runtime version update checking with multiple prompt styles and custom callbacks |
112
+ | faviconManager | Manage website favicon links injection into HTML files, supports string shorthand config |
113
+ | loadingManager | Global Loading state management with request interception and white-screen Loading |
114
+
115
+ ### buildProgress
116
+
117
+ Display real-time build progress bar in terminal during Vite build, supporting three display formats.
118
+
119
+ **Progress calculation logic:**
120
+
121
+ 1. config phase (5%) → resolve phase (10%) → transform phase (15%-85%, by module conversion ratio) → bundle phase (+10%) → write phase (+5%) → done (100%)
122
+ 2. Non-TTY terminal environments (e.g. CI/CD) automatically degrade to log output mode
123
+
124
+ | Option | Type | Default | Description |
125
+ | --------------- | ------------------------------------- | ------- | ---------------------------------------------------- |
126
+ | width | `number` | `30` | Progress bar width (characters) |
127
+ | format | `'bar'` \| `'spinner'` \| `'minimal'` | `'bar'` | Progress bar display format |
128
+ | completeChar | `string` | `'█'` | Fill character for completed portion |
129
+ | incompleteChar | `string` | `'░'` | Fill character for incomplete portion |
130
+ | clearOnComplete | `boolean` | `true` | Whether to clear progress bar on build completion |
131
+ | showModuleName | `boolean` | `true` | Whether to show the currently processing module name |
132
+ | theme | `ProgressTheme` | - | Custom color theme |
133
+
134
+ **ProgressTheme**
135
+
136
+ | Property | Type | Description |
137
+ | --------------- | -------------------------- | ------------------------ |
138
+ | completeColor | `(text: string) => string` | Completed portion color |
139
+ | incompleteColor | `(text: string) => string` | Incomplete portion color |
140
+ | percentageColor | `(text: string) => string` | Percentage number color |
141
+ | phaseColor | `(text: string) => string` | Phase label color |
142
+ | moduleColor | `(text: string) => string` | Module name color |
143
+
144
+ ```typescript
145
+ // Default bar format
146
+ buildProgress()
147
+
148
+ // Spinner format
149
+ buildProgress({ format: 'spinner' })
150
+
151
+ // Minimal format
152
+ buildProgress({ format: 'minimal' })
153
+
154
+ // Custom appearance
155
+ buildProgress({
156
+ width: 40,
157
+ completeChar: '■',
158
+ incompleteChar: '□',
159
+ clearOnComplete: false
160
+ })
161
+
162
+ // Custom color theme
163
+ buildProgress({
164
+ theme: {
165
+ completeColor: t => `\x1b[32m${t}\x1b[39m`,
166
+ incompleteColor: t => `\x1b[90m${t}\x1b[39m`,
167
+ percentageColor: t => `\x1b[1m${t}\x1b[22m`,
168
+ phaseColor: t => `\x1b[36m${t}\x1b[39m`,
169
+ moduleColor: t => `\x1b[90m${t}\x1b[39m`
170
+ }
171
+ })
172
+ ```
173
+
174
+ ### copyFile
175
+
176
+ Copy files or directories to specified locations after Vite build is completed, with `enforce: 'post'`.
177
+
178
+ | Option | Type | Default | Description |
179
+ | ----------- | --------- | ------- | ------------------------------------------ |
180
+ | sourceDir | `string` | - | Source directory path (required) |
181
+ | targetDir | `string` | - | Target directory path (required) |
182
+ | overwrite | `boolean` | `true` | Whether to overwrite existing files |
183
+ | recursive | `boolean` | `true` | Whether to recursively copy subdirectories |
184
+ | incremental | `boolean` | `true` | Whether to enable incremental copying |
185
+
186
+ ```typescript
187
+ // Basic usage
188
+ copyFile({
189
+ sourceDir: 'src/assets',
190
+ targetDir: 'dist/assets'
191
+ })
192
+
193
+ // Disable overwrite and incremental copy
194
+ copyFile({
195
+ sourceDir: 'src/static',
196
+ targetDir: 'dist/static',
197
+ overwrite: false,
198
+ incremental: false
199
+ })
200
+ ```
201
+
202
+ ### generateRouter
203
+
204
+ Automatically generate router configuration files based on uni-app project's `pages.json`.
205
+
206
+ | Option | Type | Default | Description |
207
+ | -------------------- | --------------------------------------------------------- | ------------------------ | ------------------------------------------------ |
208
+ | pagesJsonPath | `string` | `'src/pages.json'` | Path to pages.json file |
209
+ | outputPath | `string` | `'src/router.config.ts'` | Output file path |
210
+ | outputFormat | `'ts'` \| `'js'` | `'ts'` | Output file format |
211
+ | nameStrategy | `'path'` \| `'camelCase'` \| `'pascalCase'` \| `'custom'` | `'camelCase'` | Route name strategy |
212
+ | customNameGenerator | `(path: string) => string` | - | Custom route name generator function |
213
+ | includeSubPackages | `boolean` | `true` | Whether to include sub-package routes |
214
+ | watch | `boolean` | `true` | Whether to watch changes and auto-regenerate |
215
+ | metaMapping | `Record<string, string>` | - | Mapping from page style fields to meta |
216
+ | exportTypes | `boolean` | `true` | Whether to export type definitions |
217
+ | preserveRouteChanges | `boolean` | `true` | Whether to preserve user modifications to routes |
218
+
219
+ > Default `metaMapping` is `{ navigationBarTitleText: 'title', requireAuth: 'requireAuth' }`, automatically mapping page style fields to route meta. When `nameStrategy` is `'custom'`, `customNameGenerator` must be
220
+ > provided.
221
+
222
+ ```typescript
223
+ // Basic usage
224
+ generateRouter()
225
+
226
+ // Custom pages.json path
227
+ generateRouter({ pagesJsonPath: 'pages.json' })
228
+
229
+ // Output JavaScript file
230
+ generateRouter({ outputFormat: 'js', outputPath: 'src/router.config.js' })
231
+
232
+ // PascalCase naming strategy
233
+ generateRouter({ nameStrategy: 'pascalCase' })
234
+
235
+ // Custom route name generator
236
+ generateRouter({
237
+ nameStrategy: 'custom',
238
+ customNameGenerator: path => `route_${path.replace(/\//g, '_')}`
239
+ })
240
+
241
+ // Custom meta mapping
242
+ generateRouter({
243
+ metaMapping: {
244
+ navigationBarTitleText: 'title',
245
+ requireAuth: 'requireAuth',
246
+ customField: 'custom'
247
+ }
248
+ })
249
+ ```
250
+
251
+ ### generateVersion
252
+
253
+ Automatically generate version numbers during the Vite build process.
254
+
255
+ | Option | Type | Default | Description |
256
+ | ------------ | --------------------------------------------------------------------------------- | ------------------- | -------------------------------- |
257
+ | format | `'timestamp'` \| `'date'` \| `'datetime'` \| `'semver'` \| `'hash'` \| `'custom'` | `'timestamp'` | Version number format |
258
+ | customFormat | `string` | - | Custom format template |
259
+ | semverBase | `string` | `'1.0.0'` | Semantic version base value |
260
+ | outputType | `'file'` \| `'define'` \| `'both'` | `'file'` | Output type |
261
+ | outputFile | `string` | `'version.json'` | Output file path |
262
+ | defineName | `string` | `'__APP_VERSION__'` | Injected global variable name |
263
+ | hashLength | `number` | `8` | Hash length (1-32) |
264
+ | prefix | `string` | - | Version number prefix |
265
+ | suffix | `string` | - | Version number suffix |
266
+ | extra | `Record<string, unknown>` | - | Additional info (JSON file only) |
267
+
268
+ **customFormat placeholders:**
269
+
270
+ | Placeholder | Description | Example |
271
+ | ------------- | ----------------------------------- | --------------- |
272
+ | `{YYYY}` | Four-digit year | `2026` |
273
+ | `{YY}` | Two-digit year | `26` |
274
+ | `{MM}` | Two-digit month | `05` |
275
+ | `{DD}` | Two-digit day | `22` |
276
+ | `{HH}` | Two-digit hour (24h) | `15` |
277
+ | `{mm}` | Two-digit minute | `30` |
278
+ | `{ss}` | Two-digit second | `00` |
279
+ | `{SSS}` | Three-digit millisecond | `123` |
280
+ | `{timestamp}` | Timestamp (milliseconds) | `1779464601000` |
281
+ | `{hash}` | Random hash | `a1b2c3d4` |
282
+ | `{major}` | Major version (requires semverBase) | `1` |
283
+ | `{minor}` | Minor version (requires semverBase) | `0` |
284
+ | `{patch}` | Patch version (requires semverBase) | `0` |
285
+
286
+ > When `format` is `'custom'`, `customFormat` must be provided. When `outputType` is `'define'` or `'both'`, a `{defineName}_INFO` global variable is also injected, containing complete info such as version, build time,
287
+ > and timestamp.
288
+
289
+ ```typescript
290
+ // Timestamp format (default)
291
+ generateVersion()
292
+
293
+ // Date format
294
+ generateVersion({ format: 'date' })
295
+
296
+ // Semantic version format
297
+ generateVersion({ format: 'semver', semverBase: '2.0.0', prefix: 'v' })
298
+
299
+ // Custom format
300
+ generateVersion({
301
+ format: 'custom',
302
+ customFormat: '{YYYY}.{MM}.{DD}-{hash}',
303
+ hashLength: 6
304
+ })
305
+
306
+ // Inject into code
307
+ generateVersion({ outputType: 'define', defineName: '__VERSION__' })
308
+
309
+ // Both file output and code injection
310
+ generateVersion({
311
+ outputType: 'both',
312
+ outputFile: 'build-info.json',
313
+ defineName: '__BUILD_VERSION__',
314
+ extra: { environment: 'production' }
315
+ })
316
+ ```
317
+
318
+ ### versionUpdateChecker
319
+
320
+ Periodically check for version changes at runtime and prompt users to refresh when a new version is detected. Typically used in conjunction with the `generateVersion` plugin.
321
+
322
+ **How it works:**
323
+
324
+ 1. `generateVersion` generates a version file (`version.json`) or injects a global variable at build time
325
+ 2. `versionUpdateChecker` periodically requests the version file at runtime and compares it with the current version
326
+ 3. When a version mismatch is detected, a prompt is shown to guide the user to refresh
327
+
328
+ | Option | Type | Default | Description |
329
+ | ----------------------- | ------------------------------------ | -------------------------------------------------------------- | ------------------------------------------------------------ |
330
+ | versionSource | `'define'` \| `'file'` \| `'auto'` | `'auto'` | Current version source |
331
+ | defineName | `string` | `'__APP_VERSION__'` | Global variable name in define mode |
332
+ | checkUrl | `string` | `'/version.json'` | URL path for version check file |
333
+ | checkInterval | `number` | `300000` | Check interval in milliseconds (default 5 minutes) |
334
+ | checkOnVisibilityChange | `boolean` | `true` | Whether to check immediately on page visibility change |
335
+ | enableInDev | `boolean` | `false` | Whether to enable in development mode |
336
+ | promptStyle | `'modal'` \| `'banner'` \| `'toast'` | `'modal'` | Update prompt UI style |
337
+ | promptMessage | `string` | `'A new version is available. Refresh now to get the latest?'` | Prompt message text |
338
+ | refreshButtonText | `string` | `'Refresh'` | Refresh button text |
339
+ | dismissButtonText | `string` | `'Later'` | Dismiss button text |
340
+ | customPromptTemplate | `string` | - | Custom HTML template for the prompt UI |
341
+ | customStyle | `string` | - | Custom CSS style string |
342
+ | onUpdateAvailable | `string` | - | Callback when new version is found (function body string) |
343
+ | onRefresh | `string` | - | Callback when user chooses to refresh (function body string) |
344
+ | onDismiss | `string` | - | Callback when user chooses to dismiss (function body string) |
345
+
346
+ > `versionSource` explanation: `'define'` reads from global variable, `'file'` reads from version file, `'auto'` prefers define and falls back to file. Custom templates can use `{{message}}`, `{{currentVersion}}`,
347
+ > `{{newVersion}}`, `{{refreshButton}}`, `{{dismissButton}}` placeholders. Callbacks are provided as function body strings with available variables: `currentVersion`, `newVersion`.
348
+
349
+ ```typescript
350
+ // Basic usage (with generateVersion)
351
+ generateVersion({ outputType: 'both' })
352
+ versionUpdateChecker()
353
+
354
+ // Read from version file only
355
+ versionUpdateChecker({ versionSource: 'file' })
356
+
357
+ // Custom check interval and prompt style
358
+ versionUpdateChecker({
359
+ checkInterval: 60000,
360
+ promptStyle: 'banner'
361
+ })
362
+
363
+ // Toast-style prompt
364
+ versionUpdateChecker({ promptStyle: 'toast' })
365
+
366
+ // Custom prompt text
367
+ versionUpdateChecker({
368
+ promptMessage: 'System updated, refresh to experience new features',
369
+ refreshButtonText: 'Update',
370
+ dismissButtonText: 'Cancel'
371
+ })
372
+
373
+ // Custom callbacks
374
+ versionUpdateChecker({
375
+ onUpdateAvailable: 'console.log("New version:", newVersion); return true;',
376
+ onRefresh: 'console.log("User chose to refresh");',
377
+ onDismiss: 'console.log("User chose to dismiss");'
378
+ })
379
+
380
+ // Enable in development (for debugging)
381
+ versionUpdateChecker({ enableInDev: true })
382
+ ```
383
+
384
+ ### faviconManager
385
+
386
+ Inject website icon links into the head of HTML files during the Vite build process. Supports string shorthand config.
387
+
388
+ | Option | Type | Default | Description |
389
+ | ----------- | -------- | ------- | ------------------------------- |
390
+ | base | `string` | `'/'` | Base path for icon files |
391
+ | url | `string` | - | Complete URL for the icon |
392
+ | link | `string` | - | Custom complete link tag HTML |
393
+ | icons | `Icon[]` | - | Custom icon array |
394
+ | copyOptions | `object` | - | Icon file copying configuration |
395
+
396
+ > Priority: `link` > `url` > `base`. When `link` is provided, custom HTML is injected directly; when `url` is provided, the complete URL is used; otherwise `base + '/favicon.ico'` is used.
397
+
398
+ `Icon` interface definition:
399
+
400
+ | Property | Type | Required | Description |
401
+ | -------- | -------- | -------- | ------------------ |
402
+ | rel | `string` | Yes | Icon relation type |
403
+ | href | `string` | Yes | Icon URL |
404
+ | sizes | `string` | No | Icon sizes |
405
+ | type | `string` | No | Icon MIME type |
406
+
407
+ `copyOptions` interface definition:
408
+
409
+ | Property | Type | Required | Default | Description |
410
+ | --------- | --------- | -------- | ------- | --------------------------- |
411
+ | sourceDir | `string` | Yes | - | Icon source directory |
412
+ | targetDir | `string` | Yes | - | Icon target directory |
413
+ | overwrite | `boolean` | No | `true` | Whether to overwrite files |
414
+ | recursive | `boolean` | No | `true` | Whether to copy recursively |
415
+
416
+ ```typescript
417
+ // Use default config
418
+ faviconManager()
419
+
420
+ // String shorthand (set base path)
421
+ faviconManager('/assets')
422
+
423
+ // Custom icon array
424
+ faviconManager({
425
+ base: '/assets',
426
+ icons: [
427
+ { rel: 'icon', href: '/favicon.svg', type: 'image/svg+xml' },
428
+ { rel: 'icon', href: '/favicon-32x32.png', sizes: '32x32', type: 'image/png' },
429
+ { rel: 'apple-touch-icon', href: '/apple-touch-icon.png', sizes: '180x180' }
430
+ ]
431
+ })
432
+
433
+ // Custom complete link tag
434
+ faviconManager({
435
+ link: '<link rel="icon" href="/favicon.svg" type="image/svg+xml" />'
436
+ })
437
+
438
+ // With file copying
439
+ faviconManager({
440
+ base: '/assets',
441
+ copyOptions: {
442
+ sourceDir: 'src/assets/icons',
443
+ targetDir: 'dist/assets/icons'
444
+ }
445
+ })
446
+ ```
447
+
448
+ ### loadingManager
449
+
450
+ Inject global Loading state management with XHR/Fetch request interception, white-screen Loading, custom styles, and lifecycle callbacks.
451
+
452
+ **Injection strategy:**
453
+
454
+ - `defaultVisible: false` (default): All code (CSS + HTML + JS) is dynamically injected via JS before `</body>`
455
+ - `defaultVisible: true`: CSS + HTML are injected as static tags before `</head>` (visible on white screen), JS is injected before `</body>`
456
+
457
+ | Option | Type | Default | Description |
458
+ | -------------- | ----------------------------------------------- | ----------------------- | ------------------------------------------------------- |
459
+ | position | `'center'` \| `'top'` \| `'bottom'` | `'center'` | Loading display position |
460
+ | defaultText | `string` | `'Loading...'` | Default display text |
461
+ | spinnerType | `'spinner'` \| `'dots'` \| `'pulse'` \| `'bar'` | `'spinner'` | Spinner icon type |
462
+ | style | `LoadingStyle` | - | Custom style configuration |
463
+ | transition | `TransitionConfig` | `{ enabled: true }` | Transition animation configuration |
464
+ | minDisplayTime | `MinDisplayTime` | `{ enabled: true }` | Minimum display time configuration |
465
+ | delayShow | `DelayShow` | `{ enabled: true }` | Delayed show configuration |
466
+ | debounceHide | `DebounceHide` | `{ enabled: false }` | Debounced hide configuration |
467
+ | autoBind | `'fetch'` \| `'xhr'` \| `'all'` \| `'none'` | `'none'` | Auto-bind request interception mode |
468
+ | requestFilter | `RequestFilter` | - | Request filter configuration |
469
+ | globalName | `string` | `'__LOADING_MANAGER__'` | Global variable name injected into browser |
470
+ | customTemplate | `string` | - | Custom HTML template (must include `data-loading-text`) |
471
+ | defaultVisible | `boolean` | `false` | Whether initially visible (white-screen Loading) |
472
+ | autoHideOn | `'DOMContentLoaded'` \| `'load'` \| `'manual'` | `'DOMContentLoaded'` | Auto-hide timing (requires `defaultVisible: true`) |
473
+ | callbacks | `LoadingCallbacks` | - | Lifecycle callbacks |
474
+
475
+ **LoadingStyle**
476
+
477
+ | Property | Type | Default | Description |
478
+ | ------------------ | --------- | ------------------------- | ---------------------------------------- |
479
+ | overlayColor | `string` | `'rgba(255,255,255,0.7)'` | Overlay background color |
480
+ | spinnerColor | `string` | `'#4361ee'` | Spinner icon color |
481
+ | spinnerSize | `string` | `'40px'` | Spinner icon size |
482
+ | textColor | `string` | `'#333'` | Text color |
483
+ | textSize | `string` | `'14px'` | Text size |
484
+ | customClass | `string` | - | Custom CSS class name |
485
+ | customStyle | `string` | - | Custom inline style |
486
+ | zIndex | `number` | `9999` | z-index value |
487
+ | pointerEvents | `boolean` | `true` | Whether to enable overlay pointer events |
488
+ | backdropBlur | `boolean` | `false` | Whether to enable backdrop blur |
489
+ | backdropBlurAmount | `number` | `4` | Backdrop blur amount (px) |
490
+
491
+ **TransitionConfig**
492
+
493
+ | Property | Type | Default | Description |
494
+ | -------- | --------- | ------------ | ---------------------------- |
495
+ | enabled | `boolean` | `true` | Whether to enable transition |
496
+ | duration | `number` | `200` | Transition duration (ms) |
497
+ | easing | `string` | `'ease-out'` | Easing function |
498
+
499
+ **MinDisplayTime**
500
+
501
+ | Property | Type | Default | Description |
502
+ | -------- | --------- | ------- | --------------------------------------------------------- |
503
+ | enabled | `boolean` | `true` | Whether to enable |
504
+ | duration | `number` | `300` | Minimum display time (ms), prevents Loading from flashing |
505
+
506
+ **DelayShow**
507
+
508
+ | Property | Type | Default | Description |
509
+ | -------- | --------- | ------- | ------------------------------------------------------------------------------ |
510
+ | enabled | `boolean` | `true` | Whether to enable |
511
+ | duration | `number` | `200` | Delay duration (ms); if request completes within this time, Loading won't show |
512
+
513
+ **DebounceHide**
514
+
515
+ | Property | Type | Default | Description |
516
+ | -------- | --------- | ------- | ----------------------- |
517
+ | enabled | `boolean` | `false` | Whether to enable |
518
+ | duration | `number` | `100` | Debounce wait time (ms) |
519
+
520
+ **RequestFilter**
521
+
522
+ | Property | Type | Description |
523
+ | ------------------ | ---------- | --------------------------------------------------------------------- |
524
+ | excludeUrls | `RegExp[]` | Array of URL regex patterns to exclude |
525
+ | includeUrls | `RegExp[]` | Array of URL regex patterns to include (higher priority than exclude) |
526
+ | excludeMethods | `string[]` | Array of HTTP methods to exclude |
527
+ | excludeUrlPrefixes | `string[]` | Array of URL prefixes to exclude (prefix matching, more efficient) |
528
+
529
+ **LoadingCallbacks**
530
+
531
+ Callbacks are provided as **function body strings** (injected into browser at build time, function references cannot be passed).
532
+
533
+ | Property | Type | Description |
534
+ | ------------ | -------- | ----------------------------------------------- |
535
+ | onBeforeShow | `string` | Before show callback, `return false` to prevent |
536
+ | onShow | `string` | After show callback |
537
+ | onBeforeHide | `string` | Before hide callback, `return false` to prevent |
538
+ | onHide | `string` | After hide callback |
539
+ | onDestroy | `string` | On destroy callback |
540
+
541
+ **LoadingManager API**
542
+
543
+ Access via `window.__LOADING_MANAGER__`:
544
+
545
+ | Method | Description |
546
+ | -------------------------- | ------------------------------------------------------------------- |
547
+ | `show(text?)` | Show Loading, optionally pass text |
548
+ | `hide()` | Hide Loading (subject to min display time and debounce constraints) |
549
+ | `forceHide()` | Force hide, ignoring min display time and debounce |
550
+ | `toggle(text?)` | Toggle Loading show/hide state |
551
+ | `updateText(text)` | Update text content |
552
+ | `isVisible()` | Get whether Loading is currently visible |
553
+ | `isPointerEventsEnabled()` | Get whether pointer events are currently enabled |
554
+ | `enablePointerEvents()` | Enable overlay pointer events, intercept all clicks and scrolls |
555
+ | `disablePointerEvents()` | Disable overlay pointer events, allow interaction passthrough |
556
+ | `togglePointerEvents()` | Toggle overlay pointer events state |
557
+ | `getPendingCount()` | Get the number of pending requests |
558
+ | `destroy()` | Destroy instance, clean up DOM and restore original interceptors |
559
+
560
+ ```typescript
561
+ // White-screen Loading: visible on page load, auto-hide on DOMContentLoaded
562
+ loadingManager({ defaultVisible: true, autoHideOn: 'DOMContentLoaded' })
563
+
564
+ // White-screen Loading: auto-hide after all resources loaded
565
+ loadingManager({ defaultVisible: true, autoHideOn: 'load' })
566
+
567
+ // Vue/React SPA: visible on white screen, manually hide after framework renders
568
+ loadingManager({ defaultVisible: true, autoHideOn: 'manual' })
569
+ // In app entry: window.__LOADING_MANAGER__.hide()
570
+
571
+ // Auto-intercept all requests
572
+ loadingManager({ autoBind: 'all' })
573
+
574
+ // Custom styles + request filtering
575
+ loadingManager({
576
+ style: { overlayColor: 'rgba(0,0,0,0.5)', spinnerColor: '#fff', backdropBlur: true },
577
+ autoBind: 'fetch',
578
+ requestFilter: { excludeUrls: [/\/api\/health/], excludeUrlPrefixes: ['http://localhost'] }
579
+ })
580
+
581
+ // Debounced hide (prevent rapid flashing)
582
+ loadingManager({ debounceHide: { enabled: true, duration: 100 } })
583
+
584
+ // Lifecycle callbacks
585
+ loadingManager({
586
+ callbacks: {
587
+ onBeforeShow: 'if (shouldSkip) return false;',
588
+ onShow: 'console.log("loading shown")',
589
+ onBeforeHide: 'if (shouldKeepVisible) return false;',
590
+ onHide: 'console.log("loading hidden")'
591
+ }
592
+ })
593
+
594
+ // Manual control
595
+ loadingManager()
596
+ window.__LOADING_MANAGER__.show('Saving...')
597
+ window.__LOADING_MANAGER__.hide()
598
+ window.__LOADING_MANAGER__.toggle()
599
+ window.__LOADING_MANAGER__.disablePointerEvents()
600
+ ```
601
+
602
+ ## Common Utilities
603
+
604
+ Exported via `@meng-xi/vite-plugin/common`, reusable in custom plugins:
605
+
606
+ ```typescript
607
+ import { deepMerge, formatDate, parseTemplate, toCamelCase, toPascalCase, stripJsonComments, generateRandomHash, Validator } from '@meng-xi/vite-plugin/common'
608
+ import { readFileContent, writeFileContent, fileExists, copySourceToTarget } from '@meng-xi/vite-plugin/common'
609
+ import { injectBeforeTag, injectHtmlByPriority } from '@meng-xi/vite-plugin/common'
610
+ import { makeCallback, containsScriptTag, validateIdentifierName } from '@meng-xi/vite-plugin/common'
611
+ ```
612
+
613
+ | Function | Description |
614
+ | -------------------------- | ----------------------------------------------------------------------------- |
615
+ | `deepMerge()` | Deep merge objects (undefined skipped, arrays overwritten) |
616
+ | `formatDate()` | Format date with `{YYYY}`, `{MM}`, `{DD}` etc. placeholders |
617
+ | `parseTemplate()` | Parse template string, replace placeholders |
618
+ | `toCamelCase()` | Convert to camelCase |
619
+ | `toPascalCase()` | Convert to PascalCase |
620
+ | `stripJsonComments()` | Remove comments from JSON string |
621
+ | `generateRandomHash()` | Generate random hash string (1-64 characters) |
622
+ | `readFileContent()` | Async read file content |
623
+ | `writeFileContent()` | Async write file content |
624
+ | `fileExists()` | Async check if file exists |
625
+ | `copySourceToTarget()` | Copy files or directories with incremental copy and concurrency |
626
+ | `injectBeforeTag()` | Inject code before a specified closing HTML tag |
627
+ | `injectHtmlByPriority()` | Inject code into HTML by priority (`</head>` → `</body>` → `</html>`) |
628
+ | `makeCallback()` | Wrap callback function body as safe function expression (with try-catch) |
629
+ | `containsScriptTag()` | Detect if a string contains `<script>` tags |
630
+ | `validateIdentifierName()` | Validate string as a legal JavaScript identifier, prevent prototype pollution |
631
+
632
+ ## Plugin Development Framework
633
+
634
+ ### BasePlugin Core Concepts
635
+
636
+ `BasePlugin` is the base class for all plugins, providing complete lifecycle management and development conventions:
637
+
638
+ #### Lifecycle
639
+
640
+ | Phase | Method | Description |
641
+ | ----------------- | ------------------ | -------------------------------------------------------------- |
642
+ | Initialization | `constructor` | Merge options, initialize logger and validator |
643
+ | Config Resolution | `onConfigResolved` | Called when Vite config is resolved |
644
+ | Hook Registration | `addPluginHooks` | Register Vite plugin hooks |
645
+ | Destroy | `destroy` | Automatically called during `closeBundle` for resource cleanup |
646
+
647
+ #### Automatic Hook Composition
648
+
649
+ The `toPlugin()` method automatically composes the following hooks:
650
+
651
+ - **configResolved** - Base class `onConfigResolved` runs first, then subclass hook
652
+ - **closeBundle** - Subclass hook runs first, then base class `destroy`
653
+
654
+ > Subclasses don't need to manually register `closeBundle` hooks for cleanup — just override the `destroy()` method.
655
+
656
+ #### Required Methods
657
+
658
+ | Method | Description |
659
+ | ------------------------ | --------------------- |
660
+ | `getPluginName()` | Return plugin name |
661
+ | `addPluginHooks(plugin)` | Add Vite plugin hooks |
662
+
663
+ #### Optional Methods
664
+
665
+ | Method | Default Behavior | Description |
666
+ | -------------------------- | ----------------- | ------------------------------------------- |
667
+ | `getDefaultOptions()` | Returns `{}` | Provide plugin default options |
668
+ | `validateOptions()` | No validation | Validate configuration parameters |
669
+ | `getEnforce()` | `undefined` | Plugin execution order (`'pre'` / `'post'`) |
670
+ | `onConfigResolved(config)` | Store config | Config resolved callback |
671
+ | `destroy()` | Unregister logger | Cleanup logic when plugin is destroyed |
672
+
673
+ #### Built-in Properties
674
+
675
+ | Property | Type | Description |
676
+ | ------------ | ------------------------ | ----------------------------- |
677
+ | `options` | `Required<T>` | Merged complete configuration |
678
+ | `logger` | `PluginLogger` | Plugin logger |
679
+ | `validator` | `Validator<T>` | Configuration validator |
680
+ | `viteConfig` | `ResolvedConfig \| null` | Resolved Vite configuration |
681
+
682
+ #### Error Handling Strategy
683
+
684
+ Control error behavior via the `errorStrategy` configuration option:
685
+
686
+ - `'throw'` (default) - Log error and throw exception, halting the build
687
+ - `'log'` - Log error but don't throw, continue execution
688
+ - `'ignore'` - Log error but don't throw, continue execution
689
+
690
+ Wrap error-prone operations with `safeExecute` / `safeExecuteSync`:
691
+
692
+ ```typescript
693
+ // Async safe execution
694
+ const result = await this.safeExecute(async () => {
695
+ return await someAsyncOperation()
696
+ }, 'Execute async operation')
697
+
698
+ // Sync safe execution
699
+ const value = this.safeExecuteSync(() => {
700
+ return someSyncOperation()
701
+ }, 'Execute sync operation')
702
+ ```
703
+
704
+ ### createPluginFactory
705
+
706
+ Create plugin factory functions with optional normalizer support:
707
+
708
+ ```typescript
709
+ // Basic usage
710
+ const myPlugin = createPluginFactory(MyPlugin)
711
+
712
+ // With normalizer (supports shorthand string config)
713
+ const myPlugin = createPluginFactory(MyPlugin, opt => (typeof opt === 'string' ? { path: opt } : opt))
714
+
715
+ // Usage with shorthand
716
+ myPlugin('./custom-path')
717
+ ```
718
+
719
+ ### Validator
720
+
721
+ Fluent configuration validator with chainable API:
722
+
723
+ ```typescript
724
+ import { Validator } from '@meng-xi/vite-plugin/common'
725
+
726
+ const validator = new Validator(options)
727
+ validator
728
+ .field('sourceDir')
729
+ .required()
730
+ .string()
731
+ .field('targetDir')
732
+ .required()
733
+ .string()
734
+ .field('overwrite')
735
+ .boolean()
736
+ .default(true)
737
+ .field('port')
738
+ .number()
739
+ .field('list')
740
+ .array()
741
+ .field('config')
742
+ .object()
743
+ .field('name')
744
+ .custom(val => val.length > 0, 'name cannot be empty')
745
+ .validate()
746
+ ```
747
+
748
+ | Method | Description |
749
+ | ------------ | --------------------------------------------------------------- |
750
+ | `field()` | Specify the field to validate |
751
+ | `required()` | Mark field as required |
752
+ | `string()` | Validate field value is a string type |
753
+ | `boolean()` | Validate field value is a boolean type |
754
+ | `number()` | Validate field value is a number type |
755
+ | `array()` | Validate field value is an array type |
756
+ | `object()` | Validate field value is an object type |
757
+ | `default()` | Set default value for field (only when value is undefined/null) |
758
+ | `custom()` | Validate field value with a custom function |
759
+ | `validate()` | Execute validation, throws error on failure |
760
+
761
+ ### Logger
762
+
763
+ Global singleton log manager providing independent log control for each plugin:
764
+
765
+ ```typescript
766
+ import { Logger } from '@meng-xi/vite-plugin/logger'
767
+
768
+ // Create logger (usually called automatically by BasePlugin)
769
+ Logger.create({ name: 'my-plugin', enabled: true })
770
+
771
+ // Unregister plugin log config (automatically called on plugin destroy)
772
+ Logger.unregister('my-plugin')
773
+
774
+ // Destroy singleton (for test scenarios)
775
+ Logger.destroy()
776
+ ```
777
+
778
+ Log output format:
779
+
780
+ ```
781
+ ℹ️ [@meng-xi/vite-plugin:my-plugin] Info message
782
+ ✅ [@meng-xi/vite-plugin:my-plugin] Success message
783
+ ⚠️ [@meng-xi/vite-plugin:my-plugin] Warning message
784
+ ❌ [@meng-xi/vite-plugin:my-plugin] Error message
785
+ ```
786
+
787
+ ### Custom Plugin Example
788
+
789
+ ```typescript
790
+ import { BasePlugin, createPluginFactory } from '@meng-xi/vite-plugin'
791
+ import type { BasePluginOptions, PluginWithInstance } from '@meng-xi/vite-plugin/factory'
792
+ import type { Plugin } from 'vite'
793
+
794
+ interface MyPluginOptions extends BasePluginOptions {
795
+ path: string
796
+ }
797
+
798
+ class MyPlugin extends BasePlugin<MyPluginOptions> {
799
+ protected getDefaultOptions() {
800
+ return { path: './default' }
801
+ }
802
+
803
+ protected validateOptions(): void {
804
+ this.validator.field('path').required().string().validate()
805
+ }
806
+
807
+ protected getPluginName(): string {
808
+ return 'my-plugin'
809
+ }
810
+
811
+ protected addPluginHooks(plugin: Plugin): void {
812
+ plugin.buildStart = () => {
813
+ this.logger.info(`Plugin started with path: ${this.options.path}`)
814
+ }
815
+ }
816
+
817
+ protected destroy(): void {
818
+ super.destroy()
819
+ // Custom cleanup logic, e.g. close connections, stop watchers
820
+ }
821
+ }
822
+
823
+ // Basic usage
824
+ export const myPlugin = createPluginFactory(MyPlugin)
825
+
826
+ // With normalizer (supports shorthand string config)
827
+ export const myPluginWithNormalizer = createPluginFactory(MyPlugin, opt => (typeof opt === 'string' ? { path: opt } : opt))
828
+ // Usage with shorthand: myPluginWithNormalizer('./custom-path')
829
+ ```
830
+
831
+ ## Sub-path Exports
832
+
833
+ Support importing modules on demand to reduce bundle size:
834
+
835
+ ```typescript
836
+ // Full import
837
+ import { buildProgress, copyFile, loadingManager, BasePlugin, Logger } from '@meng-xi/vite-plugin'
838
+
839
+ // Module-level import
840
+ import { BasePlugin, createPluginFactory } from '@meng-xi/vite-plugin/factory'
841
+ import { Logger } from '@meng-xi/vite-plugin/logger'
842
+ import { buildProgress, copyFile, generateRouter, loadingManager } from '@meng-xi/vite-plugin/plugins'
843
+ import { Validator, readFileContent, writeFileContent } from '@meng-xi/vite-plugin/common'
844
+
845
+ // Type imports (on-demand type definitions from sub-paths)
846
+ import type { PluginWithInstance, PluginFactory, BasePluginOptions } from '@meng-xi/vite-plugin/factory'
847
+ import type { BuildProgressOptions, GenerateVersionOptions, VersionUpdateCheckerOptions, FaviconManagerOptions, LoadingManagerOptions, Icon } from '@meng-xi/vite-plugin/plugins'
848
+ import type { DateFormatOptions } from '@meng-xi/vite-plugin/common'
849
+ ```
850
+
851
+ ## Changelog
852
+
853
+ View [GitHub Releases](https://github.com/MengXi-Studio/vite-plugin/releases)
854
+
855
+ ## Contributing
856
+
857
+ Contributions are welcome! Please follow these steps:
858
+
859
+ 1. Fork this project
860
+ 2. Create a feature branch: `git checkout -b feature/your-feature`
861
+ 3. Commit changes: `git commit -m "feat: your feature description"`
862
+ 4. Push branch: `git push origin feature/your-feature`
863
+ 5. Create a Pull Request
864
+
865
+ ## License
866
+
867
+ [MIT](LICENSE)