@meng-xi/vite-plugin 0.0.9 → 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
@@ -15,7 +15,7 @@
15
15
 
16
16
  ## Features
17
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
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
19
  - **Plugin Development Framework** - Exports core components like BasePlugin, Logger, Validator for building custom Vite plugins
20
20
  - **Complete Lifecycle** - Supports initialization, config resolution, destroy lifecycle management with automatic hook composition
21
21
  - **Type Safe** - Complete TypeScript type definitions with configuration validators ensuring parameter correctness
@@ -46,7 +46,7 @@ pnpm add @meng-xi/vite-plugin -D
46
46
 
47
47
  ```typescript
48
48
  import { defineConfig } from 'vite'
49
- import { buildProgress, copyFile, generateRouter, generateVersion, injectIco, injectLoading } from '@meng-xi/vite-plugin'
49
+ import { buildProgress, copyFile, generateRouter, generateVersion, versionUpdateChecker, faviconManager, loadingManager } from '@meng-xi/vite-plugin'
50
50
 
51
51
  export default defineConfig({
52
52
  plugins: [
@@ -71,11 +71,14 @@ export default defineConfig({
71
71
  outputType: 'both'
72
72
  }),
73
73
 
74
+ // Version update checker (works with generateVersion)
75
+ versionUpdateChecker(),
76
+
74
77
  // Inject website icon (supports string shorthand)
75
- injectIco('/assets'),
78
+ faviconManager('/assets'),
76
79
 
77
- // Inject global Loading
78
- injectLoading({
80
+ // Global Loading state management
81
+ loadingManager({
79
82
  defaultVisible: true,
80
83
  autoHideOn: 'DOMContentLoaded'
81
84
  })
@@ -97,238 +100,17 @@ const routerPlugin = generateRouter({ watch: true }) as PluginWithInstance<Gener
97
100
  console.log(routerPlugin.pluginInstance?.options)
98
101
  ```
99
102
 
