@meng-xi/vite-plugin 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README-en.md +43 -17
  2. package/README.md +56 -30
  3. package/dist/common/concurrency/index.cjs +1 -0
  4. package/dist/common/concurrency/index.d.cts +26 -0
  5. package/dist/common/concurrency/index.d.mts +26 -0
  6. package/dist/common/concurrency/index.d.ts +26 -0
  7. package/dist/common/concurrency/index.mjs +1 -0
  8. package/dist/common/format/index.cjs +1 -1
  9. package/dist/common/format/index.d.cts +19 -1
  10. package/dist/common/format/index.d.mts +19 -1
  11. package/dist/common/format/index.d.ts +19 -1
  12. package/dist/common/format/index.mjs +1 -1
  13. package/dist/common/fs/index.cjs +1 -1
  14. package/dist/common/fs/index.d.cts +19 -1
  15. package/dist/common/fs/index.d.mts +19 -1
  16. package/dist/common/fs/index.d.ts +19 -1
  17. package/dist/common/fs/index.mjs +1 -1
  18. package/dist/common/index.cjs +1 -1
  19. package/dist/common/index.d.cts +3 -2
  20. package/dist/common/index.d.mts +3 -2
  21. package/dist/common/index.d.ts +3 -2
  22. package/dist/common/index.mjs +1 -1
  23. package/dist/index.cjs +1 -1
  24. package/dist/index.d.cts +5 -3
  25. package/dist/index.d.mts +5 -3
  26. package/dist/index.d.ts +5 -3
  27. package/dist/index.mjs +1 -1
  28. package/dist/plugins/assetManifest/index.cjs +1 -1
  29. package/dist/plugins/assetManifest/index.mjs +1 -1
  30. package/dist/plugins/autoImport/index.cjs +13 -13
  31. package/dist/plugins/autoImport/index.mjs +8 -8
  32. package/dist/plugins/bundleAnalyzer/index.cjs +1 -1
  33. package/dist/plugins/bundleAnalyzer/index.mjs +1 -1
  34. package/dist/plugins/compressAssets/index.cjs +1 -1
  35. package/dist/plugins/compressAssets/index.mjs +1 -1
  36. package/dist/plugins/copyFile/index.cjs +1 -1
  37. package/dist/plugins/copyFile/index.mjs +1 -1
  38. package/dist/plugins/envGuard/index.cjs +5 -5
  39. package/dist/plugins/envGuard/index.mjs +8 -8
  40. package/dist/plugins/faviconManager/index.cjs +1 -1
  41. package/dist/plugins/faviconManager/index.mjs +1 -1
  42. package/dist/plugins/generateRouter/index.cjs +62 -5
  43. package/dist/plugins/generateRouter/index.d.cts +29 -1
  44. package/dist/plugins/generateRouter/index.d.mts +29 -1
  45. package/dist/plugins/generateRouter/index.d.ts +29 -1
  46. package/dist/plugins/generateRouter/index.mjs +62 -5
  47. package/dist/plugins/generateVersion/index.cjs +1 -1
  48. package/dist/plugins/generateVersion/index.mjs +1 -1
  49. package/dist/plugins/imageOptimizer/index.cjs +5 -0
  50. package/dist/plugins/imageOptimizer/index.d.cts +242 -0
  51. package/dist/plugins/imageOptimizer/index.d.mts +242 -0
  52. package/dist/plugins/imageOptimizer/index.d.ts +242 -0
  53. package/dist/plugins/imageOptimizer/index.mjs +5 -0
  54. package/dist/plugins/index.cjs +1 -1
  55. package/dist/plugins/index.d.cts +2 -1
  56. package/dist/plugins/index.d.mts +2 -1
  57. package/dist/plugins/index.d.ts +2 -1
  58. package/dist/plugins/index.mjs +1 -1
  59. package/package.json +15 -1
package/README-en.md CHANGED
@@ -15,10 +15,10 @@
15
15
 
16
16
  ## Features
17
17
 
18
- - **Ready to Use** - 12 practical plugins covering auto-import, build progress, bundle analysis & compression, file copying, environment variable validation, route generation, version management, HTML injection, favicon
19
- management, global Loading, and more
18
+ - **Ready to Use** - 14 practical plugins covering auto-import, build progress, bundle analysis & compression, file copying, environment variable validation, route generation, version management, HTML injection, favicon
19
+ management, global Loading, image optimization, and more
20
20
  - **Plugin Development Framework** - Exports core components like BasePlugin, Logger, and Validator to quickly build custom Vite plugins
21
- - **Common Utility Library** - Built-in 7 Common utility modules supporting on-demand sub-path imports
21
+ - **Common Utility Library** - Built-in 8 Common utility modules supporting on-demand sub-path imports
22
22
  - **Type Safe** - Complete TypeScript type definitions with configuration validators
23
23
  - **On-demand Import** - Supports sub-path exports to reduce bundle size
24
24
 
@@ -41,10 +41,26 @@ pnpm add @meng-xi/vite-plugin -D
41
41
 
42
42
  ```typescript
43
43
  import { defineConfig } from 'vite'
44
- import { autoImport, buildProgress, bundleAnalyzer, compressAssets, copyFile, envGuard, generateRouter, generateVersion, versionUpdateChecker, htmlInject, faviconManager, loadingManager } from '@meng-xi/vite-plugin'
44
+ import {
45
+ assetManifest,
46
+ autoImport,
47
+ buildProgress,
48
+ bundleAnalyzer,
49
+ compressAssets,
50
+ copyFile,
51
+ envGuard,
52
+ generateRouter,
53
+ generateVersion,
54
+ versionUpdateChecker,
55
+ htmlInject,
56
+ faviconManager,
57
+ loadingManager,
58
+ imageOptimizer
59
+ } from '@meng-xi/vite-plugin'
45
60
 
46
61
  export default defineConfig({
47
62
  plugins: [
63
+ assetManifest({ outputFormat: 'vite', groupByEntry: true }),
48
64
  autoImport({ imports: { vue: ['ref', 'reactive', 'computed'] }, dts: 'src/auto-imports.d.ts' }),
49
65
  buildProgress(),
50
66
  bundleAnalyzer({ outputFormat: 'both' }),
@@ -56,7 +72,8 @@ export default defineConfig({
56
72
  versionUpdateChecker(),
57
73
  htmlInject({ rules: [{ id: 'meta', content: '<meta name="description" content="My App">', position: 'head-end' }] }),
58
74
  faviconManager('/assets'),
59
- loadingManager({ defaultVisible: true, autoHideOn: 'DOMContentLoaded' })
75
+ loadingManager({ defaultVisible: true, autoHideOn: 'DOMContentLoaded' }),
76
+ imageOptimizer({ quality: { jpeg: 80, webp: 75 }, convertToWebp: { png: true } })
60
77
  ]
61
78
  })
62
79
  ```
