vite-plugin-ai-perf-analyzer 1.0.3 → 1.0.4

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/dist/index.mjs CHANGED
@@ -1,11 +1,16 @@
1
- import pc from 'picocolors';
2
- import fs2 from 'fs';
3
- import path3 from 'path';
4
- import { gzipSync } from 'zlib';
5
- import { ChatOpenAI } from '@langchain/openai';
6
- import { SystemMessage, HumanMessage } from '@langchain/core/messages';
7
-
8
1
  // src/index.ts
2
+ import pc2 from "picocolors";
3
+
4
+ // src/analyzer.ts
5
+ import fs3 from "fs";
6
+ import path3 from "path";
7
+ import { gzipSync } from "zlib";
8
+ import { ChatOpenAI } from "@langchain/openai";
9
+ import { HumanMessage, SystemMessage } from "@langchain/core/messages";
10
+
11
+ // src/dependency-analyzer.ts
12
+ import fs from "fs";
13
+ import path from "path";
9
14
  var DependencyAnalyzer = class {
10
15
  /**
11
16
  * 分析依赖
@@ -50,12 +55,12 @@ var DependencyAnalyzer = class {
50
55
  parseImports(file) {
51
56
  const imports = [];
52
57
  try {
53
- const distDir = path3.resolve(process.cwd(), "dist");
54
- const filePath = path3.join(distDir, file.path);
55
- if (!fs2.existsSync(filePath)) {
58
+ const distDir = path.resolve(process.cwd(), "dist");
59
+ const filePath = path.join(distDir, file.path);
60
+ if (!fs.existsSync(filePath)) {
56
61
  return imports;
57
62
  }
58
- const content = fs2.readFileSync(filePath, "utf-8");
63
+ const content = fs.readFileSync(filePath, "utf-8");
59
64
  const patterns = [
60
65
  // node_modules 依赖
61
66
  /from\s+["']([^"']+)["']/g,
@@ -89,10 +94,14 @@ var DependencyAnalyzer = class {
89
94
  })).sort((a, b) => b.usedBy.length - a.usedBy.length).slice(0, 10);
90
95
  }
91
96
  };
97
+
98
+ // src/history-analyzer.ts
99
+ import fs2 from "fs";
100
+ import path2 from "path";
92
101
  var HistoryAnalyzer = class {
93
102
  constructor() {
94
- const reportsDir = path3.resolve(process.cwd(), "ai-reports");
95
- this.historyFile = path3.join(reportsDir, ".perf-history.json");
103
+ const reportsDir = path2.resolve(process.cwd(), "ai-reports");
104
+ this.historyFile = path2.join(reportsDir, ".perf-history.json");
96
105
  }
97
106
  /**
98
107
  * 对比当前构建与历史记录
@@ -179,7 +188,7 @@ var HistoryAnalyzer = class {
179
188
  const history = JSON.parse(content);
180
189
  return history.slice(-10);
181
190
  } catch (error) {
182
- console.warn("\u26A0\uFE0F \u65E0\u6CD5\u8BFB\u53D6\u5386\u53F2\u8BB0\u5F55");
191
+ console.warn("⚠️ 无法读取历史记录");
183
192
  return [];
184
193
  }
185
194
  }
@@ -198,7 +207,7 @@ var HistoryAnalyzer = class {
198
207
  }))
199
208
  };
200
209
  history.push(record);
201
- const dir = path3.dirname(this.historyFile);
210
+ const dir = path2.dirname(this.historyFile);
202
211
  if (!fs2.existsSync(dir)) {
203
212
  fs2.mkdirSync(dir, { recursive: true });
204
213
  }
@@ -235,7 +244,7 @@ var OptimizationExamples = class {
235
244
  if (issues.some((i) => i.type === "optimization")) {
236
245
  examples.push(this.getCompressionExample());
237
246
  }
238
- if (issues.some((i) => i.description.includes("\u56FE\u7247"))) {
247
+ if (issues.some((i) => i.description.includes("图片"))) {
239
248
  examples.push(this.getImageOptimizationExample());
240
249
  }
241
250
  if (issues.some((i) => i.type === "size" && i.severity === "high")) {
@@ -249,11 +258,11 @@ var OptimizationExamples = class {
249
258
  */
250
259
  getCodeSplittingExample() {
251
260
  return {
252
- title: "\u914D\u7F6E\u4EE3\u7801\u5206\u5272",
253
- description: "\u5C06\u5927\u6587\u4EF6\u62C6\u5206\u6210\u591A\u4E2A\u5C0F\u6587\u4EF6\uFF0C\u63D0\u9AD8\u52A0\u8F7D\u6027\u80FD",
261
+ title: "配置代码分割",
262
+ description: "将大文件拆分成多个小文件,提高加载性能",
254
263
  priority: "high",
255
- impact: "\u53EF\u51CF\u5C11 30-50% \u7684\u521D\u59CB\u52A0\u8F7D\u5927\u5C0F",
256
- difficulty: "\u7B80\u5355",
264
+ impact: "可减少 30-50% 的初始加载大小",
265
+ difficulty: "简单",
257
266
  code: {
258
267
  language: "typescript",
259
268
  content: `// vite.config.ts
@@ -262,20 +271,20 @@ export default defineConfig({
262
271
  rollupOptions: {
263
272
  output: {
264
273
  manualChunks: {
265
- // \u63D0\u53D6 Vue \u6838\u5FC3\u5E93
274
+ // 提取 Vue 核心库
266
275
  'vue-vendor': ['vue', 'vue-router', 'pinia'],
267
276
 
268
- // \u63D0\u53D6 UI \u7EC4\u4EF6\u5E93
277
+ // 提取 UI 组件库
269
278
  'ui-vendor': ['element-plus', '@element-plus/icons-vue'],
270
279
 
271
- // \u63D0\u53D6\u5DE5\u5177\u5E93
280
+ // 提取工具库
272
281
  'utils': ['lodash-es', 'dayjs', 'axios'],
273
282
 
274
- // \u63D0\u53D6\u56FE\u8868\u5E93\uFF08\u5982\u679C\u4F7F\u7528\uFF09
283
+ // 提取图表库(如果使用)
275
284
  'charts': ['echarts', 'chart.js'],
276
285
  },
277
286
 
278
- // \u81EA\u52A8\u5206\u5272\u5927\u4E8E 500KB \u7684\u6587\u4EF6
287
+ // 自动分割大于 500KB 的文件
279
288
  chunkFileNames: (chunkInfo) => {
280
289
  const facadeModuleId = chunkInfo.facadeModuleId
281
290
  ? chunkInfo.facadeModuleId.split('/').pop()
@@ -285,7 +294,7 @@ export default defineConfig({
285
294
  },
286
295
  },
287
296
 
288
- // \u8BBE\u7F6E chunk \u5927\u5C0F\u8B66\u544A\u9608\u503C
297
+ // 设置 chunk 大小警告阈值
289
298
  chunkSizeWarningLimit: 500,
290
299
  },
291
300
  });`
@@ -298,11 +307,11 @@ export default defineConfig({
298
307
  getDependencyOptimizationExample(duplicates) {
299
308
  const topDuplicates = duplicates.slice(0, 3).map((d) => d.name);
300
309
  return {
301
- title: "\u4F18\u5316\u91CD\u590D\u4F9D\u8D56",
302
- description: `\u68C0\u6D4B\u5230 ${duplicates.length} \u4E2A\u91CD\u590D\u6253\u5305\u7684\u4F9D\u8D56\uFF0C\u5EFA\u8BAE\u63D0\u53D6\u5230\u516C\u5171 chunk`,
310
+ title: "优化重复依赖",
311
+ description: `检测到 ${duplicates.length} 个重复打包的依赖,建议提取到公共 chunk`,
303
312
  priority: "high",
304
- impact: `\u53EF\u51CF\u5C11 ${(duplicates.reduce((sum, d) => sum + d.size, 0) / 1024 / 1024).toFixed(2)}MB`,
305
- difficulty: "\u7B80\u5355",
313
+ impact: `可减少 ${(duplicates.reduce((sum, d) => sum + d.size, 0) / 1024 / 1024).toFixed(2)}MB`,
314
+ difficulty: "简单",
306
315
  code: {
307
316
  language: "typescript",
308
317
  content: `// vite.config.ts
@@ -311,7 +320,7 @@ export default defineConfig({
311
320
  rollupOptions: {
312
321
  output: {
313
322
  manualChunks: {
314
- // \u63D0\u53D6\u91CD\u590D\u4F9D\u8D56\u5230\u516C\u5171 chunk
323
+ // 提取重复依赖到公共 chunk
315
324
  'common-vendor': [
316
325
  ${topDuplicates.map((name) => `'${name}'`).join(",\n ")}
317
326
  ],
@@ -320,7 +329,7 @@ export default defineConfig({
320
329
  },
321
330
  },
322
331
 
323
- // \u6216\u8005\u4F7F\u7528\u81EA\u52A8\u5206\u5272\u7B56\u7565
332
+ // 或者使用自动分割策略
324
333
  optimizeDeps: {
325
334
  include: [
326
335
  ${topDuplicates.map((name) => `'${name}'`).join(",\n ")}
@@ -336,31 +345,31 @@ export default defineConfig({
336
345
  */
337
346
  getCompressionExample() {
338
347
  return {
339
- title: "\u542F\u7528 Gzip/Brotli \u538B\u7F29",
340
- description: "\u4F7F\u7528\u538B\u7F29\u63D2\u4EF6\u51CF\u5C11\u4F20\u8F93\u5927\u5C0F",
348
+ title: "启用 Gzip/Brotli 压缩",
349
+ description: "使用压缩插件减少传输大小",
341
350
  priority: "medium",
342
- impact: "\u53EF\u51CF\u5C11 60-70% \u7684\u4F20\u8F93\u5927\u5C0F",
343
- difficulty: "\u7B80\u5355",
351
+ impact: "可减少 60-70% 的传输大小",
352
+ difficulty: "简单",
344
353
  code: {
345
354
  language: "bash",
346
- content: `# 1. \u5B89\u88C5\u538B\u7F29\u63D2\u4EF6
355
+ content: `# 1. 安装压缩插件
347
356
  npm install vite-plugin-compression -D
348
357
 
349
- # 2. \u914D\u7F6E\u63D2\u4EF6
358
+ # 2. 配置插件
350
359
  # vite.config.ts
351
360
  import viteCompression from 'vite-plugin-compression';
352
361
 
353
362
  export default defineConfig({
354
363
  plugins: [
355
- // Gzip \u538B\u7F29
364
+ // Gzip 压缩
356
365
  viteCompression({
357
366
  algorithm: 'gzip',
358
367
  ext: '.gz',
359
- threshold: 10240, // \u5927\u4E8E 10KB \u624D\u538B\u7F29
368
+ threshold: 10240, // 大于 10KB 才压缩
360
369
  deleteOriginFile: false,
361
370
  }),
362
371
 
363
- // Brotli \u538B\u7F29\uFF08\u53EF\u9009\uFF0C\u538B\u7F29\u7387\u66F4\u9AD8\uFF09
372
+ // Brotli 压缩(可选,压缩率更高)
364
373
  viteCompression({
365
374
  algorithm: 'brotliCompress',
366
375
  ext: '.br',
@@ -370,7 +379,7 @@ export default defineConfig({
370
379
  ],
371
380
  });
372
381
 
373
- # 3. Nginx \u914D\u7F6E\uFF08\u670D\u52A1\u5668\u7AEF\uFF09
382
+ # 3. Nginx 配置(服务器端)
374
383
  # nginx.conf
375
384
  gzip on;
376
385
  gzip_types text/plain text/css application/json application/javascript;
@@ -383,40 +392,40 @@ gzip_min_length 1024;`
383
392
  */
384
393
  getImageOptimizationExample() {
385
394
  return {
386
- title: "\u4F18\u5316\u56FE\u7247\u8D44\u6E90",
387
- description: "\u538B\u7F29\u56FE\u7247\u5E76\u8F6C\u6362\u4E3A\u73B0\u4EE3\u683C\u5F0F",
395
+ title: "优化图片资源",
396
+ description: "压缩图片并转换为现代格式",
388
397
  priority: "medium",
389
- impact: "\u53EF\u51CF\u5C11 50-80% \u7684\u56FE\u7247\u5927\u5C0F",
390
- difficulty: "\u7B80\u5355",
398
+ impact: "可减少 50-80% 的图片大小",
399
+ difficulty: "简单",
391
400
  code: {
392
401
  language: "bash",
393
- content: `# 1. \u5B89\u88C5\u56FE\u7247\u4F18\u5316\u63D2\u4EF6
402
+ content: `# 1. 安装图片优化插件
394
403
  npm install vite-plugin-imagemin -D
395
404
 
396
- # 2. \u914D\u7F6E\u63D2\u4EF6
405
+ # 2. 配置插件
397
406
  # vite.config.ts
398
407
  import viteImagemin from 'vite-plugin-imagemin';
399
408
 
400
409
  export default defineConfig({
401
410
  plugins: [
402
411
  viteImagemin({
403
- // GIF \u4F18\u5316
412
+ // GIF 优化
404
413
  gifsicle: {
405
414
  optimizationLevel: 7,
406
415
  interlaced: false,
407
416
  },
408
417
 
409
- // PNG \u4F18\u5316
418
+ // PNG 优化
410
419
  optipng: {
411
420
  optimizationLevel: 7,
412
421
  },
413
422
 
414
- // JPEG \u4F18\u5316
423
+ // JPEG 优化
415
424
  mozjpeg: {
416
425
  quality: 80,
417
426
  },
418
427
 
419
- // SVG \u4F18\u5316
428
+ // SVG 优化
420
429
  svgo: {
421
430
  plugins: [
422
431
  { name: 'removeViewBox', active: false },
@@ -424,7 +433,7 @@ export default defineConfig({
424
433
  ],
425
434
  },
426
435
 
427
- // WebP \u8F6C\u6362
436
+ // WebP 转换
428
437
  webp: {
429
438
  quality: 80,
430
439
  },
@@ -432,7 +441,7 @@ export default defineConfig({
432
441
  ],
433
442
  });
434
443
 
435
- # 3. \u4F7F\u7528 WebP \u683C\u5F0F\uFF08HTML\uFF09
444
+ # 3. 使用 WebP 格式(HTML)
436
445
  <picture>
437
446
  <source srcset="image.webp" type="image/webp">
438
447
  <img src="image.jpg" alt="description">
@@ -445,20 +454,20 @@ export default defineConfig({
445
454
  */
446
455
  getDynamicImportExample() {
447
456
  return {
448
- title: "\u4F7F\u7528\u52A8\u6001\u5BFC\u5165\uFF08\u61D2\u52A0\u8F7D\uFF09",
449
- description: "\u6309\u9700\u52A0\u8F7D\u7EC4\u4EF6\u548C\u8DEF\u7531\uFF0C\u51CF\u5C11\u521D\u59CB\u52A0\u8F7D\u5927\u5C0F",
457
+ title: "使用动态导入(懒加载)",
458
+ description: "按需加载组件和路由,减少初始加载大小",
450
459
  priority: "high",
451
- impact: "\u53EF\u51CF\u5C11 40-60% \u7684\u521D\u59CB\u52A0\u8F7D\u5927\u5C0F",
452
- difficulty: "\u4E2D\u7B49",
460
+ impact: "可减少 40-60% 的初始加载大小",
461
+ difficulty: "中等",
453
462
  code: {
454
463
  language: "typescript",
455
- content: `// 1. \u8DEF\u7531\u61D2\u52A0\u8F7D
464
+ content: `// 1. 路由懒加载
456
465
  // router/index.ts
457
466
  const routes = [
458
467
  {
459
468
  path: '/dashboard',
460
469
  name: 'Dashboard',
461
- // \u4F7F\u7528\u52A8\u6001\u5BFC\u5165
470
+ // 使用动态导入
462
471
  component: () => import('@/views/Dashboard.vue'),
463
472
  },
464
473
  {
@@ -468,17 +477,17 @@ const routes = [
468
477
  },
469
478
  ];
470
479
 
471
- // 2. \u7EC4\u4EF6\u61D2\u52A0\u8F7D
480
+ // 2. 组件懒加载
472
481
  // App.vue
473
482
  <script setup>
474
483
  import { defineAsyncComponent } from 'vue';
475
484
 
476
- // \u5F02\u6B65\u7EC4\u4EF6
485
+ // 异步组件
477
486
  const HeavyComponent = defineAsyncComponent(() =>
478
487
  import('./components/HeavyComponent.vue')
479
488
  );
480
489
 
481
- // \u5E26\u52A0\u8F7D\u72B6\u6001\u7684\u5F02\u6B65\u7EC4\u4EF6
490
+ // 带加载状态的异步组件
482
491
  const AsyncComponent = defineAsyncComponent({
483
492
  loader: () => import('./components/AsyncComponent.vue'),
484
493
  loadingComponent: LoadingSpinner,
@@ -488,12 +497,12 @@ const AsyncComponent = defineAsyncComponent({
488
497
  });
489
498
  </script>
490
499
 
491
- // 3. \u6761\u4EF6\u52A0\u8F7D
500
+ // 3. 条件加载
492
501
  <script setup>
493
502
  const loadCharts = async () => {
494
503
  if (needCharts) {
495
504
  const { default: ECharts } = await import('echarts');
496
- // \u4F7F\u7528 ECharts
505
+ // 使用 ECharts
497
506
  }
498
507
  };
499
508
  </script>`
@@ -505,50 +514,50 @@ const loadCharts = async () => {
505
514
  */
506
515
  getTreeShakingExample() {
507
516
  return {
508
- title: "\u4F18\u5316 Tree-shaking",
509
- description: "\u786E\u4FDD\u672A\u4F7F\u7528\u7684\u4EE3\u7801\u88AB\u6B63\u786E\u79FB\u9664",
517
+ title: "优化 Tree-shaking",
518
+ description: "确保未使用的代码被正确移除",
510
519
  priority: "medium",
511
- impact: "\u53EF\u51CF\u5C11 10-30% \u7684\u4EE3\u7801\u5927\u5C0F",
512
- difficulty: "\u4E2D\u7B49",
520
+ impact: "可减少 10-30% 的代码大小",
521
+ difficulty: "中等",
513
522
  code: {
514
523
  language: "typescript",
515
- content: `// 1. \u4F7F\u7528 ES6 \u6A21\u5757\u5BFC\u5165\uFF08\u652F\u6301 tree-shaking\uFF09
516
- // \u274C \u4E0D\u63A8\u8350
524
+ content: `// 1. 使用 ES6 模块导入(支持 tree-shaking
525
+ // 不推荐
517
526
  import _ from 'lodash';
518
527
  const result = _.debounce(fn, 300);
519
528
 
520
- // \u2705 \u63A8\u8350
529
+ // 推荐
521
530
  import { debounce } from 'lodash-es';
522
531
  const result = debounce(fn, 300);
523
532
 
524
- // 2. \u914D\u7F6E package.json
533
+ // 2. 配置 package.json
525
534
  {
526
- "sideEffects": false, // \u6807\u8BB0\u4E3A\u65E0\u526F\u4F5C\u7528
527
- // \u6216\u6307\u5B9A\u6709\u526F\u4F5C\u7528\u7684\u6587\u4EF6
535
+ "sideEffects": false, // 标记为无副作用
536
+ // 或指定有副作用的文件
528
537
  "sideEffects": ["*.css", "*.scss"]
529
538
  }
530
539
 
531
- // 3. Vite \u914D\u7F6E
540
+ // 3. Vite 配置
532
541
  // vite.config.ts
533
542
  export default defineConfig({
534
543
  build: {
535
- // \u542F\u7528 tree-shaking
544
+ // 启用 tree-shaking
536
545
  minify: 'terser',
537
546
  terserOptions: {
538
547
  compress: {
539
- drop_console: true, // \u79FB\u9664 console
540
- drop_debugger: true, // \u79FB\u9664 debugger
541
- pure_funcs: ['console.log'], // \u79FB\u9664\u7279\u5B9A\u51FD\u6570
548
+ drop_console: true, // 移除 console
549
+ drop_debugger: true, // 移除 debugger
550
+ pure_funcs: ['console.log'], // 移除特定函数
542
551
  },
543
552
  },
544
553
  },
545
554
  });
546
555
 
547
- // 4. \u907F\u514D\u526F\u4F5C\u7528\u5BFC\u5165
548
- // \u274C \u4E0D\u63A8\u8350\uFF08\u4F1A\u5BFC\u5165\u6574\u4E2A\u6A21\u5757\uFF09
556
+ // 4. 避免副作用导入
557
+ // 不推荐(会导入整个模块)
549
558
  import 'some-library';
550
559
 
551
- // \u2705 \u63A8\u8350\uFF08\u660E\u786E\u5BFC\u5165\u9700\u8981\u7684\u90E8\u5206\uFF09
560
+ // 推荐(明确导入需要的部分)
552
561
  import { specificFunction } from 'some-library';`
553
562
  }
554
563
  };
@@ -578,14 +587,14 @@ var PerfAnalyzer = class {
578
587
  */
579
588
  async analyze() {
580
589
  const distDir = path3.resolve(process.cwd(), "dist");
581
- if (!fs2.existsSync(distDir)) {
582
- throw new Error("\u6784\u5EFA\u76EE\u5F55\u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C\u6784\u5EFA");
590
+ if (!fs3.existsSync(distDir)) {
591
+ throw new Error("构建目录不存在,请先执行构建");
583
592
  }
584
593
  const bundles = this.collectBundles(distDir);
585
594
  const summary = this.calculateSummary(bundles);
586
- console.log("\u{1F4E6} \u6B63\u5728\u5206\u6790\u4F9D\u8D56...");
595
+ console.log("📦 正在分析依赖...");
587
596
  const dependencies = this.depAnalyzer.analyzeDependencies(bundles);
588
- console.log("\u{1F4CA} \u6B63\u5728\u5BF9\u6BD4\u5386\u53F2\u8BB0\u5F55...");
597
+ console.log("📊 正在对比历史记录...");
589
598
  const comparison = this.historyAnalyzer.compare(
590
599
  bundles,
591
600
  summary.totalSize,
@@ -593,14 +602,14 @@ var PerfAnalyzer = class {
593
602
  );
594
603
  const issues = this.detectIssues(bundles, summary, dependencies);
595
604
  const suggestions = this.generateSuggestions(issues);
596
- console.log("\u{1F4A1} \u6B63\u5728\u751F\u6210\u4F18\u5316\u793A\u4F8B...");
605
+ console.log("💡 正在生成优化示例...");
597
606
  const optimizationExamples = this.examplesGenerator.generate(
598
607
  issues,
599
608
  dependencies.duplicates
600
609
  );
601
610
  let aiAnalysis;
602
611
  if (this.llm) {
603
- console.log("\u{1F916} \u6B63\u5728\u4F7F\u7528 AI \u5206\u6790\u6027\u80FD...\n");
612
+ console.log("🤖 正在使用 AI 分析性能...\n");
604
613
  aiAnalysis = await this.performAIAnalysis(
605
614
  bundles,
606
615
  summary,
@@ -625,14 +634,14 @@ var PerfAnalyzer = class {
625
634
  */
626
635
  collectBundles(dir, baseDir = dir) {
627
636
  const bundles = [];
628
- const files = fs2.readdirSync(dir);
637
+ const files = fs3.readdirSync(dir);
629
638
  for (const file of files) {
630
639
  const filePath = path3.join(dir, file);
631
- const stat = fs2.statSync(filePath);
640
+ const stat = fs3.statSync(filePath);
632
641
  if (stat.isDirectory()) {
633
642
  bundles.push(...this.collectBundles(filePath, baseDir));
634
643
  } else {
635
- const content = fs2.readFileSync(filePath);
644
+ const content = fs3.readFileSync(filePath);
636
645
  const gzipSize = gzipSync(content).length;
637
646
  const relativePath = path3.relative(baseDir, filePath);
638
647
  bundles.push({
@@ -708,9 +717,9 @@ var PerfAnalyzer = class {
708
717
  issues.push({
709
718
  type: "size",
710
719
  severity: "high",
711
- title: "\u6784\u5EFA\u4EA7\u7269\u603B\u5927\u5C0F\u8FC7\u5927",
712
- description: `\u603B\u5927\u5C0F ${totalSizeMB.toFixed(2)}MB \u8D85\u8FC7\u9608\u503C ${threshold.totalSize}MB`,
713
- suggestion: "\u8003\u8651\u4EE3\u7801\u5206\u5272\u3001tree-shaking\u3001\u538B\u7F29\u7B49\u4F18\u5316\u624B\u6BB5"
720
+ title: "构建产物总大小过大",
721
+ description: `总大小 ${totalSizeMB.toFixed(2)}MB 超过阈值 ${threshold.totalSize}MB`,
722
+ suggestion: "考虑代码分割、tree-shaking、压缩等优化手段"
714
723
  });
715
724
  }
716
725
  const largeBundles = bundles.filter(
@@ -720,12 +729,12 @@ var PerfAnalyzer = class {
720
729
  issues.push({
721
730
  type: "size",
722
731
  severity: "medium",
723
- title: "\u5B58\u5728\u8FC7\u5927\u7684\u5355\u4E2A\u6587\u4EF6",
724
- description: `\u53D1\u73B0 ${largeBundles.length} \u4E2A\u6587\u4EF6\u8D85\u8FC7 ${threshold.bundleSize}KB`,
732
+ title: "存在过大的单个文件",
733
+ description: `发现 ${largeBundles.length} 个文件超过 ${threshold.bundleSize}KB`,
725
734
  files: largeBundles.map(
726
735
  (b) => `${b.name} (${(b.size / 1024).toFixed(2)}KB)`
727
736
  ),
728
- suggestion: "\u8003\u8651\u62C6\u5206\u5927\u6587\u4EF6\uFF0C\u4F7F\u7528\u52A8\u6001\u5BFC\u5165\u6216\u4EE3\u7801\u5206\u5272"
737
+ suggestion: "考虑拆分大文件,使用动态导入或代码分割"
729
738
  });
730
739
  }
731
740
  const jsFiles = bundles.filter((b) => b.type === "javascript");
@@ -733,9 +742,9 @@ var PerfAnalyzer = class {
733
742
  issues.push({
734
743
  type: "count",
735
744
  severity: "low",
736
- title: "JavaScript \u6587\u4EF6\u6570\u91CF\u8FC7\u591A",
737
- description: `\u5171\u6709 ${jsFiles.length} \u4E2A JS \u6587\u4EF6\uFF0C\u8D85\u8FC7\u9608\u503C ${threshold.chunkCount}`,
738
- suggestion: "\u8FC7\u591A\u7684\u6587\u4EF6\u4F1A\u589E\u52A0 HTTP \u8BF7\u6C42\u6570\uFF0C\u8003\u8651\u5408\u5E76\u5C0F\u6587\u4EF6"
745
+ title: "JavaScript 文件数量过多",
746
+ description: `共有 ${jsFiles.length} JS 文件,超过阈值 ${threshold.chunkCount}`,
747
+ suggestion: "过多的文件会增加 HTTP 请求数,考虑合并小文件"
739
748
  });
740
749
  }
741
750
  const images = bundles.filter((b) => b.type === "image");
@@ -744,12 +753,12 @@ var PerfAnalyzer = class {
744
753
  issues.push({
745
754
  type: "optimization",
746
755
  severity: "medium",
747
- title: "\u5B58\u5728\u672A\u4F18\u5316\u7684\u56FE\u7247",
748
- description: `\u53D1\u73B0 ${largeImages.length} \u4E2A\u5927\u4E8E 100KB \u7684\u56FE\u7247`,
756
+ title: "存在未优化的图片",
757
+ description: `发现 ${largeImages.length} 个大于 100KB 的图片`,
749
758
  files: largeImages.map(
750
759
  (img) => `${img.name} (${(img.size / 1024).toFixed(2)}KB)`
751
760
  ),
752
- suggestion: "\u4F7F\u7528\u56FE\u7247\u538B\u7F29\u5DE5\u5177\uFF0C\u6216\u8F6C\u6362\u4E3A WebP \u683C\u5F0F"
761
+ suggestion: "使用图片压缩工具,或转换为 WebP 格式"
753
762
  });
754
763
  }
755
764
  const sourcemaps = bundles.filter((b) => b.type === "sourcemap");
@@ -758,11 +767,11 @@ var PerfAnalyzer = class {
758
767
  issues.push({
759
768
  type: "optimization",
760
769
  severity: "low",
761
- title: "\u751F\u4EA7\u73AF\u5883\u5305\u542B sourcemap",
762
- description: `Sourcemap \u6587\u4EF6\u5360\u7528 ${(totalMapSize / 1024 / 1024).toFixed(
770
+ title: "生产环境包含 sourcemap",
771
+ description: `Sourcemap 文件占用 ${(totalMapSize / 1024 / 1024).toFixed(
763
772
  2
764
773
  )}MB`,
765
- suggestion: "\u751F\u4EA7\u73AF\u5883\u5EFA\u8BAE\u7981\u7528 sourcemap \u6216\u4F7F\u7528\u5916\u90E8 sourcemap"
774
+ suggestion: "生产环境建议禁用 sourcemap 或使用外部 sourcemap"
766
775
  });
767
776
  }
768
777
  if (dependencies && dependencies.duplicates.length > 0) {
@@ -770,12 +779,12 @@ var PerfAnalyzer = class {
770
779
  issues.push({
771
780
  type: "dependency",
772
781
  severity: "medium",
773
- title: "\u68C0\u6D4B\u5230\u91CD\u590D\u6253\u5305\u7684\u4F9D\u8D56",
774
- description: `\u53D1\u73B0 ${dependencies.duplicates.length} \u4E2A\u4F9D\u8D56\u88AB\u591A\u6B21\u6253\u5305`,
782
+ title: "检测到重复打包的依赖",
783
+ description: `发现 ${dependencies.duplicates.length} 个依赖被多次打包`,
775
784
  files: topDuplicates.map(
776
- (d) => `${d.name} (\u88AB ${d.usedBy.length} \u4E2A\u6587\u4EF6\u4F7F\u7528, ${(d.size / 1024).toFixed(2)}KB)`
785
+ (d) => `${d.name} ( ${d.usedBy.length} 个文件使用, ${(d.size / 1024).toFixed(2)}KB)`
777
786
  ),
778
- suggestion: "\u5C06\u91CD\u590D\u4F9D\u8D56\u63D0\u53D6\u5230\u516C\u5171 chunk \u4E2D"
787
+ suggestion: "将重复依赖提取到公共 chunk "
779
788
  });
780
789
  }
781
790
  return issues;
@@ -786,16 +795,16 @@ var PerfAnalyzer = class {
786
795
  generateSuggestions(issues) {
787
796
  const suggestions = [];
788
797
  if (issues.some((i) => i.type === "size")) {
789
- suggestions.push("\u542F\u7528 gzip/brotli \u538B\u7F29");
790
- suggestions.push("\u914D\u7F6E Vite \u7684 build.rollupOptions \u8FDB\u884C\u4EE3\u7801\u5206\u5272");
791
- suggestions.push("\u4F7F\u7528 vite-plugin-compression \u63D2\u4EF6");
798
+ suggestions.push("启用 gzip/brotli 压缩");
799
+ suggestions.push("配置 Vite build.rollupOptions 进行代码分割");
800
+ suggestions.push("使用 vite-plugin-compression 插件");
792
801
  }
793
802
  if (issues.some((i) => i.type === "optimization")) {
794
- suggestions.push("\u4F7F\u7528 vite-plugin-imagemin \u4F18\u5316\u56FE\u7247");
795
- suggestions.push("\u914D\u7F6E CSS \u538B\u7F29\u548C tree-shaking");
803
+ suggestions.push("使用 vite-plugin-imagemin 优化图片");
804
+ suggestions.push("配置 CSS 压缩和 tree-shaking");
796
805
  }
797
806
  if (issues.length === 0) {
798
- suggestions.push("\u6784\u5EFA\u4EA7\u7269\u5DF2\u7ECF\u5F88\u4F18\u79C0\uFF0C\u7EE7\u7EED\u4FDD\u6301\uFF01");
807
+ suggestions.push("构建产物已经很优秀,继续保持!");
799
808
  }
800
809
  return suggestions;
801
810
  }
@@ -804,11 +813,11 @@ var PerfAnalyzer = class {
804
813
  */
805
814
  async performAIAnalysis(bundles, summary, issues, dependencies, comparison) {
806
815
  const systemPrompt = new SystemMessage(
807
- "\u4F60\u662F\u4E00\u4E2A\u4E13\u4E1A\u7684\u524D\u7AEF\u6027\u80FD\u4F18\u5316\u4E13\u5BB6\uFF0C\u7CBE\u901A Vite\u3001Webpack \u7B49\u6784\u5EFA\u5DE5\u5177\u3002\u8BF7\u5206\u6790\u6784\u5EFA\u4EA7\u7269\u5E76\u63D0\u4F9B\u4E13\u4E1A\u7684\u4F18\u5316\u5EFA\u8BAE\u3002"
816
+ "你是一个专业的前端性能优化专家,精通 Vite、Webpack 等构建工具。请分析构建产物并提供专业的优化建议。"
808
817
  );
809
818
  const bundlesSummary = summary.largestFiles.slice(0, 5).map((b) => `- ${b.name}: ${(b.size / 1024).toFixed(2)}KB`).join("\n");
810
819
  const typesSummary = Object.entries(summary.byType).map(
811
- ([type, info]) => `- ${type}: ${info.count} \u4E2A\u6587\u4EF6, ${(info.size / 1024).toFixed(2)}KB`
820
+ ([type, info]) => `- ${type}: ${info.count} 个文件, ${(info.size / 1024).toFixed(2)}KB`
812
821
  ).join("\n");
813
822
  const issuesSummary = issues.map(
814
823
  (issue) => `- [${issue.severity}] ${issue.title}: ${issue.description}`
@@ -816,51 +825,56 @@ var PerfAnalyzer = class {
816
825
  let dependencySummary = "";
817
826
  if (dependencies) {
818
827
  dependencySummary = `
819
- ## \u4F9D\u8D56\u5206\u6790
820
- - \u603B\u4F9D\u8D56\u6570: ${dependencies.total}
821
- - \u91CD\u590D\u4F9D\u8D56: ${dependencies.duplicates.length} \u4E2A
822
- - \u6700\u5927\u4F9D\u8D56: ${dependencies.largest.slice(0, 3).map((d) => `${d.name} (${(d.size / 1024).toFixed(2)}KB)`).join(", ")}`;
828
+ ## 依赖分析
829
+ - 总依赖数: ${dependencies.total}
830
+ - 重复依赖: ${dependencies.duplicates.length}
831
+ - 最大依赖: ${dependencies.largest.slice(0, 3).map((d) => `${d.name} (${(d.size / 1024).toFixed(2)}KB)`).join(", ")}`;
823
832
  }
824
833
  let comparisonSummary = "";
825
834
  if (comparison) {
826
- const sizeChange = comparison.totalSize.trend === "increased" ? "\u589E\u52A0" : "\u51CF\u5C11";
835
+ const sizeChange = comparison.totalSize.trend === "increased" ? "增加" : "减少";
827
836
  comparisonSummary = `
828
- ## \u5386\u53F2\u5BF9\u6BD4
829
- - \u603B\u5927\u5C0F${sizeChange}: ${Math.abs(comparison.totalSize.diffPercent).toFixed(2)}%
830
- - \u6587\u4EF6\u6570\u91CF\u53D8\u5316: ${comparison.fileCount.diff > 0 ? "+" : ""}${comparison.fileCount.diff}
831
- - \u65B0\u589E\u6587\u4EF6: ${comparison.newFiles.length} \u4E2A
832
- - \u5220\u9664\u6587\u4EF6: ${comparison.removedFiles.length} \u4E2A`;
837
+ ## 历史对比
838
+ - 总大小${sizeChange}: ${Math.abs(comparison.totalSize.diffPercent).toFixed(2)}%
839
+ - 文件数量变化: ${comparison.fileCount.diff > 0 ? "+" : ""}${comparison.fileCount.diff}
840
+ - 新增文件: ${comparison.newFiles.length}
841
+ - 删除文件: ${comparison.removedFiles.length} 个`;
833
842
  }
834
843
  const userPrompt = new HumanMessage(`
835
- \u8BF7\u5206\u6790\u4EE5\u4E0B\u6784\u5EFA\u4EA7\u7269\u4FE1\u606F\uFF1A
844
+ 请分析以下构建产物信息:
836
845
 
837
- ## \u603B\u4F53\u7EDF\u8BA1
838
- - \u603B\u5927\u5C0F: ${(summary.totalSize / 1024 / 1024).toFixed(2)}MB
839
- - Gzip \u540E: ${(summary.totalGzipSize / 1024 / 1024).toFixed(2)}MB
840
- - \u6587\u4EF6\u6570\u91CF: ${summary.fileCount}
846
+ ## 总体统计
847
+ - 总大小: ${(summary.totalSize / 1024 / 1024).toFixed(2)}MB
848
+ - Gzip 后: ${(summary.totalGzipSize / 1024 / 1024).toFixed(2)}MB
849
+ - 文件数量: ${summary.fileCount}
841
850
 
842
- ## \u6700\u5927\u7684\u6587\u4EF6
851
+ ## 最大的文件
843
852
  ${bundlesSummary}
844
853
 
845
- ## \u6309\u7C7B\u578B\u5206\u7EC4
854
+ ## 按类型分组
846
855
  ${typesSummary}
847
856
  ${dependencySummary}
848
857
  ${comparisonSummary}
849
858
 
850
- ## \u68C0\u6D4B\u5230\u7684\u95EE\u9898
851
- ${issuesSummary || "\u65E0\u660E\u663E\u95EE\u9898"}
859
+ ## 检测到的问题
860
+ ${issuesSummary || "无明显问题"}
852
861
 
853
- \u8BF7\u63D0\u4F9B\uFF1A
854
- 1. \u6027\u80FD\u8BC4\u4F30\uFF083-5 \u53E5\u8BDD\uFF09
855
- 2. \u5177\u4F53\u4F18\u5316\u5EFA\u8BAE\uFF083-5 \u6761\uFF0C\u6BCF\u6761\u7B80\u6D01\u660E\u4E86\uFF09
856
- 3. \u4F18\u5148\u7EA7\u6392\u5E8F
862
+ 请提供:
863
+ 1. 性能评估(3-5 句话)
864
+ 2. 具体优化建议(3-5 条,每条简洁明了)
865
+ 3. 优先级排序
857
866
 
858
- \u8BF7\u7528\u7B80\u6D01\u4E13\u4E1A\u7684\u8BED\u8A00\u56DE\u7B54\uFF0C\u4E0D\u8981\u8FC7\u4E8E\u5197\u957F\u3002
867
+ 请用简洁专业的语言回答,不要过于冗长。
859
868
  `);
860
869
  const response = await this.llm.invoke([systemPrompt, userPrompt]);
861
870
  return response.content.toString();
862
871
  }
863
872
  };
873
+
874
+ // src/reporter.ts
875
+ import fs4 from "fs";
876
+ import path4 from "path";
877
+ import pc from "picocolors";
864
878
  var PerfReporter = class {
865
879
  /**
866
880
  * 生成报告
@@ -877,88 +891,88 @@ var PerfReporter = class {
877
891
  * 控制台输出
878
892
  */
879
893
  printConsole(result) {
880
- console.log("\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
881
- console.log("\u26A1 \u6027\u80FD\u5206\u6790\u62A5\u544A");
882
- console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
883
- console.log("\u{1F4CA} \u603B\u4F53\u7EDF\u8BA1:");
894
+ console.log("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
895
+ console.log(" 性能分析报告");
896
+ console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
897
+ console.log("📊 总体统计:");
884
898
  console.log(
885
- ` \u603B\u5927\u5C0F: ${(result.summary.totalSize / 1024 / 1024).toFixed(2)}MB`
899
+ ` 总大小: ${(result.summary.totalSize / 1024 / 1024).toFixed(2)}MB`
886
900
  );
887
901
  console.log(
888
- ` Gzip\u540E: ${(result.summary.totalGzipSize / 1024 / 1024).toFixed(2)}MB`
902
+ ` Gzip后: ${(result.summary.totalGzipSize / 1024 / 1024).toFixed(2)}MB`
889
903
  );
890
- console.log(` \u6587\u4EF6\u6570: ${result.summary.fileCount}
904
+ console.log(` 文件数: ${result.summary.fileCount}
891
905
  `);
892
906
  if (result.comparison) {
893
- console.log("\u{1F4C8} \u5386\u53F2\u5BF9\u6BD4:");
907
+ console.log("📈 历史对比:");
894
908
  const { totalSize, fileCount, newFiles, removedFiles } = result.comparison;
895
- const sizeIcon = totalSize.trend === "increased" ? "\u{1F4C8}" : "\u{1F4C9}";
909
+ const sizeIcon = totalSize.trend === "increased" ? "📈" : "📉";
896
910
  console.log(
897
- ` ${sizeIcon} \u603B\u5927\u5C0F: ${totalSize.trend === "increased" ? "+" : ""}${(totalSize.diff / 1024 / 1024).toFixed(2)}MB (${totalSize.diffPercent > 0 ? "+" : ""}${totalSize.diffPercent.toFixed(2)}%)`
911
+ ` ${sizeIcon} 总大小: ${totalSize.trend === "increased" ? "+" : ""}${(totalSize.diff / 1024 / 1024).toFixed(2)}MB (${totalSize.diffPercent > 0 ? "+" : ""}${totalSize.diffPercent.toFixed(2)}%)`
898
912
  );
899
913
  console.log(
900
- ` \u{1F4CA} \u6587\u4EF6\u6570: ${fileCount.diff > 0 ? "+" : ""}${fileCount.diff}`
914
+ ` 📊 文件数: ${fileCount.diff > 0 ? "+" : ""}${fileCount.diff}`
901
915
  );
902
916
  if (newFiles.length > 0) {
903
- console.log(` \u2728 \u65B0\u589E: ${newFiles.slice(0, 3).join(", ")}`);
917
+ console.log(` 新增: ${newFiles.slice(0, 3).join(", ")}`);
904
918
  }
905
919
  if (removedFiles.length > 0) {
906
- console.log(` \u{1F5D1}\uFE0F \u5220\u9664: ${removedFiles.slice(0, 3).join(", ")}`);
920
+ console.log(` 🗑️ 删除: ${removedFiles.slice(0, 3).join(", ")}`);
907
921
  }
908
922
  console.log();
909
923
  }
910
924
  if (result.dependencies) {
911
- console.log("\u{1F4E6} \u4F9D\u8D56\u5206\u6790:");
912
- console.log(` \u603B\u4F9D\u8D56\u6570: ${result.dependencies.total}`);
925
+ console.log("📦 依赖分析:");
926
+ console.log(` 总依赖数: ${result.dependencies.total}`);
913
927
  if (result.dependencies.duplicates.length > 0) {
914
928
  console.log(
915
- ` \u26A0\uFE0F \u91CD\u590D\u4F9D\u8D56: ${result.dependencies.duplicates.length} \u4E2A`
929
+ ` ⚠️ 重复依赖: ${result.dependencies.duplicates.length} 个`
916
930
  );
917
931
  result.dependencies.duplicates.slice(0, 3).forEach((dep) => {
918
932
  console.log(
919
- ` - ${dep.name}: \u88AB ${dep.usedBy.length} \u4E2A\u6587\u4EF6\u4F7F\u7528`
933
+ ` - ${dep.name}: ${dep.usedBy.length} 个文件使用`
920
934
  );
921
935
  });
922
936
  }
923
937
  console.log();
924
938
  }
925
- console.log("\u{1F4E6} \u6700\u5927\u7684\u6587\u4EF6:");
939
+ console.log("📦 最大的文件:");
926
940
  result.summary.largestFiles.slice(0, 5).forEach((file, index) => {
927
941
  console.log(
928
942
  ` ${index + 1}. ${file.name}: ${(file.size / 1024).toFixed(2)}KB`
929
943
  );
930
944
  });
931
945
  console.log();
932
- console.log("\u{1F4CB} \u6309\u7C7B\u578B\u7EDF\u8BA1:");
946
+ console.log("📋 按类型统计:");
933
947
  Object.entries(result.summary.byType).forEach(([type, info]) => {
934
948
  console.log(
935
- ` ${type}: ${info.count} \u4E2A, ${(info.size / 1024).toFixed(2)}KB`
949
+ ` ${type}: ${info.count} 个, ${(info.size / 1024).toFixed(2)}KB`
936
950
  );
937
951
  });
938
952
  console.log();
939
953
  if (result.issues.length > 0) {
940
- console.log("\u26A0\uFE0F \u6027\u80FD\u95EE\u9898:");
954
+ console.log("⚠️ 性能问题:");
941
955
  result.issues.forEach((issue, index) => {
942
956
  const icon = this.getSeverityIcon(issue.severity);
943
957
  console.log(` ${index + 1}. ${icon} ${issue.title}`);
944
958
  console.log(` ${issue.description}`);
945
959
  if (issue.suggestion) {
946
- console.log(` \u{1F4A1} ${issue.suggestion}`);
960
+ console.log(` 💡 ${issue.suggestion}`);
947
961
  }
948
962
  });
949
963
  console.log();
950
964
  } else {
951
- console.log("\u2705 \u672A\u53D1\u73B0\u660E\u663E\u7684\u6027\u80FD\u95EE\u9898\n");
965
+ console.log(" 未发现明显的性能问题\n");
952
966
  }
953
967
  if (result.suggestions.length > 0) {
954
- console.log("\u{1F4A1} \u4F18\u5316\u5EFA\u8BAE:");
968
+ console.log("💡 优化建议:");
955
969
  result.suggestions.forEach((suggestion, index) => {
956
970
  console.log(` ${index + 1}. ${suggestion}`);
957
971
  });
958
972
  console.log();
959
973
  }
960
974
  if (result.optimizationExamples && result.optimizationExamples.length > 0) {
961
- console.log("\u{1F527} \u4F18\u5316\u793A\u4F8B:");
975
+ console.log("🔧 优化示例:");
962
976
  result.optimizationExamples.slice(0, 3).forEach((example, index) => {
963
977
  console.log(` ${index + 1}. ${example.title} (${example.priority})`);
964
978
  console.log(` ${example.description}`);
@@ -966,13 +980,13 @@ var PerfReporter = class {
966
980
  console.log();
967
981
  }
968
982
  if (result.aiAnalysis) {
969
- console.log("\u{1F916} AI \u5206\u6790:");
983
+ console.log("🤖 AI 分析:");
970
984
  console.log(
971
985
  result.aiAnalysis.split("\n").map((line) => ` ${line}`).join("\n")
972
986
  );
973
987
  console.log();
974
988
  }
975
- console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
989
+ console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
976
990
  }
977
991
  /**
978
992
  * 生成 HTML 报告
@@ -984,7 +998,7 @@ var PerfReporter = class {
984
998
  <head>
985
999
  <meta charset="UTF-8">
986
1000
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
987
- <title>\u6027\u80FD\u5206\u6790\u62A5\u544A</title>
1001
+ <title>性能分析报告</title>
988
1002
  <style>
989
1003
  * { margin: 0; padding: 0; box-sizing: border-box; }
990
1004
  body {
@@ -1224,33 +1238,33 @@ var PerfReporter = class {
1224
1238
  <body>
1225
1239
  <div class="container">
1226
1240
  <div class="header">
1227
- <h1>\u26A1 \u6027\u80FD\u5206\u6790\u62A5\u544A</h1>
1228
- <div class="time">\u751F\u6210\u65F6\u95F4: ${result.timestamp}</div>
1241
+ <h1>⚡ 性能分析报告</h1>
1242
+ <div class="time">生成时间: ${result.timestamp}</div>
1229
1243
  </div>
1230
1244
 
1231
1245
  <div class="content">
1232
- <!-- \u603B\u4F53\u7EDF\u8BA1 -->
1246
+ <!-- 总体统计 -->
1233
1247
  <div class="section">
1234
- <div class="section-title">\u{1F4CA} \u603B\u4F53\u7EDF\u8BA1</div>
1248
+ <div class="section-title">📊 总体统计</div>
1235
1249
  <div class="stats-grid">
1236
1250
  <div class="stat-card">
1237
- <div class="stat-label">\u603B\u5927\u5C0F</div>
1251
+ <div class="stat-label">总大小</div>
1238
1252
  <div class="stat-value">${(result.summary.totalSize / 1024 / 1024).toFixed(2)}MB</div>
1239
1253
  </div>
1240
1254
  <div class="stat-card">
1241
- <div class="stat-label">Gzip \u540E</div>
1255
+ <div class="stat-label">Gzip 后</div>
1242
1256
  <div class="stat-value">${(result.summary.totalGzipSize / 1024 / 1024).toFixed(2)}MB</div>
1243
1257
  </div>
1244
1258
  <div class="stat-card">
1245
- <div class="stat-label">\u6587\u4EF6\u6570\u91CF</div>
1259
+ <div class="stat-label">文件数量</div>
1246
1260
  <div class="stat-value">${result.summary.fileCount}</div>
1247
1261
  </div>
1248
1262
  </div>
1249
1263
  </div>
1250
1264
 
1251
- <!-- \u6700\u5927\u6587\u4EF6 -->
1265
+ <!-- 最大文件 -->
1252
1266
  <div class="section">
1253
- <div class="section-title">\u{1F4E6} \u6700\u5927\u7684\u6587\u4EF6</div>
1267
+ <div class="section-title">📦 最大的文件</div>
1254
1268
  <div class="file-list">
1255
1269
  ${result.summary.largestFiles.slice(0, 10).map(
1256
1270
  (file) => `
@@ -1263,49 +1277,49 @@ var PerfReporter = class {
1263
1277
  </div>
1264
1278
  </div>
1265
1279
 
1266
- <!-- \u6309\u7C7B\u578B\u7EDF\u8BA1 -->
1280
+ <!-- 按类型统计 -->
1267
1281
  <div class="section">
1268
- <div class="section-title">\u{1F4CB} \u6309\u7C7B\u578B\u7EDF\u8BA1</div>
1282
+ <div class="section-title">📋 按类型统计</div>
1269
1283
  <div class="file-list">
1270
1284
  ${Object.entries(result.summary.byType).map(
1271
1285
  ([type, info]) => `
1272
1286
  <div class="file-item">
1273
1287
  <span class="file-name">${type}</span>
1274
- <span class="file-size">${info.count} \u4E2A, ${(info.size / 1024).toFixed(2)}KB</span>
1288
+ <span class="file-size">${info.count} 个, ${(info.size / 1024).toFixed(2)}KB</span>
1275
1289
  </div>
1276
1290
  `
1277
1291
  ).join("")}
1278
1292
  </div>
1279
1293
  </div>
1280
1294
 
1281
- <!-- \u5386\u53F2\u5BF9\u6BD4 -->
1295
+ <!-- 历史对比 -->
1282
1296
  ${result.comparison ? `
1283
1297
  <div class="section">
1284
- <div class="section-title">\u{1F4C8} \u5386\u53F2\u5BF9\u6BD4</div>
1298
+ <div class="section-title">📈 历史对比</div>
1285
1299
  <div class="stats-grid">
1286
1300
  <div class="stat-card ${result.comparison.totalSize.trend === "increased" ? "trend-up" : "trend-down"}">
1287
- <div class="stat-label">\u603B\u5927\u5C0F\u53D8\u5316</div>
1301
+ <div class="stat-label">总大小变化</div>
1288
1302
  <div class="stat-value">${result.comparison.totalSize.diffPercent > 0 ? "+" : ""}${result.comparison.totalSize.diffPercent.toFixed(2)}%</div>
1289
1303
  <div class="stat-label">${result.comparison.totalSize.trend === "increased" ? "+" : ""}${(result.comparison.totalSize.diff / 1024 / 1024).toFixed(
1290
1304
  2
1291
1305
  )}MB</div>
1292
1306
  </div>
1293
1307
  <div class="stat-card">
1294
- <div class="stat-label">\u6587\u4EF6\u6570\u53D8\u5316</div>
1308
+ <div class="stat-label">文件数变化</div>
1295
1309
  <div class="stat-value">${result.comparison.fileCount.diff > 0 ? "+" : ""}${result.comparison.fileCount.diff}</div>
1296
1310
  </div>
1297
1311
  <div class="stat-card">
1298
- <div class="stat-label">\u65B0\u589E\u6587\u4EF6</div>
1312
+ <div class="stat-label">新增文件</div>
1299
1313
  <div class="stat-value">${result.comparison.newFiles.length}</div>
1300
1314
  </div>
1301
1315
  <div class="stat-card">
1302
- <div class="stat-label">\u5220\u9664\u6587\u4EF6</div>
1316
+ <div class="stat-label">删除文件</div>
1303
1317
  <div class="stat-value">${result.comparison.removedFiles.length}</div>
1304
1318
  </div>
1305
1319
  </div>
1306
1320
  ${result.comparison.largestFileChanges.length > 0 ? `
1307
1321
  <div class="file-list" style="margin-top: 15px;">
1308
- <div style="font-weight: bold; padding: 10px; color: #333;">\u6587\u4EF6\u5927\u5C0F\u53D8\u5316 Top 5:</div>
1322
+ <div style="font-weight: bold; padding: 10px; color: #333;">文件大小变化 Top 5:</div>
1309
1323
  ${result.comparison.largestFileChanges.slice(0, 5).map(
1310
1324
  (change) => `
1311
1325
  <div class="file-item">
@@ -1323,29 +1337,29 @@ var PerfReporter = class {
1323
1337
  </div>
1324
1338
  ` : ""}
1325
1339
 
1326
- <!-- \u4F9D\u8D56\u5206\u6790 -->
1340
+ <!-- 依赖分析 -->
1327
1341
  ${result.dependencies ? `
1328
1342
  <div class="section">
1329
- <div class="section-title">\u{1F4E6} \u4F9D\u8D56\u5206\u6790</div>
1343
+ <div class="section-title">📦 依赖分析</div>
1330
1344
  <div class="stats-grid">
1331
1345
  <div class="stat-card">
1332
- <div class="stat-label">\u603B\u4F9D\u8D56\u6570</div>
1346
+ <div class="stat-label">总依赖数</div>
1333
1347
  <div class="stat-value">${result.dependencies.total}</div>
1334
1348
  </div>
1335
1349
  <div class="stat-card ${result.dependencies.duplicates.length > 0 ? "trend-up" : ""}">
1336
- <div class="stat-label">\u91CD\u590D\u4F9D\u8D56</div>
1350
+ <div class="stat-label">重复依赖</div>
1337
1351
  <div class="stat-value">${result.dependencies.duplicates.length}</div>
1338
1352
  </div>
1339
1353
  </div>
1340
1354
  ${result.dependencies.duplicates.length > 0 ? `
1341
1355
  <div class="file-list" style="margin-top: 15px;">
1342
- <div style="font-weight: bold; padding: 10px; color: #333;">\u91CD\u590D\u6253\u5305\u7684\u4F9D\u8D56:</div>
1356
+ <div style="font-weight: bold; padding: 10px; color: #333;">重复打包的依赖:</div>
1343
1357
  ${result.dependencies.duplicates.slice(0, 10).map(
1344
1358
  (dep) => `
1345
1359
  <div class="file-item">
1346
1360
  <span class="file-name">${dep.name}</span>
1347
1361
  <span class="file-size">
1348
- ${(dep.size / 1024).toFixed(2)}KB \xB7 \u88AB ${dep.usedBy.length} \u4E2A\u6587\u4EF6\u4F7F\u7528
1362
+ ${(dep.size / 1024).toFixed(2)}KB · ${dep.usedBy.length} 个文件使用
1349
1363
  </span>
1350
1364
  </div>
1351
1365
  `
@@ -1354,7 +1368,7 @@ var PerfReporter = class {
1354
1368
  ` : ""}
1355
1369
  ${result.dependencies.largest.length > 0 ? `
1356
1370
  <div class="file-list" style="margin-top: 15px;">
1357
- <div style="font-weight: bold; padding: 10px; color: #333;">\u6700\u5927\u7684\u4F9D\u8D56:</div>
1371
+ <div style="font-weight: bold; padding: 10px; color: #333;">最大的依赖:</div>
1358
1372
  ${result.dependencies.largest.slice(0, 10).map(
1359
1373
  (dep) => `
1360
1374
  <div class="file-item">
@@ -1370,10 +1384,10 @@ var PerfReporter = class {
1370
1384
  </div>
1371
1385
  ` : ""}
1372
1386
 
1373
- <!-- \u6027\u80FD\u95EE\u9898 -->
1387
+ <!-- 性能问题 -->
1374
1388
  ${result.issues.length > 0 ? `
1375
1389
  <div class="section">
1376
- <div class="section-title">\u26A0\uFE0F \u6027\u80FD\u95EE\u9898</div>
1390
+ <div class="section-title">⚠️ 性能问题</div>
1377
1391
  ${result.issues.map(
1378
1392
  (issue) => `
1379
1393
  <div class="issue ${issue.severity}">
@@ -1381,20 +1395,20 @@ var PerfReporter = class {
1381
1395
  issue.severity
1382
1396
  )} ${issue.title}</div>
1383
1397
  <div class="issue-desc">${issue.description}</div>
1384
- ${issue.files ? `<div class="issue-desc">\u76F8\u5173\u6587\u4EF6: ${issue.files.join(
1398
+ ${issue.files ? `<div class="issue-desc">相关文件: ${issue.files.join(
1385
1399
  ", "
1386
1400
  )}</div>` : ""}
1387
- ${issue.suggestion ? `<div class="issue-suggestion">\u{1F4A1} ${issue.suggestion}</div>` : ""}
1401
+ ${issue.suggestion ? `<div class="issue-suggestion">💡 ${issue.suggestion}</div>` : ""}
1388
1402
  </div>
1389
1403
  `
1390
1404
  ).join("")}
1391
1405
  </div>
1392
- ` : '<div class="section"><div class="section-title">\u2705 \u672A\u53D1\u73B0\u660E\u663E\u7684\u6027\u80FD\u95EE\u9898</div></div>'}
1406
+ ` : '<div class="section"><div class="section-title">✅ 未发现明显的性能问题</div></div>'}
1393
1407
 
1394
- <!-- \u4F18\u5316\u5EFA\u8BAE -->
1408
+ <!-- 优化建议 -->
1395
1409
  ${result.suggestions.length > 0 ? `
1396
1410
  <div class="section">
1397
- <div class="section-title">\u{1F4A1} \u4F18\u5316\u5EFA\u8BAE</div>
1411
+ <div class="section-title">💡 优化建议</div>
1398
1412
  <div class="suggestion-list">
1399
1413
  ${result.suggestions.map(
1400
1414
  (suggestion, index) => `
@@ -1405,21 +1419,21 @@ var PerfReporter = class {
1405
1419
  </div>
1406
1420
  ` : ""}
1407
1421
 
1408
- <!-- \u4F18\u5316\u793A\u4F8B -->
1422
+ <!-- 优化示例 -->
1409
1423
  ${result.optimizationExamples && result.optimizationExamples.length > 0 ? `
1410
1424
  <div class="section">
1411
- <div class="section-title">\u{1F527} \u4F18\u5316\u793A\u4F8B</div>
1425
+ <div class="section-title">🔧 优化示例</div>
1412
1426
  ${result.optimizationExamples.map(
1413
1427
  (example) => `
1414
1428
  <div class="optimization-example">
1415
1429
  <div class="example-header">
1416
1430
  <div class="example-title">
1417
1431
  ${example.title}
1418
- <span class="priority-badge priority-${example.priority}">${example.priority === "high" ? "\u9AD8\u4F18\u5148\u7EA7" : example.priority === "medium" ? "\u4E2D\u4F18\u5148\u7EA7" : "\u4F4E\u4F18\u5148\u7EA7"}</span>
1432
+ <span class="priority-badge priority-${example.priority}">${example.priority === "high" ? "高优先级" : example.priority === "medium" ? "中优先级" : "低优先级"}</span>
1419
1433
  </div>
1420
1434
  <div class="example-meta">
1421
- <span>\u{1F4AA} ${example.difficulty}</span>
1422
- <span>\u{1F4C8} ${example.impact}</span>
1435
+ <span>💪 ${example.difficulty}</span>
1436
+ <span>📈 ${example.impact}</span>
1423
1437
  </div>
1424
1438
  </div>
1425
1439
  <div class="example-desc">${example.description}</div>
@@ -1429,7 +1443,7 @@ var PerfReporter = class {
1429
1443
  </div>
1430
1444
  ${example.relatedFiles && example.relatedFiles.length > 0 ? `
1431
1445
  <div class="related-files">
1432
- <strong>\u76F8\u5173\u6587\u4EF6:</strong> ${example.relatedFiles.join(", ")}
1446
+ <strong>相关文件:</strong> ${example.relatedFiles.join(", ")}
1433
1447
  </div>
1434
1448
  ` : ""}
1435
1449
  </div>
@@ -1438,10 +1452,10 @@ var PerfReporter = class {
1438
1452
  </div>
1439
1453
  ` : ""}
1440
1454
 
1441
- <!-- AI \u5206\u6790 -->
1455
+ <!-- AI 分析 -->
1442
1456
  ${result.aiAnalysis ? `
1443
1457
  <div class="section">
1444
- <div class="section-title">\u{1F916} AI \u5206\u6790</div>
1458
+ <div class="section-title">🤖 AI 分析</div>
1445
1459
  <div class="ai-analysis">${this.escapeHtml(result.aiAnalysis)}</div>
1446
1460
  </div>
1447
1461
  ` : ""}
@@ -1455,38 +1469,38 @@ var PerfReporter = class {
1455
1469
  </body>
1456
1470
  </html>
1457
1471
  `.trim();
1458
- const reportsDir = path3.resolve(process.cwd(), "ai-reports");
1459
- if (!fs2.existsSync(reportsDir)) {
1460
- fs2.mkdirSync(reportsDir, { recursive: true });
1472
+ const reportsDir = path4.resolve(process.cwd(), "ai-reports");
1473
+ if (!fs4.existsSync(reportsDir)) {
1474
+ fs4.mkdirSync(reportsDir, { recursive: true });
1461
1475
  }
1462
- const reportPath = path3.resolve(reportsDir, "performance-report.html");
1463
- fs2.writeFileSync(reportPath, html, "utf-8");
1476
+ const reportPath = path4.resolve(reportsDir, "performance-report.html");
1477
+ fs4.writeFileSync(reportPath, html, "utf-8");
1464
1478
  console.log(pc.green(`
1465
- \u{1F4C4} \u6027\u80FD\u62A5\u544A\u5DF2\u751F\u6210: ${pc.cyan(reportPath)}
1479
+ 📄 性能报告已生成: ${pc.cyan(reportPath)}
1466
1480
  `));
1467
1481
  }
1468
1482
  /**
1469
1483
  * 生成 JSON 报告
1470
1484
  */
1471
1485
  async generateJSONReport(result) {
1472
- const reportsDir = path3.resolve(process.cwd(), "ai-reports");
1473
- if (!fs2.existsSync(reportsDir)) {
1474
- fs2.mkdirSync(reportsDir, { recursive: true });
1486
+ const reportsDir = path4.resolve(process.cwd(), "ai-reports");
1487
+ if (!fs4.existsSync(reportsDir)) {
1488
+ fs4.mkdirSync(reportsDir, { recursive: true });
1475
1489
  }
1476
- const reportPath = path3.resolve(reportsDir, "performance-report.json");
1477
- fs2.writeFileSync(reportPath, JSON.stringify(result, null, 2), "utf-8");
1478
- console.log(pc.green(`\u{1F4C4} JSON \u62A5\u544A\u5DF2\u751F\u6210: ${pc.cyan(reportPath)}`));
1490
+ const reportPath = path4.resolve(reportsDir, "performance-report.json");
1491
+ fs4.writeFileSync(reportPath, JSON.stringify(result, null, 2), "utf-8");
1492
+ console.log(pc.green(`📄 JSON 报告已生成: ${pc.cyan(reportPath)}`));
1479
1493
  }
1480
1494
  /**
1481
1495
  * 获取严重程度图标
1482
1496
  */
1483
1497
  getSeverityIcon(severity) {
1484
1498
  const icons = {
1485
- high: "\u{1F534}",
1486
- medium: "\u{1F7E1}",
1487
- low: "\u{1F535}"
1499
+ high: "🔴",
1500
+ medium: "🟡",
1501
+ low: "🔵"
1488
1502
  };
1489
- return icons[severity] || "\u26AA";
1503
+ return icons[severity] || "";
1490
1504
  }
1491
1505
  /**
1492
1506
  * 转义 HTML
@@ -1531,19 +1545,19 @@ function vitePluginAIPerfAnalyzer(options = {}) {
1531
1545
  enforce: "post",
1532
1546
  configResolved(config) {
1533
1547
  if (!enabled) return;
1534
- console.log(pc.cyan("\n\u26A1 AI \u6027\u80FD\u5206\u6790\u63D2\u4EF6\u5DF2\u542F\u52A8..."));
1535
- console.log(`\u{1F4CA} \u5206\u6790\u9608\u503C:`);
1536
- console.log(` \u5355\u6587\u4EF6: ${pc.yellow(threshold.bundleSize + "KB")}`);
1537
- console.log(` \u603B\u5927\u5C0F: ${pc.yellow(threshold.totalSize + "MB")}`);
1548
+ console.log(pc2.cyan("\n AI 性能分析插件已启动..."));
1549
+ console.log(`📊 分析阈值:`);
1550
+ console.log(` 单文件: ${pc2.yellow(threshold.bundleSize + "KB")}`);
1551
+ console.log(` 总大小: ${pc2.yellow(threshold.totalSize + "MB")}`);
1538
1552
  console.log(
1539
- ` Chunk\u6570: ${pc.yellow((threshold.chunkCount ?? 10).toString())}`
1553
+ ` Chunk数: ${pc2.yellow((threshold.chunkCount ?? 10).toString())}`
1540
1554
  );
1541
- console.log(`\u{1F511} API Key: ${apiKey ? "\u5DF2\u914D\u7F6E" : "\u672A\u914D\u7F6E"}
1555
+ console.log(`🔑 API Key: ${apiKey ? "已配置" : "未配置"}
1542
1556
  `);
1543
1557
  },
1544
1558
  async closeBundle() {
1545
1559
  if (!enabled) return;
1546
- console.log("\n\u26A1 \u6B63\u5728\u5206\u6790\u6784\u5EFA\u4EA7\u7269...\n");
1560
+ console.log("\n 正在分析构建产物...\n");
1547
1561
  try {
1548
1562
  analysisResult = await analyzer.analyze();
1549
1563
  await reporter.generate(analysisResult, output);
@@ -1551,13 +1565,14 @@ function vitePluginAIPerfAnalyzer(options = {}) {
1551
1565
  reporter.printConsole(analysisResult);
1552
1566
  }
1553
1567
  } catch (error) {
1554
- console.error("\u274C \u6027\u80FD\u5206\u6790\u5931\u8D25:", error.message);
1568
+ console.error(" 性能分析失败:", error.message);
1555
1569
  }
1556
1570
  }
1557
1571
  };
1558
1572
  }
1559
1573
  var index_default = vitePluginAIPerfAnalyzer;
1560
-
1561
- export { index_default as default, vitePluginAIPerfAnalyzer };
1562
- //# sourceMappingURL=index.mjs.map
1574
+ export {
1575
+ index_default as default,
1576
+ vitePluginAIPerfAnalyzer
1577
+ };
1563
1578
  //# sourceMappingURL=index.mjs.map