100
- ### Developing Custom Plugins
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
- // Custom cleanup logic, e.g. close connections, stop watchers
133
- }
134
- }
135
-
136
- // Basic usage
137
- export const myPlugin = createPluginFactory(MyPlugin)
138
-
139
- // With normalizer (supports shorthand string config)
140
- export const myPluginWithNormalizer = createPluginFactory(MyPlugin, opt => (typeof opt === 'string' ? { path: opt } : opt))
141
- // Usage with shorthand: myPluginWithNormalizer('./custom-path')
142
- ```
143
-
144
- ## Plugin Development Framework
145
-
146
- ### BasePlugin Core Concepts
147
-
148
- `BasePlugin` is the base class for all plugins, providing complete lifecycle management and development conventions:
149
-
150
- #### Lifecycle
151
-
152
- | Phase | Method | Description |
153
- | ----------------- | ------------------ | -------------------------------------------------------------- |
154
- | Initialization | `constructor` | Merge options, initialize logger and validator |
155
- | Config Resolution | `onConfigResolved` | Called when Vite config is resolved |
156
- | Hook Registration | `addPluginHooks` | Register Vite plugin hooks |
157
- | Destroy | `destroy` | Automatically called during `closeBundle` for resource cleanup |
158
-
159
- #### Automatic Hook Composition
160
-
161
- The `toPlugin()` method automatically composes the following hooks:
162
-
163
- - **configResolved** - Base class `onConfigResolved` runs first, then subclass hook
164
- - **closeBundle** - Subclass hook runs first, then base class `destroy`
165
-
166
- > Subclasses don't need to manually register `closeBundle` hooks for cleanup — just override the `destroy()` method.
167
-
168
- #### Required Methods
169
-
170
- | Method | Description |
171
- | ------------------------ | --------------------- |
172
- | `getPluginName()` | Return plugin name |
173
- | `addPluginHooks(plugin)` | Add Vite plugin hooks |
174
-
175
- #### Optional Methods
176
-
177
- | Method | Default Behavior | Description |
178
- | -------------------------- | ----------------- | ------------------------------------------- |
179
- | `getDefaultOptions()` | Returns `{}` | Provide plugin default options |
180
- | `validateOptions()` | No validation | Validate configuration parameters |
181
- | `getEnforce()` | `undefined` | Plugin execution order (`'pre'` / `'post'`) |
182
- | `onConfigResolved(config)` | Store config | Config resolved callback |
183
- | `destroy()` | Unregister logger | Cleanup logic when plugin is destroyed |
184
-
185
- #### Built-in Properties
186
-
187
- | Property | Type | Description |
188
- | ------------ | ------------------------ | ----------------------------- |
189
- | `options` | `Required<T>` | Merged complete configuration |
190
- | `logger` | `PluginLogger` | Plugin logger |
191
- | `validator` | `Validator<T>` | Configuration validator |
192
- | `viteConfig` | `ResolvedConfig \| null` | Resolved Vite configuration |
193
-
194
- #### Error Handling Strategy
195
-
196
- Control error behavior via the `errorStrategy` configuration option:
197
-
198
- - `'throw'` (default) - Log error and throw exception, halting the build
199
- - `'log'` - Log error but don't throw, continue execution
200
- - `'ignore'` - Log error but don't throw, continue execution
201
-
202
- Wrap error-prone operations with `safeExecute` / `safeExecuteSync`:
203
-
204
- ```typescript
205
- // Async safe execution
206
- const result = await this.safeExecute(async () => {
207
- return await someAsyncOperation()
208
- }, 'Execute async operation')
209
-
210
- // Sync safe execution
211
- const value = this.safeExecuteSync(() => {
212
- return someSyncOperation()
213
- }, 'Execute sync operation')
214
- ```
215
-
216
- ### createPluginFactory
217
-
218
- Create plugin factory functions with optional normalizer support:
219
-
220
- ```typescript
221
- // Basic usage
222
- const myPlugin = createPluginFactory(MyPlugin)
223
-
224
- // With normalizer (supports shorthand string config)
225
- const myPlugin = createPluginFactory(MyPlugin, opt => (typeof opt === 'string' ? { path: opt } : opt))
226
-
227
- // Usage with shorthand
228
- myPlugin('./custom-path')
229
- ```
230
-
231
- ### Validator
232
-
233
- Fluent configuration validator with chainable API:
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 cannot be empty')
257
- .validate()
258
- ```
259
-
260
- | Method | Description |
261
- | ------------ | --------------------------------------------------------------- |
262
- | `field()` | Specify the field to validate |
263
- | `required()` | Mark field as required |
264
- | `string()` | Validate field value is a string type |
265
- | `boolean()` | Validate field value is a boolean type |
266
- | `number()` | Validate field value is a number type |
267
- | `array()` | Validate field value is an array type |
268
- | `object()` | Validate field value is an object type |
269
- | `default()` | Set default value for field (only when value is undefined/null) |
270
- | `custom()` | Validate field value with a custom function |
271
- | `validate()` | Execute validation, throws error on failure |
272
-
273
- ### Logger
274
-
275
- Global singleton log manager providing independent log control for each plugin:
276
-
277
- ```typescript
278
- import { Logger } from '@meng-xi/vite-plugin/logger'
279
-
280
- // Create logger (usually called automatically by BasePlugin)
281
- Logger.create({ name: 'my-plugin', enabled: true })
282
-
283
- // Unregister plugin log config (automatically called on plugin destroy)
284
- Logger.unregister('my-plugin')
285
-
286
- // Destroy singleton (for test scenarios)
287
- Logger.destroy()
288
- ```
289
-
290
- Log output format:
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
- ### Common Utilities
300
-
301
- Exported via `@meng-xi/vite-plugin/common`, reusable in custom plugins:
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
- | Function | Description |
309
- | ---------------------- | --------------------------------------------------------------- |
310
- | `deepMerge()` | Deep merge objects (undefined skipped, arrays overwritten) |
311
- | `formatDate()` | Format date with `{YYYY}`, `{MM}`, `{DD}` etc. placeholders |
312
- | `parseTemplate()` | Parse template string, replace placeholders |
313
- | `toCamelCase()` | Convert to camelCase |
314
- | `toPascalCase()` | Convert to PascalCase |
315
- | `stripJsonComments()` | Remove comments from JSON string |
316
- | `generateRandomHash()` | Generate random hash string (1-64 characters) |
317
- | `readFileContent()` | Async read file content |
318
- | `writeFileContent()` | Async write file content |
319
- | `fileExists()` | Async check if file exists |
320
- | `copySourceToTarget()` | Copy files or directories with incremental copy and concurrency |
321
-
322
103
  ## Built-in Plugins
323
104
 