@@ -65,6 +82,7 @@ export default defineConfig({
65
82
 
66
83
  | Plugin | Description |
67
84
  | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
85
+ | [assetManifest](https://mengxi-studio.github.io/vite-plugin/en/plugins/asset-manifest.html) | Build artifact manifest generation with multiple output formats, entry grouping, and runtime injection |
68
86
  | [autoImport](https://mengxi-studio.github.io/vite-plugin/en/plugins/auto-import.html) | Auto-inject import statements with preset mappings, directory scanning, and Vue template auto-import |
69
87
  | [buildProgress](https://mengxi-studio.github.io/vite-plugin/en/plugins/build-progress.html) | Real-time terminal build progress bar, supports bar / spinner / minimal |
70
88
  | [bundleAnalyzer](https://mengxi-studio.github.io/vite-plugin/en/plugins/bundle-analyzer.html) | Bundle volume analysis with JSON/HTML reports, gzip calculation, threshold alerts, and build diff |
@@ -77,6 +95,7 @@ export default defineConfig({
77
95
  | [htmlInject](https://mengxi-studio.github.io/vite-plugin/en/plugins/html-inject.html) | HTML content injection with multiple positions, selector targeting, conditional injection, template variables, and security filtering |
78
96
  | [loadingManager](https://mengxi-studio.github.io/vite-plugin/en/plugins/loading-manager.html) | Global Loading state management with request interception, debounce, transition animations, and white-screen Loading |
79
97
  | [versionUpdateChecker](https://mengxi-studio.github.io/vite-plugin/en/plugins/version-update-checker.html) | Runtime version update checking with multiple prompt styles and custom callbacks |
98
+ | [imageOptimizer](https://mengxi-studio.github.io/vite-plugin/en/plugins/image-optimizer.html) | Image optimization & format conversion, supports JPEG/PNG/WebP/AVIF/GIF/TIFF/SVG, concurrent processing and statistics report |
80
99
 
81
100
  ## Plugin Development Framework
82
101
 
@@ -132,10 +151,13 @@ export const myPlugin = createPluginFactory(MyPlugin)
132
151
  Built-in general-purpose utility function library, organized by functional modules, supporting on-demand sub-path imports.
133
152
 
134
153
  ```typescript
135
- // Formatting: date params, template variable replacement, file size, date formatting
136
- import { getDateFormatParams, parseTemplate, formatDate, formatFileSize } from '@meng-xi/vite-plugin/common/format'
154
+ // Formatting: date params, template variable replacement, file size, date formatting, compression ratio
155
+ import { getDateFormatParams, parseTemplate, formatDate, formatFileSize, calcRatio } from '@meng-xi/vite-plugin/common/format'
137
156
 
138
- // File system: file copy, directory scan, safe write, change detection
157
+ // Concurrency: batch execution with concurrency limit
158
+ import { runWithConcurrency } from '@meng-xi/vite-plugin/common/concurrency'
159
+
160
+ // File system: file copy, directory scan, safe write, change detection, report path resolution
139
161
  import { copySourceToTarget, scanDirectory, writeFileSyncSafely, shouldUpdateFileContent } from '@meng-xi/vite-plugin/common/fs'
140
162
 
141
163
  // HTML: tag injection, content sanitization, attribute escaping
@@ -154,15 +176,16 @@ import { Validator, validateGlobalName, validateNoScriptInTemplate } from '@meng
154
176
  import { normalizePath, isExtensionIncluded, isPathExcluded, isPreCompressed } from '@meng-xi/vite-plugin/common/path'
155
177
  ```
156
178
 
157
- | Sub-path | Description |
158
- | -------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
159
- | [`common/format`](https://mengxi-studio.github.io/vite-plugin/en/common/format.html) | Date param extraction, template variable replacement `{{key}}`, date formatting `{YYYY}`, file size |
160
- | [`common/fs`](https://mengxi-studio.github.io/vite-plugin/en/common/fs.html) | File/directory copy, directory scan, sync safe write, file change detection |
161
- | [`common/html`](https://mengxi-studio.github.io/vite-plugin/en/common/html.html) | HTML tag injection, dual-zone injection, content sanitization, HTML attribute value escaping |
162
- | [`common/script`](https://mengxi-studio.github.io/vite-plugin/en/common/script.html) | Callback body wrapping into safe function expressions (with try-catch) |
163
- | [`common/ui`](https://mengxi-studio.github.io/vite-plugin/en/common/ui.html) | Terminal ANSI color code constants |
164
- | [`common/validation`](https://mengxi-studio.github.io/vite-plugin/en/common/validation.html) | Chain-style config validator, global name validation, script detection, callback field validation |
165
- | [`common/path`](https://mengxi-studio.github.io/vite-plugin/en/common/path.html) | Path normalization, extension filtering, path exclusion matching, pre-compression format detection |
179
+ | Sub-path | Description |
180
+ | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
181
+ | [`common/format`](https://mengxi-studio.github.io/vite-plugin/en/common/format.html) | Date param extraction, template variable replacement `{{key}}`, date formatting `{YYYY}`, file size, compression ratio |
182
+ | [`common/concurrency`](https://mengxi-studio.github.io/vite-plugin/en/common/concurrency.html) | Batch async execution with concurrency limit |
183
+ | [`common/fs`](https://mengxi-studio.github.io/vite-plugin/en/common/fs.html) | File/directory copy, directory scan, sync safe write, file change detection |
184
+ | [`common/html`](https://mengxi-studio.github.io/vite-plugin/en/common/html.html) | HTML tag injection, dual-zone injection, content sanitization, HTML attribute value escaping |
185
+ | [`common/script`](https://mengxi-studio.github.io/vite-plugin/en/common/script.html) | Callback body wrapping into safe function expressions (with try-catch) |
186
+ | [`common/ui`](https://mengxi-studio.github.io/vite-plugin/en/common/ui.html) | Terminal ANSI color code constants |
187
+ | [`common/validation`](https://mengxi-studio.github.io/vite-plugin/en/common/validation.html) | Chain-style config validator, global name validation, script detection, callback field validation |
188
+ | [`common/path`](https://mengxi-studio.github.io/vite-plugin/en/common/path.html) | Path normalization, extension filtering, path exclusion matching, pre-compression format detection |
166
189
 
167
190
  ## Sub-path Exports
168
191
 
@@ -174,6 +197,8 @@ import { normalizePath, isExtensionIncluded, isPathExcluded, isPreCompressed } f
174
197
  | `@meng-xi/vite-plugin/plugins` | All plugins |
175
198
  | `@meng-xi/vite-plugin/common` | All utility functions |
176
199
  | `@meng-xi/vite-plugin/common/*` | Utility sub-modules |
200
+ | `@meng-xi/vite-plugin/common/concurrency` | Concurrency control utilities |
201
+ | `@meng-xi/vite-plugin/plugins/asset-manifest` | assetManifest plugin |
177
202
  | `@meng-xi/vite-plugin/plugins/auto-import` | autoImport plugin |
178
203
  | `@meng-xi/vite-plugin/plugins/build-progress` | buildProgress plugin |
179
204
  | `@meng-xi/vite-plugin/plugins/bundle-analyzer` | bundleAnalyzer plugin |
@@ -186,6 +211,7 @@ import { normalizePath, isExtensionIncluded, isPathExcluded, isPreCompressed } f
186
211
  | `@meng-xi/vite-plugin/plugins/html-inject` | htmlInject plugin |
187
212
  | `@meng-xi/vite-plugin/plugins/loading-manager` | loadingManager plugin |
188
213
  | `@meng-xi/vite-plugin/plugins/version-update-checker` | versionUpdateChecker plugin |
214
+ | `@meng-xi/vite-plugin/plugins/image-optimizer` | imageOptimizer plugin |
189
215
 
190
216
  ## License
191
217
 
package/README.md CHANGED
@@ -15,9 +15,9 @@
15
15
 
16
16
  ## 特性
17
17
 
18
- - **开箱即用** - 12 个实用插件,覆盖自动导入、构建进度、产物分析与压缩、文件复制、环境变量校验、路由生成、版本管理、HTML 注入、图标管理、全局 Loading 等场景
18
+ - **开箱即用** - 14 个实用插件,覆盖自动导入、构建进度、产物分析与压缩、文件复制、环境变量校验、路由生成、版本管理、HTML 注入、图标管理、全局 Loading、图片优化 等场景
19
19
  - **插件开发框架** - 导出 BasePlugin、Logger、Validator 等核心组件,快速构建自定义 Vite 插件
20
- - **通用工具库** - 内置 7 大 Common 工具模块,支持按需子路径导入
20
+ - **通用工具库** - 内置 8 大 Common 工具模块,支持按需子路径导入
21
21
  - **类型安全** - 完整 TypeScript 类型定义与配置验证器
22
22
  - **按需导入** - 支持子路径导出,减少打包体积
23
23
 
@@ -40,10 +40,26 @@ pnpm add @meng-xi/vite-plugin -D
40
40
 
41
41
  ```typescript
42
42
  import { defineConfig } from 'vite'
43
- import { autoImport, buildProgress, bundleAnalyzer, compressAssets, copyFile, envGuard, generateRouter, generateVersion, versionUpdateChecker, htmlInject, faviconManager, loadingManager } from '@meng-xi/vite-plugin'
43
+ import {
44
+ assetManifest,
45
+ autoImport,
46
+ buildProgress,
47
+ bundleAnalyzer,
48
+ compressAssets,
49
+ copyFile,
50
+ envGuard,
51
+ generateRouter,
52
+ generateVersion,
53
+ versionUpdateChecker,
54
+ htmlInject,
55
+ faviconManager,
56
+ loadingManager,
57
+ imageOptimizer
58
+ } from '@meng-xi/vite-plugin'
44
59
 
45
60
  export default defineConfig({
46
61
  plugins: [
62
+ assetManifest({ outputFormat: 'vite', groupByEntry: true }),
47
63
  autoImport({ imports: { vue: ['ref', 'reactive', 'computed'] }, dts: 'src/auto-imports.d.ts' }),
48
64
  buildProgress(),
49
65
  bundleAnalyzer({ outputFormat: 'both' }),
@@ -55,27 +71,30 @@ export default defineConfig({
55
71
  versionUpdateChecker(),
56
72
  htmlInject({ rules: [{ id: 'meta', content: '<meta name="description" content="My App">', position: 'head-end' }] }),
57
73
  faviconManager('/assets'),
58
- loadingManager({ defaultVisible: true, autoHideOn: 'DOMContentLoaded' })
74
+ loadingManager({ defaultVisible: true, autoHideOn: 'DOMContentLoaded' }),
75
+ imageOptimizer({ quality: { jpeg: 80, webp: 75 }, convertToWebp: { png: true } })
59
76
  ]
60
77
  })
61
78
  ```
62
79
 
63
80
  ## 内置插件
64
81
 
65
- | 插件 | 说明 |
66
- | ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
67
- | [autoImport](https://mengxi-studio.github.io/vite-plugin/plugins/auto-import.html) | 自动注入 import 语句,支持预设映射、目录扫描和 Vue 模板自动导入 |
68
- | [buildProgress](https://mengxi-studio.github.io/vite-plugin/plugins/build-progress.html) | 终端实时构建进度条,支持 bar / spinner / minimal |
69
- | [bundleAnalyzer](https://mengxi-studio.github.io/vite-plugin/plugins/bundle-analyzer.html) | 构建产物体积分析,支持 JSON/HTML 报告、gzip 计算、阈值告警和构建对比 |
70
- | [compressAssets](https://mengxi-studio.github.io/vite-plugin/plugins/compress-assets.html) | 构建产物压缩,支持 gzip / brotli / both,并发压缩和统计报告 |
71
- | [copyFile](https://mengxi-studio.github.io/vite-plugin/plugins/copy-file.html) | 构建完成后复制文件或目录,支持增量复制 |
72
- | [envGuard](https://mengxi-studio.github.io/vite-plugin/plugins/env-guard.html) | 环境变量校验,支持类型检查、范围验证、自定义规则和运行时守卫 |
73
- | [faviconManager](https://mengxi-studio.github.io/vite-plugin/plugins/favicon-manager.html) | 管理网站图标链接注入和文件复制,支持字符串简写配置 |
74
- | [generateRouter](https://mengxi-studio.github.io/vite-plugin/plugins/generate-router.html) | 根据 pages.json 自动生成路由配置与类型声明(uni-app) |
75
- | [generateVersion](https://mengxi-studio.github.io/vite-plugin/plugins/generate-version.html) | 自动生成版本号,支持文件输出和全局变量注入 |
76
- | [htmlInject](https://mengxi-studio.github.io/vite-plugin/plugins/html-inject.html) | HTML 内容注入,支持多种位置、选择器定位、条件注入、模板变量和安全过滤 |
77
- | [loadingManager](https://mengxi-studio.github.io/vite-plugin/plugins/loading-manager.html) | 全局 Loading 状态管理,支持请求拦截、防抖、过渡动画和白屏 Loading |
78
- | [versionUpdateChecker](https://mengxi-studio.github.io/vite-plugin/plugins/version-update-checker.html) | 运行时版本更新检查,支持多种提示样式和自定义回调 |
82
+ | 插件 | 说明 |
83
+ | ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
84
+ | [assetManifest](https://mengxi-studio.github.io/vite-plugin/plugins/asset-manifest.html) | 构建产物资源清单生成,支持多种输出格式、按入口分组和运行时注入 |
85
+ | [autoImport](https://mengxi-studio.github.io/vite-plugin/plugins/auto-import.html) | 自动注入 import 语句,支持预设映射、目录扫描和 Vue 模板自动导入 |
86
+ | [buildProgress](https://mengxi-studio.github.io/vite-plugin/plugins/build-progress.html) | 终端实时构建进度条,支持 bar / spinner / minimal |
87
+ | [bundleAnalyzer](https://mengxi-studio.github.io/vite-plugin/plugins/bundle-analyzer.html) | 构建产物体积分析,支持 JSON/HTML 报告、gzip 计算、阈值告警和构建对比 |
88
+ | [compressAssets](https://mengxi-studio.github.io/vite-plugin/plugins/compress-assets.html) | 构建产物压缩,支持 gzip / brotli / both,并发压缩和统计报告 |
89
+ | [copyFile](https://mengxi-studio.github.io/vite-plugin/plugins/copy-file.html) | 构建完成后复制文件或目录,支持增量复制 |
90
+ | [envGuard](https://mengxi-studio.github.io/vite-plugin/plugins/env-guard.html) | 环境变量校验,支持类型检查、范围验证、自定义规则和运行时守卫 |
91
+ | [faviconManager](https://mengxi-studio.github.io/vite-plugin/plugins/favicon-manager.html) | 管理网站图标链接注入和文件复制,支持字符串简写配置 |
92
+ | [generateRouter](https://mengxi-studio.github.io/vite-plugin/plugins/generate-router.html) | 根据 pages.json 自动生成路由配置与类型声明(uni-app) |
93
+ | [generateVersion](https://mengxi-studio.github.io/vite-plugin/plugins/generate-version.html) | 自动生成版本号,支持文件输出和全局变量注入 |
94
+ | [htmlInject](https://mengxi-studio.github.io/vite-plugin/plugins/html-inject.html) | HTML 内容注入,支持多种位置、选择器定位、条件注入、模板变量和安全过滤 |
95
+ | [loadingManager](https://mengxi-studio.github.io/vite-plugin/plugins/loading-manager.html) | 全局 Loading 状态管理,支持请求拦截、防抖、过渡动画和白屏 Loading |
96
+ | [versionUpdateChecker](https://mengxi-studio.github.io/vite-plugin/plugins/version-update-checker.html) | 运行时版本更新检查,支持多种提示样式和自定义回调 |
97
+ | [imageOptimizer](https://mengxi-studio.github.io/vite-plugin/plugins/image-optimizer.html) | 图片优化压缩与格式转换,支持 JPEG/PNG/WebP/AVIF/GIF/TIFF/SVG,并发处理和统计报告 |
79
98
 
80
99
  ## 插件开发框架
81
100
 
@@ -131,10 +150,13 @@ export const myPlugin = createPluginFactory(MyPlugin)
131
150
  内置通用工具函数库,按功能模块组织,支持子路径按需导入。
132
151
 
133
152
  ```typescript
134
- // 格式化:日期参数、模板变量替换、文件大小、日期格式化
135
- import { getDateFormatParams, parseTemplate, formatDate, formatFileSize } from '@meng-xi/vite-plugin/common/format'
153
+ // 格式化:日期参数、模板变量替换、文件大小、日期格式化、压缩率计算
154
+ import { getDateFormatParams, parseTemplate, formatDate, formatFileSize, calcRatio } from '@meng-xi/vite-plugin/common/format'
136
155
 
137
- // 文件系统:文件复制、目录扫描、安全写入、变更检测
156
+ // 并发控制:带并发限制的批量执行
157
+ import { runWithConcurrency } from '@meng-xi/vite-plugin/common/concurrency'
158
+
159
+ // 文件系统:文件复制、目录扫描、安全写入、变更检测、报告路径解析
138
160
  import { copySourceToTarget, scanDirectory, writeFileSyncSafely, shouldUpdateFileContent } from '@meng-xi/vite-plugin/common/fs'
139
161
 
140
162
  // HTML:标签注入、内容消毒、属性转义
@@ -153,15 +175,16 @@ import { Validator, validateGlobalName, validateNoScriptInTemplate } from '@meng
153
175
  import { normalizePath, isExtensionIncluded, isPathExcluded, isPreCompressed } from '@meng-xi/vite-plugin/common/path'
154
176
  ```
155
177
 
156
- | 子路径 | 描述 |
157
- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
158
- | [`common/format`](https://mengxi-studio.github.io/vite-plugin/common/format.html) | 日期参数提取、模板变量替换 `{{key}}`、日期格式化 `{YYYY}`、文件大小格式化 |
159
- | [`common/fs`](https://mengxi-studio.github.io/vite-plugin/common/fs.html) | 文件/目录复制、目录扫描、同步安全写入、文件变更检测 |
160
- | [`common/html`](https://mengxi-studio.github.io/vite-plugin/common/html.html) | HTML 标签注入、双区域注入、内容安全消毒、HTML 属性值转义 |
161
- | [`common/script`](https://mengxi-studio.github.io/vite-plugin/common/script.html) | 回调函数体包装为安全的函数表达式(含 try-catch) |
162
- | [`common/ui`](https://mengxi-studio.github.io/vite-plugin/common/ui.html) | 终端 ANSI 颜色码常量 |
163
- | [`common/validation`](https://mengxi-studio.github.io/vite-plugin/common/validation.html) | 链式配置验证器、全局名称校验、脚本检测、回调字段校验 |
164
- | [`common/path`](https://mengxi-studio.github.io/vite-plugin/common/path.html) | 路径规范化、扩展名过滤、路径排除匹配、预压缩格式检测 |
178
+ | 子路径 | 描述 |
179
+ | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
180
+ | [`common/format`](https://mengxi-studio.github.io/vite-plugin/common/format.html) | 日期参数提取、模板变量替换 `{{key}}`、日期格式化 `{YYYY}`、文件大小格式化、压缩率计算 |
181
+ | [`common/concurrency`](https://mengxi-studio.github.io/vite-plugin/common/concurrency.html) | 带并发限制的批量异步执行 |
182
+ | [`common/fs`](https://mengxi-studio.github.io/vite-plugin/common/fs.html) | 文件/目录复制、目录扫描、同步安全写入、文件变更检测 |
183
+ | [`common/html`](https://mengxi-studio.github.io/vite-plugin/common/html.html) | HTML 标签注入、双区域注入、内容安全消毒、HTML 属性值转义 |
184
+ | [`common/script`](https://mengxi-studio.github.io/vite-plugin/common/script.html) | 回调函数体包装为安全的函数表达式(含 try-catch) |
185
+ | [`common/ui`](https://mengxi-studio.github.io/vite-plugin/common/ui.html) | 终端 ANSI 颜色码常量 |
186
+ | [`common/validation`](https://mengxi-studio.github.io/vite-plugin/common/validation.html) | 链式配置验证器、全局名称校验、脚本检测、回调字段校验 |
187
+ | [`common/path`](https://mengxi-studio.github.io/vite-plugin/common/path.html) | 路径规范化、扩展名过滤、路径排除匹配、预压缩格式检测 |
165
188
 
166
189
  ## 子路径导出
167
190
 
@@ -173,6 +196,8 @@ import { normalizePath, isExtensionIncluded, isPathExcluded, isPreCompressed } f
173
196
  | `@meng-xi/vite-plugin/plugins` | 所有插件 |
174
197
  | `@meng-xi/vite-plugin/common` | 所有工具函数 |
175
198
  | `@meng-xi/vite-plugin/common/*` | 各工具子模块 |
199
+ | `@meng-xi/vite-plugin/common/concurrency` | 并发控制工具 |
200
+ | `@meng-xi/vite-plugin/plugins/asset-manifest` | assetManifest 插件 |
176
201
  | `@meng-xi/vite-plugin/plugins/auto-import` | autoImport 插件 |
177
202
  | `@meng-xi/vite-plugin/plugins/build-progress` | buildProgress 插件 |
178
203
  | `@meng-xi/vite-plugin/plugins/bundle-analyzer` | bundleAnalyzer 插件 |
@@ -185,6 +210,7 @@ import { normalizePath, isExtensionIncluded, isPathExcluded, isPreCompressed } f
185
210
  | `@meng-xi/vite-plugin/plugins/html-inject` | htmlInject 插件 |
186
211
  | `@meng-xi/vite-plugin/plugins/loading-manager` | loadingManager 插件 |
187
212
  | `@meng-xi/vite-plugin/plugins/version-update-checker` | versionUpdateChecker 插件 |
213
+ | `@meng-xi/vite-plugin/plugins/image-optimizer` | imageOptimizer 插件 |
188
214
 
189
215
  ## License
190
216
 
@@ -0,0 +1 @@
1
+ "use strict";async function runWithConcurrency(n,a,e){const t=[];let r=0;async function i(){for(;r<n.length;){const c=r++,o=await a(n[c]);t[c]=o}}const l=Array(Math.min(e,n.length)).fill(null).map(()=>i());return await Promise.all(l),t}exports.runWithConcurrency=runWithConcurrency;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * 带并发限制的批量执行
3
+ *
4
+ * @template T - 输入项类型
5
+ * @template R - 返回结果类型
6
+ * @param {T[]} items - 待处理项列表
7
+ * @param {(item: T) => Promise<R>} handler - 处理函数
8
+ * @param {number} concurrency - 最大并发数
9
+ * @returns {Promise<R[]>} 处理结果数组,顺序与输入项对应
10
+ *
11
+ * @description 使用工作池模式并发执行异步任务,结果顺序与输入项对应。
12
+ * 当并发数大于等于项数时,所有项同时执行;否则按并发数分批执行。
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * const results = await runWithConcurrency(
17
+ * [1, 2, 3, 4, 5],
18
+ * async (n) => { await delay(100); return n * 2 },
19
+ * 2
20
+ * )
21
+ * // [2, 4, 6, 8, 10]
22
+ * ```
23
+ */
24
+ declare function runWithConcurrency<T, R>(items: T[], handler: (item: T) => Promise<R>, concurrency: number): Promise<R[]>;
25
+
26
+ export { runWithConcurrency };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * 带并发限制的批量执行
3
+ *
4
+ * @template T - 输入项类型
5
+ * @template R - 返回结果类型
6
+ * @param {T[]} items - 待处理项列表
7
+ * @param {(item: T) => Promise<R>} handler - 处理函数
8
+ * @param {number} concurrency - 最大并发数
9
+ * @returns {Promise<R[]>} 处理结果数组,顺序与输入项对应
10
+ *
11
+ * @description 使用工作池模式并发执行异步任务,结果顺序与输入项对应。
12
+ * 当并发数大于等于项数时,所有项同时执行;否则按并发数分批执行。
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * const results = await runWithConcurrency(
17
+ * [1, 2, 3, 4, 5],
18
+ * async (n) => { await delay(100); return n * 2 },
19
+ * 2
20
+ * )
21
+ * // [2, 4, 6, 8, 10]
22
+ * ```
23
+ */
24
+ declare function runWithConcurrency<T, R>(items: T[], handler: (item: T) => Promise<R>, concurrency: number): Promise<R[]>;
25
+
26
+ export { runWithConcurrency };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * 带并发限制的批量执行
3
+ *
4
+ * @template T - 输入项类型
5
+ * @template R - 返回结果类型
6
+ * @param {T[]} items - 待处理项列表
7
+ * @param {(item: T) => Promise<R>} handler - 处理函数
8
+ * @param {number} concurrency - 最大并发数
9
+ * @returns {Promise<R[]>} 处理结果数组,顺序与输入项对应
10
+ *
11
+ * @description 使用工作池模式并发执行异步任务,结果顺序与输入项对应。
12
+ * 当并发数大于等于项数时,所有项同时执行;否则按并发数分批执行。
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * const results = await runWithConcurrency(
17
+ * [1, 2, 3, 4, 5],
18
+ * async (n) => { await delay(100); return n * 2 },
19
+ * 2
20
+ * )
21
+ * // [2, 4, 6, 8, 10]
22
+ * ```
23
+ */
24
+ declare function runWithConcurrency<T, R>(items: T[], handler: (item: T) => Promise<R>, concurrency: number): Promise<R[]>;
25
+
26
+ export { runWithConcurrency };
@@ -0,0 +1 @@
1
+ async function s(n,c,l){const t=[];let r=0;async function o(){for(;r<n.length;){const a=r++,i=await c(n[a]);t[a]=i}}const e=Array(Math.min(l,n.length)).fill(null).map(()=>o());return await Promise.all(e),t}export{s as runWithConcurrency};
@@ -1 +1 @@
1
- "use strict";function t(e,r=2){return e.toString().padStart(r,"0")}function getDateFormatParams(e=new Date){return{YYYY:e.getFullYear().toString(),YY:e.getFullYear().toString().slice(-2),MM:t(e.getMonth()+1),DD:t(e.getDate()),HH:t(e.getHours()),mm:t(e.getMinutes()),ss:t(e.getSeconds()),SSS:t(e.getMilliseconds(),3),timestamp:e.getTime().toString()}}function parseTemplate(e,r){let o=e;for(const[n,a]of Object.entries(r)){const s=n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),i=a.replace(/\$/g,"$$$$");o=o.replace(new RegExp(`\\{\\{${s}\\}\\}`,"g"),i)}return o}function formatDate(e,r){const o=getDateFormatParams(e);let n=r;for(const[a,s]of Object.entries(o))n=n.replace(new RegExp(`\\{${a}\\}`,"g"),s);return n}function formatFileSize(e){return e<1024?`${e}B`:e<1024*1024?`${(e/1024).toFixed(1)}KB`:`${(e/(1024*1024)).toFixed(2)}MB`}exports.formatDate=formatDate,exports.formatFileSize=formatFileSize,exports.getDateFormatParams=getDateFormatParams,exports.parseTemplate=parseTemplate;
1
+ "use strict";function r(t,e=2){return t.toString().padStart(e,"0")}function getDateFormatParams(t=new Date){return{YYYY:t.getFullYear().toString(),YY:t.getFullYear().toString().slice(-2),MM:r(t.getMonth()+1),DD:r(t.getDate()),HH:r(t.getHours()),mm:r(t.getMinutes()),ss:r(t.getSeconds()),SSS:r(t.getMilliseconds(),3),timestamp:t.getTime().toString()}}function parseTemplate(t,e){let o=t;for(const[n,a]of Object.entries(e)){const i=n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),s=a.replace(/\$/g,"$$$$");o=o.replace(new RegExp(`\\{\\{${i}\\}\\}`,"g"),s)}return o}function formatDate(t,e){const o=getDateFormatParams(t);let n=e;for(const[a,i]of Object.entries(o))n=n.replace(new RegExp(`\\{${a}\\}`,"g"),i);return n}function formatFileSize(t){return t<1024?`${t}B`:t<1024*1024?`${(t/1024).toFixed(1)}KB`:`${(t/(1024*1024)).toFixed(2)}MB`}function calcRatio(t,e){return t>0?Number(((1-e/t)*100).toFixed(1)):0}exports.calcRatio=calcRatio,exports.formatDate=formatDate,exports.formatFileSize=formatFileSize,exports.getDateFormatParams=getDateFormatParams,exports.parseTemplate=parseTemplate;
@@ -88,6 +88,24 @@ declare function formatDate(date: Date, format: string): string;
88
88
  * ```
89
89
  */
90
90
  declare function formatFileSize(bytes: number): string;
91
+ /**
92
+ * 计算压缩率百分比
93
+ *
94
+ * @param {number} originalSize - 原始大小(字节)
95
+ * @param {number} compressedSize - 压缩后大小(字节)
96
+ * @returns {number} 压缩率百分比(0-100),如 65.2 表示体积减少 65.2%
97
+ *
98
+ * @description 计算公式: (1 - compressedSize / originalSize) * 100,保留一位小数。
99
+ * 当 originalSize 为 0 时返回 0,避免除零错误。
100
+ *
101
+ * @example
102
+ * ```typescript
103
+ * calcRatio(10000, 6000) // 40.0
104
+ * calcRatio(10000, 10000) // 0
105
+ * calcRatio(0, 0) // 0
106
+ * ```
107
+ */
108
+ declare function calcRatio(originalSize: number, compressedSize: number): number;
91
109
 
92
- export { formatDate, formatFileSize, getDateFormatParams, parseTemplate };
110
+ export { calcRatio, formatDate, formatFileSize, getDateFormatParams, parseTemplate };
93
111
  export type { DateFormatOptions };
@@ -88,6 +88,24 @@ declare function formatDate(date: Date, format: string): string;
88
88
  * ```
89
89
  */
90
90
  declare function formatFileSize(bytes: number): string;
91
+ /**
92
+ * 计算压缩率百分比
93
+ *
94
+ * @param {number} originalSize - 原始大小(字节)
95
+ * @param {number} compressedSize - 压缩后大小(字节)
96
+ * @returns {number} 压缩率百分比(0-100),如 65.2 表示体积减少 65.2%
97
+ *
98
+ * @description 计算公式: (1 - compressedSize / originalSize) * 100,保留一位小数。
99
+ * 当 originalSize 为 0 时返回 0,避免除零错误。
100
+ *
101
+ * @example
102
+ * ```typescript
103
+ * calcRatio(10000, 6000) // 40.0
104
+ * calcRatio(10000, 10000) // 0
105
+ * calcRatio(0, 0) // 0
106
+ * ```
107
+ */
108
+ declare function calcRatio(originalSize: number, compressedSize: number): number;
91
109
 
92
- export { formatDate, formatFileSize, getDateFormatParams, parseTemplate };
110
+ export { calcRatio, formatDate, formatFileSize, getDateFormatParams, parseTemplate };
93
111
  export type { DateFormatOptions };
@@ -88,6 +88,24 @@ declare function formatDate(date: Date, format: string): string;
88
88
  * ```
89
89
  */
90
90
  declare function formatFileSize(bytes: number): string;
91
+ /**
92
+ * 计算压缩率百分比
93
+ *
94
+ * @param {number} originalSize - 原始大小(字节)
95
+ * @param {number} compressedSize - 压缩后大小(字节)
96
+ * @returns {number} 压缩率百分比(0-100),如 65.2 表示体积减少 65.2%
97
+ *
98
+ * @description 计算公式: (1 - compressedSize / originalSize) * 100,保留一位小数。
99
+ * 当 originalSize 为 0 时返回 0,避免除零错误。
100
+ *
101
+ * @example
102
+ * ```typescript
103
+ * calcRatio(10000, 6000) // 40.0
104
+ * calcRatio(10000, 10000) // 0
105
+ * calcRatio(0, 0) // 0
106
+ * ```
107
+ */
108
+ declare function calcRatio(originalSize: number, compressedSize: number): number;
91
109
 
92
- export { formatDate, formatFileSize, getDateFormatParams, parseTemplate };
110
+ export { calcRatio, formatDate, formatFileSize, getDateFormatParams, parseTemplate };
93
111
  export type { DateFormatOptions };
@@ -1 +1 @@
1
- function e(t,n=2){return t.toString().padStart(n,"0")}function g(t=new Date){return{YYYY:t.getFullYear().toString(),YY:t.getFullYear().toString().slice(-2),MM:e(t.getMonth()+1),DD:e(t.getDate()),HH:e(t.getHours()),mm:e(t.getMinutes()),ss:e(t.getSeconds()),SSS:e(t.getMilliseconds(),3),timestamp:t.getTime().toString()}}function c(t,n){let r=t;for(const[o,i]of Object.entries(n)){const a=o.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),s=i.replace(/\$/g,"$$$$");r=r.replace(new RegExp(`\\{\\{${a}\\}\\}`,"g"),s)}return r}function l(t,n){const r=g(t);let o=n;for(const[i,a]of Object.entries(r))o=o.replace(new RegExp(`\\{${i}\\}`,"g"),a);return o}function u(t){return t<1024?`${t}B`:t<1024*1024?`${(t/1024).toFixed(1)}KB`:`${(t/(1024*1024)).toFixed(2)}MB`}export{l as formatDate,u as formatFileSize,g as getDateFormatParams,c as parseTemplate};
1
+ function n(t,e=2){return t.toString().padStart(e,"0")}function c(t=new Date){return{YYYY:t.getFullYear().toString(),YY:t.getFullYear().toString().slice(-2),MM:n(t.getMonth()+1),DD:n(t.getDate()),HH:n(t.getHours()),mm:n(t.getMinutes()),ss:n(t.getSeconds()),SSS:n(t.getMilliseconds(),3),timestamp:t.getTime().toString()}}function u(t,e){let r=t;for(const[o,i]of Object.entries(e)){const a=o.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),g=i.replace(/\$/g,"$$$$");r=r.replace(new RegExp(`\\{\\{${a}\\}\\}`,"g"),g)}return r}function s(t,e){const r=c(t);let o=e;for(const[i,a]of Object.entries(r))o=o.replace(new RegExp(`\\{${i}\\}`,"g"),a);return o}function l(t){return t<1024?`${t}B`:t<1024*1024?`${(t/1024).toFixed(1)}KB`:`${(t/(1024*1024)).toFixed(2)}MB`}function f(t,e){return t>0?Number(((1-e/t)*100).toFixed(1)):0}export{f as calcRatio,s as formatDate,l as formatFileSize,c as getDateFormatParams,u as parseTemplate};
@@ -1 +1 @@
1
- "use strict";const s=require("fs"),u=require("path");function _interopDefaultCompat(t){return t&&typeof t=="object"&&"default"in t?t.default:t}const s__default=_interopDefaultCompat(s),u__default=_interopDefaultCompat(u),R=10;async function checkSourceExists(t){try{await s__default.promises.access(t,s__default.constants.F_OK)}catch(e){const i=e;throw i.code==="ENOENT"?new Error(`\u590D\u5236\u6587\u4EF6\u5931\u8D25\uFF1A\u6E90\u6587\u4EF6\u4E0D\u5B58\u5728 - ${t}`):i.code==="EACCES"?new Error(`\u590D\u5236\u6587\u4EF6\u5931\u8D25\uFF1A\u6CA1\u6709\u6743\u9650\u8BBF\u95EE\u6E90\u6587\u4EF6 - ${t}`):new Error(`\u590D\u5236\u6587\u4EF6\u5931\u8D25\uFF1A\u68C0\u67E5\u6E90\u6587\u4EF6\u65F6\u51FA\u9519 - ${t}\uFF0C\u9519\u8BEF\uFF1A${i.message}`)}}async function g(t){try{await s__default.promises.mkdir(t,{recursive:!0})}catch(e){const i=e;throw i.code==="EACCES"?new Error(`\u590D\u5236\u6587\u4EF6\u5931\u8D25\uFF1A\u6CA1\u6709\u6743\u9650\u521B\u5EFA\u76EE\u6807\u76EE\u5F55 - ${t}`):new Error(`\u590D\u5236\u6587\u4EF6\u5931\u8D25\uFF1A\u521B\u5EFA\u76EE\u6807\u76EE\u5F55\u65F6\u51FA\u9519 - ${t}\uFF0C\u9519\u8BEF\uFF1A${i.message}`)}}async function h(t,e){const i=await s__default.promises.readdir(t,{withFileTypes:!0}),r=[];for(const o of i){const c=u__default.join(t,o.name),l=o.isFile(),F=o.isDirectory();if(r.push({path:c,isFile:l,isDirectory:F}),F&&e){const p=await h(c,e);r.push(...p)}}return r}async function C(t,e){try{const[i,r]=await Promise.all([s__default.promises.stat(t),s__default.promises.stat(e)]);return i.mtimeMs>r.mtimeMs||i.size!==r.size}catch{return!0}}async function D(t){try{return await s__default.promises.access(t,s__default.constants.F_OK),!0}catch{return!1}}async function T(t,e,i){const r=[];let o=0;async function c(){for(;o<t.length;){const F=o++,p=await e(t[F]);r[F]=p}}const l=Array(Math.min(i,t.length)).fill(null).map(()=>c());return await Promise.all(l),r}async function copySourceToTarget(t,e,i){const r=Date.now(),{recursive:o,overwrite:c,incremental:l=!1,parallelLimit:F=R}=i;let p=0,w=0,f=0;if((await s__default.promises.stat(t)).isDirectory()){await g(e);const a=await h(t,o),E=a.filter(n=>n.isFile);f=a.filter(n=>n.isDirectory).length;const S=new Set;for(const n of E){const A=u__default.relative(t,n.path),d=u__default.dirname(u__default.join(e,A));S.add(d)}await Promise.all([...S].map(n=>g(n)));const x=await T(E,async n=>{const A=u__default.relative(t,n.path),d=u__default.join(e,A);let m=c;return m||(m=!await D(d)),l&&m&&(m=await C(n.path,d)),m?(await s__default.promises.copyFile(n.path,d),{copied:!0,skipped:!1}):{copied:!1,skipped:!0}},F);for(const n of x)n.copied&&p++,n.skipped&&w++}else{await g(u__default.dirname(e));let a=c;a||(a=!await D(e)),l&&a&&(a=await C(t,e)),a?(await s__default.promises.copyFile(t,e),p++):w++}const y=Date.now()-r;return{copiedFiles:p,skippedFiles:w,copiedDirs:f,executionTime:y}}async function writeFileContent(t,e){try{await s__default.promises.writeFile(t,e,"utf-8")}catch(i){const r=i;throw r.code==="EACCES"?new Error(`\u5199\u5165\u6587\u4EF6\u5931\u8D25\uFF1A\u6CA1\u6709\u6743\u9650\u5199\u5165\u6587\u4EF6 - ${t}`):new Error(`\u5199\u5165\u6587\u4EF6\u5931\u8D25\uFF1A\u5199\u5165\u6587\u4EF6\u65F6\u51FA\u9519 - ${t}\uFF0C\u9519\u8BEF\uFF1A${r.message}`)}}async function scanDirectory(t,e={}){const{includeExtensions:i=[],excludePatterns:r=[],filter:o}=e,c=[];async function l(F){const p=await s__default.promises.readdir(F,{withFileTypes:!0});for(const w of p){const f=u__default.join(F,w.name);if(w.isDirectory()){await l(f);continue}if(!w.isFile()||r.some(E=>E.startsWith("*")?f.endsWith(E.slice(1)):f.includes(E)))continue;const y=u__default.extname(w.name).toLowerCase();if(i.length>0&&!i.includes(y))continue;const a=await s__default.promises.stat(f);o&&!o(f,y,a.size)||c.push({filePath:f,size:a.size,extension:y})}}return await l(t),c}async function writeJsonReport(t,e,i=2){await writeFileContent(t,JSON.stringify(e,null,i))}function writeFileSyncSafely(t,e){const i=u__default.dirname(t);s__default.existsSync(i)||s__default.mkdirSync(i,{recursive:!0}),s__default.writeFileSync(t,e,"utf-8")}function shouldUpdateFileContent(t,e){if(!s__default.existsSync(t))return!0;try{return s__default.readFileSync(t,"utf-8")!==e}catch{return!0}}exports.checkSourceExists=checkSourceExists,exports.copySourceToTarget=copySourceToTarget,exports.scanDirectory=scanDirectory,exports.shouldUpdateFileContent=shouldUpdateFileContent,exports.writeFileContent=writeFileContent,exports.writeFileSyncSafely=writeFileSyncSafely,exports.writeJsonReport=writeJsonReport;
1
+ "use strict";const s=require("fs"),o=require("path"),common_concurrency_index=require("../concurrency/index.cjs");function _interopDefaultCompat(t){return t&&typeof t=="object"&&"default"in t?t.default:t}const s__default=_interopDefaultCompat(s),o__default=_interopDefaultCompat(o),R=10;async function checkSourceExists(t){try{await s__default.promises.access(t,s__default.constants.F_OK)}catch(e){const u=e;throw u.code==="ENOENT"?new Error(`\u590D\u5236\u6587\u4EF6\u5931\u8D25\uFF1A\u6E90\u6587\u4EF6\u4E0D\u5B58\u5728 - ${t}`):u.code==="EACCES"?new Error(`\u590D\u5236\u6587\u4EF6\u5931\u8D25\uFF1A\u6CA1\u6709\u6743\u9650\u8BBF\u95EE\u6E90\u6587\u4EF6 - ${t}`):new Error(`\u590D\u5236\u6587\u4EF6\u5931\u8D25\uFF1A\u68C0\u67E5\u6E90\u6587\u4EF6\u65F6\u51FA\u9519 - ${t}\uFF0C\u9519\u8BEF\uFF1A${u.message}`)}}async function g(t){try{await s__default.promises.mkdir(t,{recursive:!0})}catch(e){const u=e;throw u.code==="EACCES"?new Error(`\u590D\u5236\u6587\u4EF6\u5931\u8D25\uFF1A\u6CA1\u6709\u6743\u9650\u521B\u5EFA\u76EE\u6807\u76EE\u5F55 - ${t}`):new Error(`\u590D\u5236\u6587\u4EF6\u5931\u8D25\uFF1A\u521B\u5EFA\u76EE\u6807\u76EE\u5F55\u65F6\u51FA\u9519 - ${t}\uFF0C\u9519\u8BEF\uFF1A${u.message}`)}}async function C(t,e){const u=await s__default.promises.readdir(t,{withFileTypes:!0}),n=[];for(const a of u){const F=o__default.join(t,a.name),p=a.isFile(),f=a.isDirectory();if(n.push({path:F,isFile:p,isDirectory:f}),f&&e){const w=await C(F,e);n.push(...w)}}return n}async function h(t,e){try{const[u,n]=await Promise.all([s__default.promises.stat(t),s__default.promises.stat(e)]);return u.mtimeMs>n.mtimeMs||u.size!==n.size}catch{return!0}}async function D(t){try{return await s__default.promises.access(t,s__default.constants.F_OK),!0}catch{return!1}}async function copySourceToTarget(t,e,u){const n=Date.now(),{recursive:a,overwrite:F,incremental:p=!1,parallelLimit:f=R}=u;let w=0,l=0,c=0;if((await s__default.promises.stat(t)).isDirectory()){await g(e);const i=await C(t,a),E=i.filter(r=>r.isFile);c=i.filter(r=>r.isDirectory).length;const x=new Set;for(const r of E){const A=o__default.relative(t,r.path),d=o__default.dirname(o__default.join(e,A));x.add(d)}await Promise.all([...x].map(r=>g(r)));const S=await common_concurrency_index.runWithConcurrency(E,async r=>{const A=o__default.relative(t,r.path),d=o__default.join(e,A);let m=F;return m||(m=!await D(d)),p&&m&&(m=await h(r.path,d)),m?(await s__default.promises.copyFile(r.path,d),{copied:!0,skipped:!1}):{copied:!1,skipped:!0}},f);for(const r of S)r.copied&&w++,r.skipped&&l++}else{await g(o__default.dirname(e));let i=F;i||(i=!await D(e)),p&&i&&(i=await h(t,e)),i?(await s__default.promises.copyFile(t,e),w++):l++}const y=Date.now()-n;return{copiedFiles:w,skippedFiles:l,copiedDirs:c,executionTime:y}}async function writeFileContent(t,e){try{await s__default.promises.writeFile(t,e,"utf-8")}catch(u){const n=u;throw n.code==="EACCES"?new Error(`\u5199\u5165\u6587\u4EF6\u5931\u8D25\uFF1A\u6CA1\u6709\u6743\u9650\u5199\u5165\u6587\u4EF6 - ${t}`):new Error(`\u5199\u5165\u6587\u4EF6\u5931\u8D25\uFF1A\u5199\u5165\u6587\u4EF6\u65F6\u51FA\u9519 - ${t}\uFF0C\u9519\u8BEF\uFF1A${n.message}`)}}async function scanDirectory(t,e={}){const{includeExtensions:u=[],excludePatterns:n=[],filter:a}=e,F=[];async function p(f){const w=await s__default.promises.readdir(f,{withFileTypes:!0});for(const l of w){const c=o__default.join(f,l.name);if(l.isDirectory()){await p(c);continue}if(!l.isFile()||n.some(E=>E.startsWith("*")?c.endsWith(E.slice(1)):c.includes(E)))continue;const y=o__default.extname(l.name).toLowerCase();if(u.length>0&&!u.includes(y))continue;const i=await s__default.promises.stat(c);a&&!a(c,y,i.size)||F.push({filePath:c,size:i.size,extension:y})}}return await p(t),F}async function writeJsonReport(t,e,u=2){await writeFileContent(t,JSON.stringify(e,null,u))}function writeFileSyncSafely(t,e){const u=o__default.dirname(t);s__default.existsSync(u)||s__default.mkdirSync(u,{recursive:!0}),s__default.writeFileSync(t,e,"utf-8")}function shouldUpdateFileContent(t,e){if(!s__default.existsSync(t))return!0;try{return s__default.readFileSync(t,"utf-8")!==e}catch{return!0}}function resolveReportPath(t,e){return e?o__default.isAbsolute(e)?e:o__default.join(t,e):null}exports.checkSourceExists=checkSourceExists,exports.copySourceToTarget=copySourceToTarget,exports.resolveReportPath=resolveReportPath,exports.scanDirectory=scanDirectory,exports.shouldUpdateFileContent=shouldUpdateFileContent,exports.writeFileContent=writeFileContent,exports.writeFileSyncSafely=writeFileSyncSafely,exports.writeJsonReport=writeJsonReport;
@@ -160,6 +160,24 @@ declare function writeFileSyncSafely(filePath: string, content: string): void;
160
160
  * ```
161
161
  */
162
162
  declare function shouldUpdateFileContent(filePath: string, newContent: string): boolean;
163
+ /**
164
+ * 解析报告输出路径
165
+ *
166
+ * @param {string} outDir - 构建输出目录路径
167
+ * @param {string | false} reportPath - 报告文件路径,为 false 时返回 null
168
+ * @returns {string | null} 解析后的绝对路径,reportPath 为 false 时返回 null
169
+ *
170
+ * @description 当 reportPath 为相对路径时,相对于 outDir 解析;
171
+ * 为绝对路径时直接使用;为 false 时返回 null。
172
+ *
173
+ * @example
174
+ * ```typescript
175
+ * resolveReportPath('dist', 'report.json') // 'dist/report.json'
176
+ * resolveReportPath('dist', '/tmp/r.json') // '/tmp/r.json'
177
+ * resolveReportPath('dist', false) // null
178
+ * ```
179
+ */
180
+ declare function resolveReportPath(outDir: string, reportPath: string | false): string | null;
163
181
 
164
- export { checkSourceExists, copySourceToTarget, scanDirectory, shouldUpdateFileContent, writeFileContent, writeFileSyncSafely, writeJsonReport };
182
+ export { checkSourceExists, copySourceToTarget, resolveReportPath, scanDirectory, shouldUpdateFileContent, writeFileContent, writeFileSyncSafely, writeJsonReport };
165
183
  export type { CopyOptions, CopyResult, ScanDirectoryOptions, ScannedFile };
@@ -160,6 +160,24 @@ declare function writeFileSyncSafely(filePath: string, content: string): void;
160
160
  * ```
161
161
  */
162
162
  declare function shouldUpdateFileContent(filePath: string, newContent: string): boolean;
163
+ /**
164
+ * 解析报告输出路径
165
+ *
166
+ * @param {string} outDir - 构建输出目录路径
167
+ * @param {string | false} reportPath - 报告文件路径,为 false 时返回 null
168
+ * @returns {string | null} 解析后的绝对路径,reportPath 为 false 时返回 null
169
+ *
170
+ * @description 当 reportPath 为相对路径时,相对于 outDir 解析;
171
+ * 为绝对路径时直接使用;为 false 时返回 null。
172
+ *
173
+ * @example
174
+ * ```typescript
175
+ * resolveReportPath('dist', 'report.json') // 'dist/report.json'
176
+ * resolveReportPath('dist', '/tmp/r.json') // '/tmp/r.json'
177
+ * resolveReportPath('dist', false) // null
178
+ * ```
179
+ */
180
+ declare function resolveReportPath(outDir: string, reportPath: string | false): string | null;
163
181
 
164
- export { checkSourceExists, copySourceToTarget, scanDirectory, shouldUpdateFileContent, writeFileContent, writeFileSyncSafely, writeJsonReport };
182
+ export { checkSourceExists, copySourceToTarget, resolveReportPath, scanDirectory, shouldUpdateFileContent, writeFileContent, writeFileSyncSafely, writeJsonReport };
165
183
  export type { CopyOptions, CopyResult, ScanDirectoryOptions, ScannedFile };
@@ -160,6 +160,24 @@ declare function writeFileSyncSafely(filePath: string, content: string): void;
160
160
  * ```
161
161
  */
162
162
  declare function shouldUpdateFileContent(filePath: string, newContent: string): boolean;
163
+ /**
164
+ * 解析报告输出路径
165
+ *
166
+ * @param {string} outDir - 构建输出目录路径
167
+ * @param {string | false} reportPath - 报告文件路径,为 false 时返回 null
168
+ * @returns {string | null} 解析后的绝对路径,reportPath 为 false 时返回 null
169
+ *
170
+ * @description 当 reportPath 为相对路径时,相对于 outDir 解析;
171
+ * 为绝对路径时直接使用;为 false 时返回 null。
172
+ *
173
+ * @example
174
+ * ```typescript
175
+ * resolveReportPath('dist', 'report.json') // 'dist/report.json'
176
+ * resolveReportPath('dist', '/tmp/r.json') // '/tmp/r.json'
177
+ * resolveReportPath('dist', false) // null
178
+ * ```
179
+ */
180
+ declare function resolveReportPath(outDir: string, reportPath: string | false): string | null;
163
181
 
164
- export { checkSourceExists, copySourceToTarget, scanDirectory, shouldUpdateFileContent, writeFileContent, writeFileSyncSafely, writeJsonReport };
182
+ export { checkSourceExists, copySourceToTarget, resolveReportPath, scanDirectory, shouldUpdateFileContent, writeFileContent, writeFileSyncSafely, writeJsonReport };
165
183
  export type { CopyOptions, CopyResult, ScanDirectoryOptions, ScannedFile };
@@ -1 +1 @@
1
- import e from"fs";import F from"path";const B=10;async function k(t){try{await e.promises.access(t,e.constants.F_OK)}catch(u){const i=u;throw i.code==="ENOENT"?new Error(`\u590D\u5236\u6587\u4EF6\u5931\u8D25\uFF1A\u6E90\u6587\u4EF6\u4E0D\u5B58\u5728 - ${t}`):i.code==="EACCES"?new Error(`\u590D\u5236\u6587\u4EF6\u5931\u8D25\uFF1A\u6CA1\u6709\u6743\u9650\u8BBF\u95EE\u6E90\u6587\u4EF6 - ${t}`):new Error(`\u590D\u5236\u6587\u4EF6\u5931\u8D25\uFF1A\u68C0\u67E5\u6E90\u6587\u4EF6\u65F6\u51FA\u9519 - ${t}\uFF0C\u9519\u8BEF\uFF1A${i.message}`)}}async function D(t){try{await e.promises.mkdir(t,{recursive:!0})}catch(u){const i=u;throw i.code==="EACCES"?new Error(`\u590D\u5236\u6587\u4EF6\u5931\u8D25\uFF1A\u6CA1\u6709\u6743\u9650\u521B\u5EFA\u76EE\u6807\u76EE\u5F55 - ${t}`):new Error(`\u590D\u5236\u6587\u4EF6\u5931\u8D25\uFF1A\u521B\u5EFA\u76EE\u6807\u76EE\u5F55\u65F6\u51FA\u9519 - ${t}\uFF0C\u9519\u8BEF\uFF1A${i.message}`)}}async function C(t,u){const i=await e.promises.readdir(t,{withFileTypes:!0}),r=[];for(const n of i){const a=F.join(t,n.name),l=n.isFile(),c=n.isDirectory();if(r.push({path:a,isFile:l,isDirectory:c}),c&&u){const p=await C(a,u);r.push(...p)}}return r}async function S(t,u){try{const[i,r]=await Promise.all([e.promises.stat(t),e.promises.stat(u)]);return i.mtimeMs>r.mtimeMs||i.size!==r.size}catch{return!0}}async function x(t){try{return await e.promises.access(t,e.constants.F_OK),!0}catch{return!1}}async function v(t,u,i){const r=[];let n=0;async function a(){for(;n<t.length;){const c=n++,p=await u(t[c]);r[c]=p}}const l=Array(Math.min(i,t.length)).fill(null).map(()=>a());return await Promise.all(l),r}async function T(t,u,i){const r=Date.now(),{recursive:n,overwrite:a,incremental:l=!1,parallelLimit:c=B}=i;let p=0,E=0,w=0;if((await e.promises.stat(t)).isDirectory()){await D(u);const o=await C(t,n),f=o.filter(s=>s.isFile);w=o.filter(s=>s.isDirectory).length;const A=new Set;for(const s of f){const h=F.relative(t,s.path),y=F.dirname(F.join(u,h));A.add(y)}await Promise.all([...A].map(s=>D(s)));const $=await v(f,async s=>{const h=F.relative(t,s.path),y=F.join(u,h);let d=a;return d||(d=!await x(y)),l&&d&&(d=await S(s.path,y)),d?(await e.promises.copyFile(s.path,y),{copied:!0,skipped:!1}):{copied:!1,skipped:!0}},c);for(const s of $)s.copied&&p++,s.skipped&&E++}else{await D(F.dirname(u));let o=a;o||(o=!await x(u)),l&&o&&(o=await S(t,u)),o?(await e.promises.copyFile(t,u),p++):E++}const m=Date.now()-r;return{copiedFiles:p,skippedFiles:E,copiedDirs:w,executionTime:m}}async function g(t,u){try{await e.promises.writeFile(t,u,"utf-8")}catch(i){const r=i;throw r.code==="EACCES"?new Error(`\u5199\u5165\u6587\u4EF6\u5931\u8D25\uFF1A\u6CA1\u6709\u6743\u9650\u5199\u5165\u6587\u4EF6 - ${t}`):new Error(`\u5199\u5165\u6587\u4EF6\u5931\u8D25\uFF1A\u5199\u5165\u6587\u4EF6\u65F6\u51FA\u9519 - ${t}\uFF0C\u9519\u8BEF\uFF1A${r.message}`)}}async function z(t,u={}){const{includeExtensions:i=[],excludePatterns:r=[],filter:n}=u,a=[];async function l(c){const p=await e.promises.readdir(c,{withFileTypes:!0});for(const E of p){const w=F.join(c,E.name);if(E.isDirectory()){await l(w);continue}if(!E.isFile()||r.some(f=>f.startsWith("*")?w.endsWith(f.slice(1)):w.includes(f)))continue;const m=F.extname(E.name).toLowerCase();if(i.length>0&&!i.includes(m))continue;const o=await e.promises.stat(w);n&&!n(w,m,o.size)||a.push({filePath:w,size:o.size,extension:m})}}return await l(t),a}async function P(t,u,i=2){await g(t,JSON.stringify(u,null,i))}function j(t,u){const i=F.dirname(t);e.existsSync(i)||e.mkdirSync(i,{recursive:!0}),e.writeFileSync(t,u,"utf-8")}function O(t,u){if(!e.existsSync(t))return!0;try{return e.readFileSync(t,"utf-8")!==u}catch{return!0}}export{k as checkSourceExists,T as copySourceToTarget,z as scanDirectory,O as shouldUpdateFileContent,g as writeFileContent,j as writeFileSyncSafely,P as writeJsonReport};
1
+ import i from"fs";import s from"path";import{runWithConcurrency as v}from"../concurrency/index.mjs";const B=10;async function k(t){try{await i.promises.access(t,i.constants.F_OK)}catch(u){const e=u;throw e.code==="ENOENT"?new Error(`\u590D\u5236\u6587\u4EF6\u5931\u8D25\uFF1A\u6E90\u6587\u4EF6\u4E0D\u5B58\u5728 - ${t}`):e.code==="EACCES"?new Error(`\u590D\u5236\u6587\u4EF6\u5931\u8D25\uFF1A\u6CA1\u6709\u6743\u9650\u8BBF\u95EE\u6E90\u6587\u4EF6 - ${t}`):new Error(`\u590D\u5236\u6587\u4EF6\u5931\u8D25\uFF1A\u68C0\u67E5\u6E90\u6587\u4EF6\u65F6\u51FA\u9519 - ${t}\uFF0C\u9519\u8BEF\uFF1A${e.message}`)}}async function D(t){try{await i.promises.mkdir(t,{recursive:!0})}catch(u){const e=u;throw e.code==="EACCES"?new Error(`\u590D\u5236\u6587\u4EF6\u5931\u8D25\uFF1A\u6CA1\u6709\u6743\u9650\u521B\u5EFA\u76EE\u6807\u76EE\u5F55 - ${t}`):new Error(`\u590D\u5236\u6587\u4EF6\u5931\u8D25\uFF1A\u521B\u5EFA\u76EE\u6807\u76EE\u5F55\u65F6\u51FA\u9519 - ${t}\uFF0C\u9519\u8BEF\uFF1A${e.message}`)}}async function C(t,u){const e=await i.promises.readdir(t,{withFileTypes:!0}),r=[];for(const c of e){const F=s.join(t,c.name),E=c.isFile(),p=c.isDirectory();if(r.push({path:F,isFile:E,isDirectory:p}),p&&u){const w=await C(F,u);r.push(...w)}}return r}async function S(t,u){try{const[e,r]=await Promise.all([i.promises.stat(t),i.promises.stat(u)]);return e.mtimeMs>r.mtimeMs||e.size!==r.size}catch{return!0}}async function x(t){try{return await i.promises.access(t,i.constants.F_OK),!0}catch{return!1}}async function T(t,u,e){const r=Date.now(),{recursive:c,overwrite:F,incremental:E=!1,parallelLimit:p=B}=e;let w=0,l=0,a=0;if((await i.promises.stat(t)).isDirectory()){await D(u);const o=await C(t,c),f=o.filter(n=>n.isFile);a=o.filter(n=>n.isDirectory).length;const A=new Set;for(const n of f){const h=s.relative(t,n.path),y=s.dirname(s.join(u,h));A.add(y)}await Promise.all([...A].map(n=>D(n)));const g=await v(f,async n=>{const h=s.relative(t,n.path),y=s.join(u,h);let d=F;return d||(d=!await x(y)),E&&d&&(d=await S(n.path,y)),d?(await i.promises.copyFile(n.path,y),{copied:!0,skipped:!1}):{copied:!1,skipped:!0}},p);for(const n of g)n.copied&&w++,n.skipped&&l++}else{await D(s.dirname(u));let o=F;o||(o=!await x(u)),E&&o&&(o=await S(t,u)),o?(await i.promises.copyFile(t,u),w++):l++}const m=Date.now()-r;return{copiedFiles:w,skippedFiles:l,copiedDirs:a,executionTime:m}}async function $(t,u){try{await i.promises.writeFile(t,u,"utf-8")}catch(e){const r=e;throw r.code==="EACCES"?new Error(`\u5199\u5165\u6587\u4EF6\u5931\u8D25\uFF1A\u6CA1\u6709\u6743\u9650\u5199\u5165\u6587\u4EF6 - ${t}`):new Error(`\u5199\u5165\u6587\u4EF6\u5931\u8D25\uFF1A\u5199\u5165\u6587\u4EF6\u65F6\u51FA\u9519 - ${t}\uFF0C\u9519\u8BEF\uFF1A${r.message}`)}}async function j(t,u={}){const{includeExtensions:e=[],excludePatterns:r=[],filter:c}=u,F=[];async function E(p){const w=await i.promises.readdir(p,{withFileTypes:!0});for(const l of w){const a=s.join(p,l.name);if(l.isDirectory()){await E(a);continue}if(!l.isFile()||r.some(f=>f.startsWith("*")?a.endsWith(f.slice(1)):a.includes(f)))continue;const m=s.extname(l.name).toLowerCase();if(e.length>0&&!e.includes(m))continue;const o=await i.promises.stat(a);c&&!c(a,m,o.size)||F.push({filePath:a,size:o.size,extension:m})}}return await E(t),F}async function z(t,u,e=2){await $(t,JSON.stringify(u,null,e))}function P(t,u){const e=s.dirname(t);i.existsSync(e)||i.mkdirSync(e,{recursive:!0}),i.writeFileSync(t,u,"utf-8")}function O(t,u){if(!i.existsSync(t))return!0;try{return i.readFileSync(t,"utf-8")!==u}catch{return!0}}function N(t,u){return u?s.isAbsolute(u)?u:s.join(t,u):null}export{k as checkSourceExists,T as copySourceToTarget,N as resolveReportPath,j as scanDirectory,O as shouldUpdateFileContent,$ as writeFileContent,P as writeFileSyncSafely,z as writeJsonReport};