324
- | Plugin | Description |
325
- | --------------- | ----------------------------------------------------------------------------------------- |
326
- | buildProgress | Real-time build progress bar in terminal, supports bar / spinner / minimal |
327
- | copyFile | Copy files or directories after build, supports incremental copying |
328
- | generateRouter | Auto-generate router config from pages.json (uni-app) |
329
- | generateVersion | Auto-generate version numbers, supports file output and global variable injection |
330
- | injectIco | Inject website icon links into HTML files, supports string shorthand config |
331
- | injectLoading | Inject global Loading state management with request interception and white-screen Loading |
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 |
332
114
 
333
115
  ### buildProgress
334
116
 
@@ -470,18 +252,18 @@ generateRouter({
470
252
 
471
253
  Automatically generate version numbers during the Vite build process.
472
254
 
473
- | Option | Type | Default | Description |
474
- | ------------ | --------------------------------------------------------------------------------- | ------------------- | ------------------------------ |
475
- | format | `'timestamp'` \| `'date'` \| `'datetime'` \| `'semver'` \| `'hash'` \| `'custom'` | `'timestamp'` | Version format |
476
- | customFormat | `string` | - | Custom format template |
477
- | semverBase | `string` | `'1.0.0'` | Semantic version base |
478
- | outputType | `'file'` \| `'define'` \| `'both'` | `'file'` | Output type |
479
- | outputFile | `string` | `'version.json'` | Output file path |
480
- | defineName | `string` | `'__APP_VERSION__'` | Global variable name to inject |
481
- | hashLength | `number` | `8` | Hash length (1-32) |
482
- | prefix | `string` | - | Version number prefix |
483
- | suffix | `string` | - | Version number suffix |
484
- | extra | `Record<string, unknown>` | - | Extra info (JSON file only) |
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) |
485
267
 
486
268
  **customFormat placeholders:**
487
269
 
@@ -533,7 +315,73 @@ generateVersion({
533
315
  })
534
316
  ```
535
317
 
536
- ### injectIco
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
537
385
 
538
386
  Inject website icon links into the head of HTML files during the Vite build process. Supports string shorthand config.
539
387
 
@@ -567,13 +415,13 @@ Inject website icon links into the head of HTML files during the Vite build proc
567
415
 
568
416
  ```typescript
569
417
  // Use default config
570
- injectIco()
418
+ faviconManager()
571
419
 
572
420
  // String shorthand (set base path)
573
- injectIco('/assets')
421
+ faviconManager('/assets')
574
422
 
575
423
  // Custom icon array
576
- injectIco({
424
+ faviconManager({
577
425
  base: '/assets',
578
426
  icons: [
579
427
  { rel: 'icon', href: '/favicon.svg', type: 'image/svg+xml' },
@@ -583,12 +431,12 @@ injectIco({
583
431
  })
584
432
 
585
433
  // Custom complete link tag
586
- injectIco({
434
+ faviconManager({
587
435
  link: '<link rel="icon" href="/favicon.svg" type="image/svg+xml" />'
588
436
  })
589
437
 
590
438
  // With file copying
591
- injectIco({
439
+ faviconManager({
592
440
  base: '/assets',
593
441
  copyOptions: {
594
442
  sourceDir: 'src/assets/icons',
@@ -597,7 +445,7 @@ injectIco({
597
445
  })
598
446
  ```
599
447
 
600
- ### injectLoading
448
+ ### loadingManager
601
449
 
602
450
  Inject global Loading state management with XHR/Fetch request interception, white-screen Loading, custom styles, and lifecycle callbacks.
603
451
 
@@ -711,30 +559,30 @@ Access via `window.__LOADING_MANAGER__`:
711
559
 
712
560
  ```typescript
713
561
  // White-screen Loading: visible on page load, auto-hide on DOMContentLoaded
714
- injectLoading({ defaultVisible: true, autoHideOn: 'DOMContentLoaded' })
562
+ loadingManager({ defaultVisible: true, autoHideOn: 'DOMContentLoaded' })
715
563
 
716
564
  // White-screen Loading: auto-hide after all resources loaded
717
- injectLoading({ defaultVisible: true, autoHideOn: 'load' })
565
+ loadingManager({ defaultVisible: true, autoHideOn: 'load' })
718
566
 
719
567
  // Vue/React SPA: visible on white screen, manually hide after framework renders
720
- injectLoading({ defaultVisible: true, autoHideOn: 'manual' })
568
+ loadingManager({ defaultVisible: true, autoHideOn: 'manual' })
721
569
  // In app entry: window.__LOADING_MANAGER__.hide()
722
570
 
723
571
  // Auto-intercept all requests
724
- injectLoading({ autoBind: 'all' })
572
+ loadingManager({ autoBind: 'all' })
725
573
 
726
574
  // Custom styles + request filtering
727
- injectLoading({
575
+ loadingManager({
728
576
  style: { overlayColor: 'rgba(0,0,0,0.5)', spinnerColor: '#fff', backdropBlur: true },
729
577
  autoBind: 'fetch',
730
578
  requestFilter: { excludeUrls: [/\/api\/health/], excludeUrlPrefixes: ['http://localhost'] }
731
579
  })
732
580
 
733
581
  // Debounced hide (prevent rapid flashing)
734
- injectLoading({ debounceHide: { enabled: true, duration: 100 } })
582
+ loadingManager({ debounceHide: { enabled: true, duration: 100 } })
735
583
 
736
584
  // Lifecycle callbacks
737
- injectLoading({
585
+ loadingManager({
738
586
  callbacks: {
739
587
  onBeforeShow: 'if (shouldSkip) return false;',
740
588
  onShow: 'console.log("loading shown")',
@@ -744,45 +592,274 @@ injectLoading({
744
592
  })
745
593
 
746
594
  // Manual control
747
- injectLoading()
595
+ loadingManager()
748
596
  window.__LOADING_MANAGER__.show('Saving...')
749
597
  window.__LOADING_MANAGER__.hide()
750
598
  window.__LOADING_MANAGER__.toggle()
751
599
  window.__LOADING_MANAGER__.disablePointerEvents()
752
600
  ```
753
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
+
754
831
  ## Sub-path Exports
755
832
 
756
833
  Support importing modules on demand to reduce bundle size:
757
834
 
758
835
  ```typescript
759
836
  // Full import
760
- import { buildProgress, copyFile, injectLoading, BasePlugin, Logger } from '@meng-xi/vite-plugin'
837
+ import { buildProgress, copyFile, loadingManager, BasePlugin, Logger } from '@meng-xi/vite-plugin'
761
838
 
762
839
  // Module-level import
763
840
  import { BasePlugin, createPluginFactory } from '@meng-xi/vite-plugin/factory'
764
841
  import { Logger } from '@meng-xi/vite-plugin/logger'
765
- import { buildProgress, copyFile, generateRouter, injectLoading } from '@meng-xi/vite-plugin/plugins'
842
+ import { buildProgress, copyFile, generateRouter, loadingManager } from '@meng-xi/vite-plugin/plugins'
766
843
  import { Validator, readFileContent, writeFileContent } from '@meng-xi/vite-plugin/common'
767
844
 
768
845
  // Type imports (on-demand type definitions from sub-paths)
769
846
  import type { PluginWithInstance, PluginFactory, BasePluginOptions } from '@meng-xi/vite-plugin/factory'
770
- import type { BuildProgressOptions, GenerateVersionOptions, InjectIcoOptions, InjectLoadingOptions, Icon } from '@meng-xi/vite-plugin/plugins'
847
+ import type { BuildProgressOptions, GenerateVersionOptions, VersionUpdateCheckerOptions, FaviconManagerOptions, LoadingManagerOptions, Icon } from '@meng-xi/vite-plugin/plugins'
771
848
  import type { DateFormatOptions } from '@meng-xi/vite-plugin/common'
772
849
  ```
773
850
 
774
851
  ## Changelog
775
852
 
776
- See [GitHub Releases](https://github.com/MengXi-Studio/vite-plugin/releases)
853
+ View [GitHub Releases](https://github.com/MengXi-Studio/vite-plugin/releases)
777
854
 
778
855
  ## Contributing
779
856
 
780
857
  Contributions are welcome! Please follow these steps:
781
858
 
782
- 1. Fork the repository
859
+ 1. Fork this project
783
860
  2. Create a feature branch: `git checkout -b feature/your-feature`
784
861
  3. Commit changes: `git commit -m "feat: your feature description"`
785
- 4. Push to branch: `git push origin feature/your-feature`
862
+ 4. Push branch: `git push origin feature/your-feature`
786
863
  5. Create a Pull Request
787
864
 
788
865
  ## License