vite-plugin-ai-perf-analyzer 1.0.3 → 1.0.5
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.js +305 -266
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +254 -239
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,21 +1,51 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
Object.defineProperty
|
|
4
|
-
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
var
|
|
9
|
-
var
|
|
10
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
11
29
|
|
|
12
|
-
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
default: () => index_default,
|
|
34
|
+
vitePluginAIPerfAnalyzer: () => vitePluginAIPerfAnalyzer
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
var import_picocolors2 = __toESM(require("picocolors"));
|
|
13
38
|
|
|
14
|
-
|
|
15
|
-
var
|
|
16
|
-
var
|
|
39
|
+
// src/analyzer.ts
|
|
40
|
+
var import_fs3 = __toESM(require("fs"));
|
|
41
|
+
var import_path3 = __toESM(require("path"));
|
|
42
|
+
var import_zlib = require("zlib");
|
|
43
|
+
var import_openai = require("@langchain/openai");
|
|
44
|
+
var import_messages = require("@langchain/core/messages");
|
|
17
45
|
|
|
18
|
-
// src/
|
|
46
|
+
// src/dependency-analyzer.ts
|
|
47
|
+
var import_fs = __toESM(require("fs"));
|
|
48
|
+
var import_path = __toESM(require("path"));
|
|
19
49
|
var DependencyAnalyzer = class {
|
|
20
50
|
/**
|
|
21
51
|
* 分析依赖
|
|
@@ -60,12 +90,12 @@ var DependencyAnalyzer = class {
|
|
|
60
90
|
parseImports(file) {
|
|
61
91
|
const imports = [];
|
|
62
92
|
try {
|
|
63
|
-
const distDir =
|
|
64
|
-
const filePath =
|
|
65
|
-
if (!
|
|
93
|
+
const distDir = import_path.default.resolve(process.cwd(), "dist");
|
|
94
|
+
const filePath = import_path.default.join(distDir, file.path);
|
|
95
|
+
if (!import_fs.default.existsSync(filePath)) {
|
|
66
96
|
return imports;
|
|
67
97
|
}
|
|
68
|
-
const content =
|
|
98
|
+
const content = import_fs.default.readFileSync(filePath, "utf-8");
|
|
69
99
|
const patterns = [
|
|
70
100
|
// node_modules 依赖
|
|
71
101
|
/from\s+["']([^"']+)["']/g,
|
|
@@ -99,10 +129,14 @@ var DependencyAnalyzer = class {
|
|
|
99
129
|
})).sort((a, b) => b.usedBy.length - a.usedBy.length).slice(0, 10);
|
|
100
130
|
}
|
|
101
131
|
};
|
|
132
|
+
|
|
133
|
+
// src/history-analyzer.ts
|
|
134
|
+
var import_fs2 = __toESM(require("fs"));
|
|
135
|
+
var import_path2 = __toESM(require("path"));
|
|
102
136
|
var HistoryAnalyzer = class {
|
|
103
137
|
constructor() {
|
|
104
|
-
const reportsDir =
|
|
105
|
-
this.historyFile =
|
|
138
|
+
const reportsDir = import_path2.default.resolve(process.cwd(), "ai-reports");
|
|
139
|
+
this.historyFile = import_path2.default.join(reportsDir, ".perf-history.json");
|
|
106
140
|
}
|
|
107
141
|
/**
|
|
108
142
|
* 对比当前构建与历史记录
|
|
@@ -181,15 +215,15 @@ var HistoryAnalyzer = class {
|
|
|
181
215
|
* 加载历史记录
|
|
182
216
|
*/
|
|
183
217
|
loadHistory() {
|
|
184
|
-
if (!
|
|
218
|
+
if (!import_fs2.default.existsSync(this.historyFile)) {
|
|
185
219
|
return [];
|
|
186
220
|
}
|
|
187
221
|
try {
|
|
188
|
-
const content =
|
|
222
|
+
const content = import_fs2.default.readFileSync(this.historyFile, "utf-8");
|
|
189
223
|
const history = JSON.parse(content);
|
|
190
224
|
return history.slice(-10);
|
|
191
225
|
} catch (error) {
|
|
192
|
-
console.warn("
|
|
226
|
+
console.warn("⚠️ 无法读取历史记录");
|
|
193
227
|
return [];
|
|
194
228
|
}
|
|
195
229
|
}
|
|
@@ -208,12 +242,12 @@ var HistoryAnalyzer = class {
|
|
|
208
242
|
}))
|
|
209
243
|
};
|
|
210
244
|
history.push(record);
|
|
211
|
-
const dir =
|
|
212
|
-
if (!
|
|
213
|
-
|
|
245
|
+
const dir = import_path2.default.dirname(this.historyFile);
|
|
246
|
+
if (!import_fs2.default.existsSync(dir)) {
|
|
247
|
+
import_fs2.default.mkdirSync(dir, { recursive: true });
|
|
214
248
|
}
|
|
215
249
|
const recentHistory = history.slice(-10);
|
|
216
|
-
|
|
250
|
+
import_fs2.default.writeFileSync(
|
|
217
251
|
this.historyFile,
|
|
218
252
|
JSON.stringify(recentHistory, null, 2),
|
|
219
253
|
"utf-8"
|
|
@@ -223,8 +257,8 @@ var HistoryAnalyzer = class {
|
|
|
223
257
|
* 清除历史记录
|
|
224
258
|
*/
|
|
225
259
|
clearHistory() {
|
|
226
|
-
if (
|
|
227
|
-
|
|
260
|
+
if (import_fs2.default.existsSync(this.historyFile)) {
|
|
261
|
+
import_fs2.default.unlinkSync(this.historyFile);
|
|
228
262
|
}
|
|
229
263
|
}
|
|
230
264
|
};
|
|
@@ -245,7 +279,7 @@ var OptimizationExamples = class {
|
|
|
245
279
|
if (issues.some((i) => i.type === "optimization")) {
|
|
246
280
|
examples.push(this.getCompressionExample());
|
|
247
281
|
}
|
|
248
|
-
if (issues.some((i) => i.description.includes("
|
|
282
|
+
if (issues.some((i) => i.description.includes("图片"))) {
|
|
249
283
|
examples.push(this.getImageOptimizationExample());
|
|
250
284
|
}
|
|
251
285
|
if (issues.some((i) => i.type === "size" && i.severity === "high")) {
|
|
@@ -259,11 +293,11 @@ var OptimizationExamples = class {
|
|
|
259
293
|
*/
|
|
260
294
|
getCodeSplittingExample() {
|
|
261
295
|
return {
|
|
262
|
-
title: "
|
|
263
|
-
description: "
|
|
296
|
+
title: "配置代码分割",
|
|
297
|
+
description: "将大文件拆分成多个小文件,提高加载性能",
|
|
264
298
|
priority: "high",
|
|
265
|
-
impact: "
|
|
266
|
-
difficulty: "
|
|
299
|
+
impact: "可减少 30-50% 的初始加载大小",
|
|
300
|
+
difficulty: "简单",
|
|
267
301
|
code: {
|
|
268
302
|
language: "typescript",
|
|
269
303
|
content: `// vite.config.ts
|
|
@@ -272,20 +306,20 @@ export default defineConfig({
|
|
|
272
306
|
rollupOptions: {
|
|
273
307
|
output: {
|
|
274
308
|
manualChunks: {
|
|
275
|
-
//
|
|
309
|
+
// 提取 Vue 核心库
|
|
276
310
|
'vue-vendor': ['vue', 'vue-router', 'pinia'],
|
|
277
311
|
|
|
278
|
-
//
|
|
312
|
+
// 提取 UI 组件库
|
|
279
313
|
'ui-vendor': ['element-plus', '@element-plus/icons-vue'],
|
|
280
314
|
|
|
281
|
-
//
|
|
315
|
+
// 提取工具库
|
|
282
316
|
'utils': ['lodash-es', 'dayjs', 'axios'],
|
|
283
317
|
|
|
284
|
-
//
|
|
318
|
+
// 提取图表库(如果使用)
|
|
285
319
|
'charts': ['echarts', 'chart.js'],
|
|
286
320
|
},
|
|
287
321
|
|
|
288
|
-
//
|
|
322
|
+
// 自动分割大于 500KB 的文件
|
|
289
323
|
chunkFileNames: (chunkInfo) => {
|
|
290
324
|
const facadeModuleId = chunkInfo.facadeModuleId
|
|
291
325
|
? chunkInfo.facadeModuleId.split('/').pop()
|
|
@@ -295,7 +329,7 @@ export default defineConfig({
|
|
|
295
329
|
},
|
|
296
330
|
},
|
|
297
331
|
|
|
298
|
-
//
|
|
332
|
+
// 设置 chunk 大小警告阈值
|
|
299
333
|
chunkSizeWarningLimit: 500,
|
|
300
334
|
},
|
|
301
335
|
});`
|
|
@@ -308,11 +342,11 @@ export default defineConfig({
|
|
|
308
342
|
getDependencyOptimizationExample(duplicates) {
|
|
309
343
|
const topDuplicates = duplicates.slice(0, 3).map((d) => d.name);
|
|
310
344
|
return {
|
|
311
|
-
title: "
|
|
312
|
-
description:
|
|
345
|
+
title: "优化重复依赖",
|
|
346
|
+
description: `检测到 ${duplicates.length} 个重复打包的依赖,建议提取到公共 chunk`,
|
|
313
347
|
priority: "high",
|
|
314
|
-
impact:
|
|
315
|
-
difficulty: "
|
|
348
|
+
impact: `可减少 ${(duplicates.reduce((sum, d) => sum + d.size, 0) / 1024 / 1024).toFixed(2)}MB`,
|
|
349
|
+
difficulty: "简单",
|
|
316
350
|
code: {
|
|
317
351
|
language: "typescript",
|
|
318
352
|
content: `// vite.config.ts
|
|
@@ -321,7 +355,7 @@ export default defineConfig({
|
|
|
321
355
|
rollupOptions: {
|
|
322
356
|
output: {
|
|
323
357
|
manualChunks: {
|
|
324
|
-
//
|
|
358
|
+
// 提取重复依赖到公共 chunk
|
|
325
359
|
'common-vendor': [
|
|
326
360
|
${topDuplicates.map((name) => `'${name}'`).join(",\n ")}
|
|
327
361
|
],
|
|
@@ -330,7 +364,7 @@ export default defineConfig({
|
|
|
330
364
|
},
|
|
331
365
|
},
|
|
332
366
|
|
|
333
|
-
//
|
|
367
|
+
// 或者使用自动分割策略
|
|
334
368
|
optimizeDeps: {
|
|
335
369
|
include: [
|
|
336
370
|
${topDuplicates.map((name) => `'${name}'`).join(",\n ")}
|
|
@@ -346,31 +380,31 @@ export default defineConfig({
|
|
|
346
380
|
*/
|
|
347
381
|
getCompressionExample() {
|
|
348
382
|
return {
|
|
349
|
-
title: "
|
|
350
|
-
description: "
|
|
383
|
+
title: "启用 Gzip/Brotli 压缩",
|
|
384
|
+
description: "使用压缩插件减少传输大小",
|
|
351
385
|
priority: "medium",
|
|
352
|
-
impact: "
|
|
353
|
-
difficulty: "
|
|
386
|
+
impact: "可减少 60-70% 的传输大小",
|
|
387
|
+
difficulty: "简单",
|
|
354
388
|
code: {
|
|
355
389
|
language: "bash",
|
|
356
|
-
content: `# 1.
|
|
390
|
+
content: `# 1. 安装压缩插件
|
|
357
391
|
npm install vite-plugin-compression -D
|
|
358
392
|
|
|
359
|
-
# 2.
|
|
393
|
+
# 2. 配置插件
|
|
360
394
|
# vite.config.ts
|
|
361
395
|
import viteCompression from 'vite-plugin-compression';
|
|
362
396
|
|
|
363
397
|
export default defineConfig({
|
|
364
398
|
plugins: [
|
|
365
|
-
// Gzip
|
|
399
|
+
// Gzip 压缩
|
|
366
400
|
viteCompression({
|
|
367
401
|
algorithm: 'gzip',
|
|
368
402
|
ext: '.gz',
|
|
369
|
-
threshold: 10240, //
|
|
403
|
+
threshold: 10240, // 大于 10KB 才压缩
|
|
370
404
|
deleteOriginFile: false,
|
|
371
405
|
}),
|
|
372
406
|
|
|
373
|
-
// Brotli
|
|
407
|
+
// Brotli 压缩(可选,压缩率更高)
|
|
374
408
|
viteCompression({
|
|
375
409
|
algorithm: 'brotliCompress',
|
|
376
410
|
ext: '.br',
|
|
@@ -380,7 +414,7 @@ export default defineConfig({
|
|
|
380
414
|
],
|
|
381
415
|
});
|
|
382
416
|
|
|
383
|
-
# 3. Nginx
|
|
417
|
+
# 3. Nginx 配置(服务器端)
|
|
384
418
|
# nginx.conf
|
|
385
419
|
gzip on;
|
|
386
420
|
gzip_types text/plain text/css application/json application/javascript;
|
|
@@ -393,40 +427,40 @@ gzip_min_length 1024;`
|
|
|
393
427
|
*/
|
|
394
428
|
getImageOptimizationExample() {
|
|
395
429
|
return {
|
|
396
|
-
title: "
|
|
397
|
-
description: "
|
|
430
|
+
title: "优化图片资源",
|
|
431
|
+
description: "压缩图片并转换为现代格式",
|
|
398
432
|
priority: "medium",
|
|
399
|
-
impact: "
|
|
400
|
-
difficulty: "
|
|
433
|
+
impact: "可减少 50-80% 的图片大小",
|
|
434
|
+
difficulty: "简单",
|
|
401
435
|
code: {
|
|
402
436
|
language: "bash",
|
|
403
|
-
content: `# 1.
|
|
437
|
+
content: `# 1. 安装图片优化插件
|
|
404
438
|
npm install vite-plugin-imagemin -D
|
|
405
439
|
|
|
406
|
-
# 2.
|
|
440
|
+
# 2. 配置插件
|
|
407
441
|
# vite.config.ts
|
|
408
442
|
import viteImagemin from 'vite-plugin-imagemin';
|
|
409
443
|
|
|
410
444
|
export default defineConfig({
|
|
411
445
|
plugins: [
|
|
412
446
|
viteImagemin({
|
|
413
|
-
// GIF
|
|
447
|
+
// GIF 优化
|
|
414
448
|
gifsicle: {
|
|
415
449
|
optimizationLevel: 7,
|
|
416
450
|
interlaced: false,
|
|
417
451
|
},
|
|
418
452
|
|
|
419
|
-
// PNG
|
|
453
|
+
// PNG 优化
|
|
420
454
|
optipng: {
|
|
421
455
|
optimizationLevel: 7,
|
|
422
456
|
},
|
|
423
457
|
|
|
424
|
-
// JPEG
|
|
458
|
+
// JPEG 优化
|
|
425
459
|
mozjpeg: {
|
|
426
460
|
quality: 80,
|
|
427
461
|
},
|
|
428
462
|
|
|
429
|
-
// SVG
|
|
463
|
+
// SVG 优化
|
|
430
464
|
svgo: {
|
|
431
465
|
plugins: [
|
|
432
466
|
{ name: 'removeViewBox', active: false },
|
|
@@ -434,7 +468,7 @@ export default defineConfig({
|
|
|
434
468
|
],
|
|
435
469
|
},
|
|
436
470
|
|
|
437
|
-
// WebP
|
|
471
|
+
// WebP 转换
|
|
438
472
|
webp: {
|
|
439
473
|
quality: 80,
|
|
440
474
|
},
|
|
@@ -442,7 +476,7 @@ export default defineConfig({
|
|
|
442
476
|
],
|
|
443
477
|
});
|
|
444
478
|
|
|
445
|
-
# 3.
|
|
479
|
+
# 3. 使用 WebP 格式(HTML)
|
|
446
480
|
<picture>
|
|
447
481
|
<source srcset="image.webp" type="image/webp">
|
|
448
482
|
<img src="image.jpg" alt="description">
|
|
@@ -455,20 +489,20 @@ export default defineConfig({
|
|
|
455
489
|
*/
|
|
456
490
|
getDynamicImportExample() {
|
|
457
491
|
return {
|
|
458
|
-
title: "
|
|
459
|
-
description: "
|
|
492
|
+
title: "使用动态导入(懒加载)",
|
|
493
|
+
description: "按需加载组件和路由,减少初始加载大小",
|
|
460
494
|
priority: "high",
|
|
461
|
-
impact: "
|
|
462
|
-
difficulty: "
|
|
495
|
+
impact: "可减少 40-60% 的初始加载大小",
|
|
496
|
+
difficulty: "中等",
|
|
463
497
|
code: {
|
|
464
498
|
language: "typescript",
|
|
465
|
-
content: `// 1.
|
|
499
|
+
content: `// 1. 路由懒加载
|
|
466
500
|
// router/index.ts
|
|
467
501
|
const routes = [
|
|
468
502
|
{
|
|
469
503
|
path: '/dashboard',
|
|
470
504
|
name: 'Dashboard',
|
|
471
|
-
//
|
|
505
|
+
// 使用动态导入
|
|
472
506
|
component: () => import('@/views/Dashboard.vue'),
|
|
473
507
|
},
|
|
474
508
|
{
|
|
@@ -478,17 +512,17 @@ const routes = [
|
|
|
478
512
|
},
|
|
479
513
|
];
|
|
480
514
|
|
|
481
|
-
// 2.
|
|
515
|
+
// 2. 组件懒加载
|
|
482
516
|
// App.vue
|
|
483
517
|
<script setup>
|
|
484
518
|
import { defineAsyncComponent } from 'vue';
|
|
485
519
|
|
|
486
|
-
//
|
|
520
|
+
// 异步组件
|
|
487
521
|
const HeavyComponent = defineAsyncComponent(() =>
|
|
488
522
|
import('./components/HeavyComponent.vue')
|
|
489
523
|
);
|
|
490
524
|
|
|
491
|
-
//
|
|
525
|
+
// 带加载状态的异步组件
|
|
492
526
|
const AsyncComponent = defineAsyncComponent({
|
|
493
527
|
loader: () => import('./components/AsyncComponent.vue'),
|
|
494
528
|
loadingComponent: LoadingSpinner,
|
|
@@ -498,12 +532,12 @@ const AsyncComponent = defineAsyncComponent({
|
|
|
498
532
|
});
|
|
499
533
|
</script>
|
|
500
534
|
|
|
501
|
-
// 3.
|
|
535
|
+
// 3. 条件加载
|
|
502
536
|
<script setup>
|
|
503
537
|
const loadCharts = async () => {
|
|
504
538
|
if (needCharts) {
|
|
505
539
|
const { default: ECharts } = await import('echarts');
|
|
506
|
-
//
|
|
540
|
+
// 使用 ECharts
|
|
507
541
|
}
|
|
508
542
|
};
|
|
509
543
|
</script>`
|
|
@@ -515,50 +549,50 @@ const loadCharts = async () => {
|
|
|
515
549
|
*/
|
|
516
550
|
getTreeShakingExample() {
|
|
517
551
|
return {
|
|
518
|
-
title: "
|
|
519
|
-
description: "
|
|
552
|
+
title: "优化 Tree-shaking",
|
|
553
|
+
description: "确保未使用的代码被正确移除",
|
|
520
554
|
priority: "medium",
|
|
521
|
-
impact: "
|
|
522
|
-
difficulty: "
|
|
555
|
+
impact: "可减少 10-30% 的代码大小",
|
|
556
|
+
difficulty: "中等",
|
|
523
557
|
code: {
|
|
524
558
|
language: "typescript",
|
|
525
|
-
content: `// 1.
|
|
526
|
-
//
|
|
559
|
+
content: `// 1. 使用 ES6 模块导入(支持 tree-shaking)
|
|
560
|
+
// ❌ 不推荐
|
|
527
561
|
import _ from 'lodash';
|
|
528
562
|
const result = _.debounce(fn, 300);
|
|
529
563
|
|
|
530
|
-
//
|
|
564
|
+
// ✅ 推荐
|
|
531
565
|
import { debounce } from 'lodash-es';
|
|
532
566
|
const result = debounce(fn, 300);
|
|
533
567
|
|
|
534
|
-
// 2.
|
|
568
|
+
// 2. 配置 package.json
|
|
535
569
|
{
|
|
536
|
-
"sideEffects": false, //
|
|
537
|
-
//
|
|
570
|
+
"sideEffects": false, // 标记为无副作用
|
|
571
|
+
// 或指定有副作用的文件
|
|
538
572
|
"sideEffects": ["*.css", "*.scss"]
|
|
539
573
|
}
|
|
540
574
|
|
|
541
|
-
// 3. Vite
|
|
575
|
+
// 3. Vite 配置
|
|
542
576
|
// vite.config.ts
|
|
543
577
|
export default defineConfig({
|
|
544
578
|
build: {
|
|
545
|
-
//
|
|
579
|
+
// 启用 tree-shaking
|
|
546
580
|
minify: 'terser',
|
|
547
581
|
terserOptions: {
|
|
548
582
|
compress: {
|
|
549
|
-
drop_console: true, //
|
|
550
|
-
drop_debugger: true, //
|
|
551
|
-
pure_funcs: ['console.log'], //
|
|
583
|
+
drop_console: true, // 移除 console
|
|
584
|
+
drop_debugger: true, // 移除 debugger
|
|
585
|
+
pure_funcs: ['console.log'], // 移除特定函数
|
|
552
586
|
},
|
|
553
587
|
},
|
|
554
588
|
},
|
|
555
589
|
});
|
|
556
590
|
|
|
557
|
-
// 4.
|
|
558
|
-
//
|
|
591
|
+
// 4. 避免副作用导入
|
|
592
|
+
// ❌ 不推荐(会导入整个模块)
|
|
559
593
|
import 'some-library';
|
|
560
594
|
|
|
561
|
-
//
|
|
595
|
+
// ✅ 推荐(明确导入需要的部分)
|
|
562
596
|
import { specificFunction } from 'some-library';`
|
|
563
597
|
}
|
|
564
598
|
};
|
|
@@ -574,7 +608,7 @@ var PerfAnalyzer = class {
|
|
|
574
608
|
this.historyAnalyzer = new HistoryAnalyzer();
|
|
575
609
|
this.examplesGenerator = new OptimizationExamples();
|
|
576
610
|
if (options.apiKey) {
|
|
577
|
-
this.llm = new
|
|
611
|
+
this.llm = new import_openai.ChatOpenAI({
|
|
578
612
|
openAIApiKey: options.apiKey,
|
|
579
613
|
configuration: { baseURL: options.apiUrl },
|
|
580
614
|
modelName: options.model,
|
|
@@ -587,15 +621,15 @@ var PerfAnalyzer = class {
|
|
|
587
621
|
* 分析构建产物
|
|
588
622
|
*/
|
|
589
623
|
async analyze() {
|
|
590
|
-
const distDir =
|
|
591
|
-
if (!
|
|
592
|
-
throw new Error("
|
|
624
|
+
const distDir = import_path3.default.resolve(process.cwd(), "dist");
|
|
625
|
+
if (!import_fs3.default.existsSync(distDir)) {
|
|
626
|
+
throw new Error("构建目录不存在,请先执行构建");
|
|
593
627
|
}
|
|
594
628
|
const bundles = this.collectBundles(distDir);
|
|
595
629
|
const summary = this.calculateSummary(bundles);
|
|
596
|
-
console.log("
|
|
630
|
+
console.log("📦 正在分析依赖...");
|
|
597
631
|
const dependencies = this.depAnalyzer.analyzeDependencies(bundles);
|
|
598
|
-
console.log("
|
|
632
|
+
console.log("📊 正在对比历史记录...");
|
|
599
633
|
const comparison = this.historyAnalyzer.compare(
|
|
600
634
|
bundles,
|
|
601
635
|
summary.totalSize,
|
|
@@ -603,14 +637,14 @@ var PerfAnalyzer = class {
|
|
|
603
637
|
);
|
|
604
638
|
const issues = this.detectIssues(bundles, summary, dependencies);
|
|
605
639
|
const suggestions = this.generateSuggestions(issues);
|
|
606
|
-
console.log("
|
|
640
|
+
console.log("💡 正在生成优化示例...");
|
|
607
641
|
const optimizationExamples = this.examplesGenerator.generate(
|
|
608
642
|
issues,
|
|
609
643
|
dependencies.duplicates
|
|
610
644
|
);
|
|
611
645
|
let aiAnalysis;
|
|
612
646
|
if (this.llm) {
|
|
613
|
-
console.log("
|
|
647
|
+
console.log("🤖 正在使用 AI 分析性能...\n");
|
|
614
648
|
aiAnalysis = await this.performAIAnalysis(
|
|
615
649
|
bundles,
|
|
616
650
|
summary,
|
|
@@ -635,16 +669,16 @@ var PerfAnalyzer = class {
|
|
|
635
669
|
*/
|
|
636
670
|
collectBundles(dir, baseDir = dir) {
|
|
637
671
|
const bundles = [];
|
|
638
|
-
const files =
|
|
672
|
+
const files = import_fs3.default.readdirSync(dir);
|
|
639
673
|
for (const file of files) {
|
|
640
|
-
const filePath =
|
|
641
|
-
const stat =
|
|
674
|
+
const filePath = import_path3.default.join(dir, file);
|
|
675
|
+
const stat = import_fs3.default.statSync(filePath);
|
|
642
676
|
if (stat.isDirectory()) {
|
|
643
677
|
bundles.push(...this.collectBundles(filePath, baseDir));
|
|
644
678
|
} else {
|
|
645
|
-
const content =
|
|
646
|
-
const gzipSize =
|
|
647
|
-
const relativePath =
|
|
679
|
+
const content = import_fs3.default.readFileSync(filePath);
|
|
680
|
+
const gzipSize = (0, import_zlib.gzipSync)(content).length;
|
|
681
|
+
const relativePath = import_path3.default.relative(baseDir, filePath);
|
|
648
682
|
bundles.push({
|
|
649
683
|
name: file,
|
|
650
684
|
size: stat.size,
|
|
@@ -660,7 +694,7 @@ var PerfAnalyzer = class {
|
|
|
660
694
|
* 获取文件类型
|
|
661
695
|
*/
|
|
662
696
|
getFileType(filename) {
|
|
663
|
-
const ext =
|
|
697
|
+
const ext = import_path3.default.extname(filename).toLowerCase();
|
|
664
698
|
const typeMap = {
|
|
665
699
|
".js": "javascript",
|
|
666
700
|
".mjs": "javascript",
|
|
@@ -718,9 +752,9 @@ var PerfAnalyzer = class {
|
|
|
718
752
|
issues.push({
|
|
719
753
|
type: "size",
|
|
720
754
|
severity: "high",
|
|
721
|
-
title: "
|
|
722
|
-
description:
|
|
723
|
-
suggestion: "
|
|
755
|
+
title: "构建产物总大小过大",
|
|
756
|
+
description: `总大小 ${totalSizeMB.toFixed(2)}MB 超过阈值 ${threshold.totalSize}MB`,
|
|
757
|
+
suggestion: "考虑代码分割、tree-shaking、压缩等优化手段"
|
|
724
758
|
});
|
|
725
759
|
}
|
|
726
760
|
const largeBundles = bundles.filter(
|
|
@@ -730,12 +764,12 @@ var PerfAnalyzer = class {
|
|
|
730
764
|
issues.push({
|
|
731
765
|
type: "size",
|
|
732
766
|
severity: "medium",
|
|
733
|
-
title: "
|
|
734
|
-
description:
|
|
767
|
+
title: "存在过大的单个文件",
|
|
768
|
+
description: `发现 ${largeBundles.length} 个文件超过 ${threshold.bundleSize}KB`,
|
|
735
769
|
files: largeBundles.map(
|
|
736
770
|
(b) => `${b.name} (${(b.size / 1024).toFixed(2)}KB)`
|
|
737
771
|
),
|
|
738
|
-
suggestion: "
|
|
772
|
+
suggestion: "考虑拆分大文件,使用动态导入或代码分割"
|
|
739
773
|
});
|
|
740
774
|
}
|
|
741
775
|
const jsFiles = bundles.filter((b) => b.type === "javascript");
|
|
@@ -743,9 +777,9 @@ var PerfAnalyzer = class {
|
|
|
743
777
|
issues.push({
|
|
744
778
|
type: "count",
|
|
745
779
|
severity: "low",
|
|
746
|
-
title: "JavaScript
|
|
747
|
-
description:
|
|
748
|
-
suggestion: "
|
|
780
|
+
title: "JavaScript 文件数量过多",
|
|
781
|
+
description: `共有 ${jsFiles.length} 个 JS 文件,超过阈值 ${threshold.chunkCount}`,
|
|
782
|
+
suggestion: "过多的文件会增加 HTTP 请求数,考虑合并小文件"
|
|
749
783
|
});
|
|
750
784
|
}
|
|
751
785
|
const images = bundles.filter((b) => b.type === "image");
|
|
@@ -754,12 +788,12 @@ var PerfAnalyzer = class {
|
|
|
754
788
|
issues.push({
|
|
755
789
|
type: "optimization",
|
|
756
790
|
severity: "medium",
|
|
757
|
-
title: "
|
|
758
|
-
description:
|
|
791
|
+
title: "存在未优化的图片",
|
|
792
|
+
description: `发现 ${largeImages.length} 个大于 100KB 的图片`,
|
|
759
793
|
files: largeImages.map(
|
|
760
794
|
(img) => `${img.name} (${(img.size / 1024).toFixed(2)}KB)`
|
|
761
795
|
),
|
|
762
|
-
suggestion: "
|
|
796
|
+
suggestion: "使用图片压缩工具,或转换为 WebP 格式"
|
|
763
797
|
});
|
|
764
798
|
}
|
|
765
799
|
const sourcemaps = bundles.filter((b) => b.type === "sourcemap");
|
|
@@ -768,11 +802,11 @@ var PerfAnalyzer = class {
|
|
|
768
802
|
issues.push({
|
|
769
803
|
type: "optimization",
|
|
770
804
|
severity: "low",
|
|
771
|
-
title: "
|
|
772
|
-
description: `Sourcemap
|
|
805
|
+
title: "生产环境包含 sourcemap",
|
|
806
|
+
description: `Sourcemap 文件占用 ${(totalMapSize / 1024 / 1024).toFixed(
|
|
773
807
|
2
|
|
774
808
|
)}MB`,
|
|
775
|
-
suggestion: "
|
|
809
|
+
suggestion: "生产环境建议禁用 sourcemap 或使用外部 sourcemap"
|
|
776
810
|
});
|
|
777
811
|
}
|
|
778
812
|
if (dependencies && dependencies.duplicates.length > 0) {
|
|
@@ -780,12 +814,12 @@ var PerfAnalyzer = class {
|
|
|
780
814
|
issues.push({
|
|
781
815
|
type: "dependency",
|
|
782
816
|
severity: "medium",
|
|
783
|
-
title: "
|
|
784
|
-
description:
|
|
817
|
+
title: "检测到重复打包的依赖",
|
|
818
|
+
description: `发现 ${dependencies.duplicates.length} 个依赖被多次打包`,
|
|
785
819
|
files: topDuplicates.map(
|
|
786
|
-
(d) => `${d.name} (
|
|
820
|
+
(d) => `${d.name} (被 ${d.usedBy.length} 个文件使用, ${(d.size / 1024).toFixed(2)}KB)`
|
|
787
821
|
),
|
|
788
|
-
suggestion: "
|
|
822
|
+
suggestion: "将重复依赖提取到公共 chunk 中"
|
|
789
823
|
});
|
|
790
824
|
}
|
|
791
825
|
return issues;
|
|
@@ -796,16 +830,16 @@ var PerfAnalyzer = class {
|
|
|
796
830
|
generateSuggestions(issues) {
|
|
797
831
|
const suggestions = [];
|
|
798
832
|
if (issues.some((i) => i.type === "size")) {
|
|
799
|
-
suggestions.push("
|
|
800
|
-
suggestions.push("
|
|
801
|
-
suggestions.push("
|
|
833
|
+
suggestions.push("启用 gzip/brotli 压缩");
|
|
834
|
+
suggestions.push("配置 Vite 的 build.rollupOptions 进行代码分割");
|
|
835
|
+
suggestions.push("使用 vite-plugin-compression 插件");
|
|
802
836
|
}
|
|
803
837
|
if (issues.some((i) => i.type === "optimization")) {
|
|
804
|
-
suggestions.push("
|
|
805
|
-
suggestions.push("
|
|
838
|
+
suggestions.push("使用 vite-plugin-imagemin 优化图片");
|
|
839
|
+
suggestions.push("配置 CSS 压缩和 tree-shaking");
|
|
806
840
|
}
|
|
807
841
|
if (issues.length === 0) {
|
|
808
|
-
suggestions.push("
|
|
842
|
+
suggestions.push("构建产物已经很优秀,继续保持!");
|
|
809
843
|
}
|
|
810
844
|
return suggestions;
|
|
811
845
|
}
|
|
@@ -813,12 +847,12 @@ var PerfAnalyzer = class {
|
|
|
813
847
|
* AI 性能分析
|
|
814
848
|
*/
|
|
815
849
|
async performAIAnalysis(bundles, summary, issues, dependencies, comparison) {
|
|
816
|
-
const systemPrompt = new
|
|
817
|
-
"
|
|
850
|
+
const systemPrompt = new import_messages.SystemMessage(
|
|
851
|
+
"你是一个专业的前端性能优化专家,精通 Vite、Webpack 等构建工具。请分析构建产物并提供专业的优化建议。"
|
|
818
852
|
);
|
|
819
853
|
const bundlesSummary = summary.largestFiles.slice(0, 5).map((b) => `- ${b.name}: ${(b.size / 1024).toFixed(2)}KB`).join("\n");
|
|
820
854
|
const typesSummary = Object.entries(summary.byType).map(
|
|
821
|
-
([type, info]) => `- ${type}: ${info.count}
|
|
855
|
+
([type, info]) => `- ${type}: ${info.count} 个文件, ${(info.size / 1024).toFixed(2)}KB`
|
|
822
856
|
).join("\n");
|
|
823
857
|
const issuesSummary = issues.map(
|
|
824
858
|
(issue) => `- [${issue.severity}] ${issue.title}: ${issue.description}`
|
|
@@ -826,51 +860,56 @@ var PerfAnalyzer = class {
|
|
|
826
860
|
let dependencySummary = "";
|
|
827
861
|
if (dependencies) {
|
|
828
862
|
dependencySummary = `
|
|
829
|
-
##
|
|
830
|
-
-
|
|
831
|
-
-
|
|
832
|
-
-
|
|
863
|
+
## 依赖分析
|
|
864
|
+
- 总依赖数: ${dependencies.total}
|
|
865
|
+
- 重复依赖: ${dependencies.duplicates.length} 个
|
|
866
|
+
- 最大依赖: ${dependencies.largest.slice(0, 3).map((d) => `${d.name} (${(d.size / 1024).toFixed(2)}KB)`).join(", ")}`;
|
|
833
867
|
}
|
|
834
868
|
let comparisonSummary = "";
|
|
835
869
|
if (comparison) {
|
|
836
|
-
const sizeChange = comparison.totalSize.trend === "increased" ? "
|
|
870
|
+
const sizeChange = comparison.totalSize.trend === "increased" ? "增加" : "减少";
|
|
837
871
|
comparisonSummary = `
|
|
838
|
-
##
|
|
839
|
-
-
|
|
840
|
-
-
|
|
841
|
-
-
|
|
842
|
-
-
|
|
843
|
-
}
|
|
844
|
-
const userPrompt = new
|
|
845
|
-
|
|
872
|
+
## 历史对比
|
|
873
|
+
- 总大小${sizeChange}: ${Math.abs(comparison.totalSize.diffPercent).toFixed(2)}%
|
|
874
|
+
- 文件数量变化: ${comparison.fileCount.diff > 0 ? "+" : ""}${comparison.fileCount.diff}
|
|
875
|
+
- 新增文件: ${comparison.newFiles.length} 个
|
|
876
|
+
- 删除文件: ${comparison.removedFiles.length} 个`;
|
|
877
|
+
}
|
|
878
|
+
const userPrompt = new import_messages.HumanMessage(`
|
|
879
|
+
请分析以下构建产物信息:
|
|
846
880
|
|
|
847
|
-
##
|
|
848
|
-
-
|
|
849
|
-
- Gzip
|
|
850
|
-
-
|
|
881
|
+
## 总体统计
|
|
882
|
+
- 总大小: ${(summary.totalSize / 1024 / 1024).toFixed(2)}MB
|
|
883
|
+
- Gzip 后: ${(summary.totalGzipSize / 1024 / 1024).toFixed(2)}MB
|
|
884
|
+
- 文件数量: ${summary.fileCount}
|
|
851
885
|
|
|
852
|
-
##
|
|
886
|
+
## 最大的文件
|
|
853
887
|
${bundlesSummary}
|
|
854
888
|
|
|
855
|
-
##
|
|
889
|
+
## 按类型分组
|
|
856
890
|
${typesSummary}
|
|
857
891
|
${dependencySummary}
|
|
858
892
|
${comparisonSummary}
|
|
859
893
|
|
|
860
|
-
##
|
|
861
|
-
${issuesSummary || "
|
|
894
|
+
## 检测到的问题
|
|
895
|
+
${issuesSummary || "无明显问题"}
|
|
862
896
|
|
|
863
|
-
|
|
864
|
-
1.
|
|
865
|
-
2.
|
|
866
|
-
3.
|
|
897
|
+
请提供:
|
|
898
|
+
1. 性能评估(3-5 句话)
|
|
899
|
+
2. 具体优化建议(3-5 条,每条简洁明了)
|
|
900
|
+
3. 优先级排序
|
|
867
901
|
|
|
868
|
-
|
|
902
|
+
请用简洁专业的语言回答,不要过于冗长。
|
|
869
903
|
`);
|
|
870
904
|
const response = await this.llm.invoke([systemPrompt, userPrompt]);
|
|
871
905
|
return response.content.toString();
|
|
872
906
|
}
|
|
873
907
|
};
|
|
908
|
+
|
|
909
|
+
// src/reporter.ts
|
|
910
|
+
var import_fs4 = __toESM(require("fs"));
|
|
911
|
+
var import_path4 = __toESM(require("path"));
|
|
912
|
+
var import_picocolors = __toESM(require("picocolors"));
|
|
874
913
|
var PerfReporter = class {
|
|
875
914
|
/**
|
|
876
915
|
* 生成报告
|
|
@@ -887,88 +926,88 @@ var PerfReporter = class {
|
|
|
887
926
|
* 控制台输出
|
|
888
927
|
*/
|
|
889
928
|
printConsole(result) {
|
|
890
|
-
console.log("\n
|
|
891
|
-
console.log("
|
|
892
|
-
console.log("
|
|
893
|
-
console.log("
|
|
929
|
+
console.log("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
|
930
|
+
console.log("⚡ 性能分析报告");
|
|
931
|
+
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
|
|
932
|
+
console.log("📊 总体统计:");
|
|
894
933
|
console.log(
|
|
895
|
-
`
|
|
934
|
+
` 总大小: ${(result.summary.totalSize / 1024 / 1024).toFixed(2)}MB`
|
|
896
935
|
);
|
|
897
936
|
console.log(
|
|
898
|
-
` Gzip
|
|
937
|
+
` Gzip后: ${(result.summary.totalGzipSize / 1024 / 1024).toFixed(2)}MB`
|
|
899
938
|
);
|
|
900
|
-
console.log(`
|
|
939
|
+
console.log(` 文件数: ${result.summary.fileCount}
|
|
901
940
|
`);
|
|
902
941
|
if (result.comparison) {
|
|
903
|
-
console.log("
|
|
942
|
+
console.log("📈 历史对比:");
|
|
904
943
|
const { totalSize, fileCount, newFiles, removedFiles } = result.comparison;
|
|
905
|
-
const sizeIcon = totalSize.trend === "increased" ? "
|
|
944
|
+
const sizeIcon = totalSize.trend === "increased" ? "📈" : "📉";
|
|
906
945
|
console.log(
|
|
907
|
-
` ${sizeIcon}
|
|
946
|
+
` ${sizeIcon} 总大小: ${totalSize.trend === "increased" ? "+" : ""}${(totalSize.diff / 1024 / 1024).toFixed(2)}MB (${totalSize.diffPercent > 0 ? "+" : ""}${totalSize.diffPercent.toFixed(2)}%)`
|
|
908
947
|
);
|
|
909
948
|
console.log(
|
|
910
|
-
`
|
|
949
|
+
` 📊 文件数: ${fileCount.diff > 0 ? "+" : ""}${fileCount.diff}`
|
|
911
950
|
);
|
|
912
951
|
if (newFiles.length > 0) {
|
|
913
|
-
console.log(`
|
|
952
|
+
console.log(` ✨ 新增: ${newFiles.slice(0, 3).join(", ")}`);
|
|
914
953
|
}
|
|
915
954
|
if (removedFiles.length > 0) {
|
|
916
|
-
console.log(`
|
|
955
|
+
console.log(` 🗑️ 删除: ${removedFiles.slice(0, 3).join(", ")}`);
|
|
917
956
|
}
|
|
918
957
|
console.log();
|
|
919
958
|
}
|
|
920
959
|
if (result.dependencies) {
|
|
921
|
-
console.log("
|
|
922
|
-
console.log(`
|
|
960
|
+
console.log("📦 依赖分析:");
|
|
961
|
+
console.log(` 总依赖数: ${result.dependencies.total}`);
|
|
923
962
|
if (result.dependencies.duplicates.length > 0) {
|
|
924
963
|
console.log(
|
|
925
|
-
`
|
|
964
|
+
` ⚠️ 重复依赖: ${result.dependencies.duplicates.length} 个`
|
|
926
965
|
);
|
|
927
966
|
result.dependencies.duplicates.slice(0, 3).forEach((dep) => {
|
|
928
967
|
console.log(
|
|
929
|
-
` - ${dep.name}:
|
|
968
|
+
` - ${dep.name}: 被 ${dep.usedBy.length} 个文件使用`
|
|
930
969
|
);
|
|
931
970
|
});
|
|
932
971
|
}
|
|
933
972
|
console.log();
|
|
934
973
|
}
|
|
935
|
-
console.log("
|
|
974
|
+
console.log("📦 最大的文件:");
|
|
936
975
|
result.summary.largestFiles.slice(0, 5).forEach((file, index) => {
|
|
937
976
|
console.log(
|
|
938
977
|
` ${index + 1}. ${file.name}: ${(file.size / 1024).toFixed(2)}KB`
|
|
939
978
|
);
|
|
940
979
|
});
|
|
941
980
|
console.log();
|
|
942
|
-
console.log("
|
|
981
|
+
console.log("📋 按类型统计:");
|
|
943
982
|
Object.entries(result.summary.byType).forEach(([type, info]) => {
|
|
944
983
|
console.log(
|
|
945
|
-
` ${type}: ${info.count}
|
|
984
|
+
` ${type}: ${info.count} 个, ${(info.size / 1024).toFixed(2)}KB`
|
|
946
985
|
);
|
|
947
986
|
});
|
|
948
987
|
console.log();
|
|
949
988
|
if (result.issues.length > 0) {
|
|
950
|
-
console.log("
|
|
989
|
+
console.log("⚠️ 性能问题:");
|
|
951
990
|
result.issues.forEach((issue, index) => {
|
|
952
991
|
const icon = this.getSeverityIcon(issue.severity);
|
|
953
992
|
console.log(` ${index + 1}. ${icon} ${issue.title}`);
|
|
954
993
|
console.log(` ${issue.description}`);
|
|
955
994
|
if (issue.suggestion) {
|
|
956
|
-
console.log(`
|
|
995
|
+
console.log(` 💡 ${issue.suggestion}`);
|
|
957
996
|
}
|
|
958
997
|
});
|
|
959
998
|
console.log();
|
|
960
999
|
} else {
|
|
961
|
-
console.log("
|
|
1000
|
+
console.log("✅ 未发现明显的性能问题\n");
|
|
962
1001
|
}
|
|
963
1002
|
if (result.suggestions.length > 0) {
|
|
964
|
-
console.log("
|
|
1003
|
+
console.log("💡 优化建议:");
|
|
965
1004
|
result.suggestions.forEach((suggestion, index) => {
|
|
966
1005
|
console.log(` ${index + 1}. ${suggestion}`);
|
|
967
1006
|
});
|
|
968
1007
|
console.log();
|
|
969
1008
|
}
|
|
970
1009
|
if (result.optimizationExamples && result.optimizationExamples.length > 0) {
|
|
971
|
-
console.log("
|
|
1010
|
+
console.log("🔧 优化示例:");
|
|
972
1011
|
result.optimizationExamples.slice(0, 3).forEach((example, index) => {
|
|
973
1012
|
console.log(` ${index + 1}. ${example.title} (${example.priority})`);
|
|
974
1013
|
console.log(` ${example.description}`);
|
|
@@ -976,13 +1015,13 @@ var PerfReporter = class {
|
|
|
976
1015
|
console.log();
|
|
977
1016
|
}
|
|
978
1017
|
if (result.aiAnalysis) {
|
|
979
|
-
console.log("
|
|
1018
|
+
console.log("🤖 AI 分析:");
|
|
980
1019
|
console.log(
|
|
981
1020
|
result.aiAnalysis.split("\n").map((line) => ` ${line}`).join("\n")
|
|
982
1021
|
);
|
|
983
1022
|
console.log();
|
|
984
1023
|
}
|
|
985
|
-
console.log("
|
|
1024
|
+
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
|
|
986
1025
|
}
|
|
987
1026
|
/**
|
|
988
1027
|
* 生成 HTML 报告
|
|
@@ -994,7 +1033,7 @@ var PerfReporter = class {
|
|
|
994
1033
|
<head>
|
|
995
1034
|
<meta charset="UTF-8">
|
|
996
1035
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
997
|
-
<title
|
|
1036
|
+
<title>性能分析报告</title>
|
|
998
1037
|
<style>
|
|
999
1038
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
1000
1039
|
body {
|
|
@@ -1234,33 +1273,33 @@ var PerfReporter = class {
|
|
|
1234
1273
|
<body>
|
|
1235
1274
|
<div class="container">
|
|
1236
1275
|
<div class="header">
|
|
1237
|
-
<h1
|
|
1238
|
-
<div class="time"
|
|
1276
|
+
<h1>⚡ 性能分析报告</h1>
|
|
1277
|
+
<div class="time">生成时间: ${result.timestamp}</div>
|
|
1239
1278
|
</div>
|
|
1240
1279
|
|
|
1241
1280
|
<div class="content">
|
|
1242
|
-
<!--
|
|
1281
|
+
<!-- 总体统计 -->
|
|
1243
1282
|
<div class="section">
|
|
1244
|
-
<div class="section-title"
|
|
1283
|
+
<div class="section-title">📊 总体统计</div>
|
|
1245
1284
|
<div class="stats-grid">
|
|
1246
1285
|
<div class="stat-card">
|
|
1247
|
-
<div class="stat-label"
|
|
1286
|
+
<div class="stat-label">总大小</div>
|
|
1248
1287
|
<div class="stat-value">${(result.summary.totalSize / 1024 / 1024).toFixed(2)}MB</div>
|
|
1249
1288
|
</div>
|
|
1250
1289
|
<div class="stat-card">
|
|
1251
|
-
<div class="stat-label">Gzip
|
|
1290
|
+
<div class="stat-label">Gzip 后</div>
|
|
1252
1291
|
<div class="stat-value">${(result.summary.totalGzipSize / 1024 / 1024).toFixed(2)}MB</div>
|
|
1253
1292
|
</div>
|
|
1254
1293
|
<div class="stat-card">
|
|
1255
|
-
<div class="stat-label"
|
|
1294
|
+
<div class="stat-label">文件数量</div>
|
|
1256
1295
|
<div class="stat-value">${result.summary.fileCount}</div>
|
|
1257
1296
|
</div>
|
|
1258
1297
|
</div>
|
|
1259
1298
|
</div>
|
|
1260
1299
|
|
|
1261
|
-
<!--
|
|
1300
|
+
<!-- 最大文件 -->
|
|
1262
1301
|
<div class="section">
|
|
1263
|
-
<div class="section-title"
|
|
1302
|
+
<div class="section-title">📦 最大的文件</div>
|
|
1264
1303
|
<div class="file-list">
|
|
1265
1304
|
${result.summary.largestFiles.slice(0, 10).map(
|
|
1266
1305
|
(file) => `
|
|
@@ -1273,49 +1312,49 @@ var PerfReporter = class {
|
|
|
1273
1312
|
</div>
|
|
1274
1313
|
</div>
|
|
1275
1314
|
|
|
1276
|
-
<!--
|
|
1315
|
+
<!-- 按类型统计 -->
|
|
1277
1316
|
<div class="section">
|
|
1278
|
-
<div class="section-title"
|
|
1317
|
+
<div class="section-title">📋 按类型统计</div>
|
|
1279
1318
|
<div class="file-list">
|
|
1280
1319
|
${Object.entries(result.summary.byType).map(
|
|
1281
1320
|
([type, info]) => `
|
|
1282
1321
|
<div class="file-item">
|
|
1283
1322
|
<span class="file-name">${type}</span>
|
|
1284
|
-
<span class="file-size">${info.count}
|
|
1323
|
+
<span class="file-size">${info.count} 个, ${(info.size / 1024).toFixed(2)}KB</span>
|
|
1285
1324
|
</div>
|
|
1286
1325
|
`
|
|
1287
1326
|
).join("")}
|
|
1288
1327
|
</div>
|
|
1289
1328
|
</div>
|
|
1290
1329
|
|
|
1291
|
-
<!--
|
|
1330
|
+
<!-- 历史对比 -->
|
|
1292
1331
|
${result.comparison ? `
|
|
1293
1332
|
<div class="section">
|
|
1294
|
-
<div class="section-title"
|
|
1333
|
+
<div class="section-title">📈 历史对比</div>
|
|
1295
1334
|
<div class="stats-grid">
|
|
1296
1335
|
<div class="stat-card ${result.comparison.totalSize.trend === "increased" ? "trend-up" : "trend-down"}">
|
|
1297
|
-
<div class="stat-label"
|
|
1336
|
+
<div class="stat-label">总大小变化</div>
|
|
1298
1337
|
<div class="stat-value">${result.comparison.totalSize.diffPercent > 0 ? "+" : ""}${result.comparison.totalSize.diffPercent.toFixed(2)}%</div>
|
|
1299
1338
|
<div class="stat-label">${result.comparison.totalSize.trend === "increased" ? "+" : ""}${(result.comparison.totalSize.diff / 1024 / 1024).toFixed(
|
|
1300
1339
|
2
|
|
1301
1340
|
)}MB</div>
|
|
1302
1341
|
</div>
|
|
1303
1342
|
<div class="stat-card">
|
|
1304
|
-
<div class="stat-label"
|
|
1343
|
+
<div class="stat-label">文件数变化</div>
|
|
1305
1344
|
<div class="stat-value">${result.comparison.fileCount.diff > 0 ? "+" : ""}${result.comparison.fileCount.diff}</div>
|
|
1306
1345
|
</div>
|
|
1307
1346
|
<div class="stat-card">
|
|
1308
|
-
<div class="stat-label"
|
|
1347
|
+
<div class="stat-label">新增文件</div>
|
|
1309
1348
|
<div class="stat-value">${result.comparison.newFiles.length}</div>
|
|
1310
1349
|
</div>
|
|
1311
1350
|
<div class="stat-card">
|
|
1312
|
-
<div class="stat-label"
|
|
1351
|
+
<div class="stat-label">删除文件</div>
|
|
1313
1352
|
<div class="stat-value">${result.comparison.removedFiles.length}</div>
|
|
1314
1353
|
</div>
|
|
1315
1354
|
</div>
|
|
1316
1355
|
${result.comparison.largestFileChanges.length > 0 ? `
|
|
1317
1356
|
<div class="file-list" style="margin-top: 15px;">
|
|
1318
|
-
<div style="font-weight: bold; padding: 10px; color: #333;"
|
|
1357
|
+
<div style="font-weight: bold; padding: 10px; color: #333;">文件大小变化 Top 5:</div>
|
|
1319
1358
|
${result.comparison.largestFileChanges.slice(0, 5).map(
|
|
1320
1359
|
(change) => `
|
|
1321
1360
|
<div class="file-item">
|
|
@@ -1333,29 +1372,29 @@ var PerfReporter = class {
|
|
|
1333
1372
|
</div>
|
|
1334
1373
|
` : ""}
|
|
1335
1374
|
|
|
1336
|
-
<!--
|
|
1375
|
+
<!-- 依赖分析 -->
|
|
1337
1376
|
${result.dependencies ? `
|
|
1338
1377
|
<div class="section">
|
|
1339
|
-
<div class="section-title"
|
|
1378
|
+
<div class="section-title">📦 依赖分析</div>
|
|
1340
1379
|
<div class="stats-grid">
|
|
1341
1380
|
<div class="stat-card">
|
|
1342
|
-
<div class="stat-label"
|
|
1381
|
+
<div class="stat-label">总依赖数</div>
|
|
1343
1382
|
<div class="stat-value">${result.dependencies.total}</div>
|
|
1344
1383
|
</div>
|
|
1345
1384
|
<div class="stat-card ${result.dependencies.duplicates.length > 0 ? "trend-up" : ""}">
|
|
1346
|
-
<div class="stat-label"
|
|
1385
|
+
<div class="stat-label">重复依赖</div>
|
|
1347
1386
|
<div class="stat-value">${result.dependencies.duplicates.length}</div>
|
|
1348
1387
|
</div>
|
|
1349
1388
|
</div>
|
|
1350
1389
|
${result.dependencies.duplicates.length > 0 ? `
|
|
1351
1390
|
<div class="file-list" style="margin-top: 15px;">
|
|
1352
|
-
<div style="font-weight: bold; padding: 10px; color: #333;"
|
|
1391
|
+
<div style="font-weight: bold; padding: 10px; color: #333;">重复打包的依赖:</div>
|
|
1353
1392
|
${result.dependencies.duplicates.slice(0, 10).map(
|
|
1354
1393
|
(dep) => `
|
|
1355
1394
|
<div class="file-item">
|
|
1356
1395
|
<span class="file-name">${dep.name}</span>
|
|
1357
1396
|
<span class="file-size">
|
|
1358
|
-
${(dep.size / 1024).toFixed(2)}KB
|
|
1397
|
+
${(dep.size / 1024).toFixed(2)}KB · 被 ${dep.usedBy.length} 个文件使用
|
|
1359
1398
|
</span>
|
|
1360
1399
|
</div>
|
|
1361
1400
|
`
|
|
@@ -1364,7 +1403,7 @@ var PerfReporter = class {
|
|
|
1364
1403
|
` : ""}
|
|
1365
1404
|
${result.dependencies.largest.length > 0 ? `
|
|
1366
1405
|
<div class="file-list" style="margin-top: 15px;">
|
|
1367
|
-
<div style="font-weight: bold; padding: 10px; color: #333;"
|
|
1406
|
+
<div style="font-weight: bold; padding: 10px; color: #333;">最大的依赖:</div>
|
|
1368
1407
|
${result.dependencies.largest.slice(0, 10).map(
|
|
1369
1408
|
(dep) => `
|
|
1370
1409
|
<div class="file-item">
|
|
@@ -1380,10 +1419,10 @@ var PerfReporter = class {
|
|
|
1380
1419
|
</div>
|
|
1381
1420
|
` : ""}
|
|
1382
1421
|
|
|
1383
|
-
<!--
|
|
1422
|
+
<!-- 性能问题 -->
|
|
1384
1423
|
${result.issues.length > 0 ? `
|
|
1385
1424
|
<div class="section">
|
|
1386
|
-
<div class="section-title"
|
|
1425
|
+
<div class="section-title">⚠️ 性能问题</div>
|
|
1387
1426
|
${result.issues.map(
|
|
1388
1427
|
(issue) => `
|
|
1389
1428
|
<div class="issue ${issue.severity}">
|
|
@@ -1391,20 +1430,20 @@ var PerfReporter = class {
|
|
|
1391
1430
|
issue.severity
|
|
1392
1431
|
)} ${issue.title}</div>
|
|
1393
1432
|
<div class="issue-desc">${issue.description}</div>
|
|
1394
|
-
${issue.files ? `<div class="issue-desc"
|
|
1433
|
+
${issue.files ? `<div class="issue-desc">相关文件: ${issue.files.join(
|
|
1395
1434
|
", "
|
|
1396
1435
|
)}</div>` : ""}
|
|
1397
|
-
${issue.suggestion ? `<div class="issue-suggestion"
|
|
1436
|
+
${issue.suggestion ? `<div class="issue-suggestion">💡 ${issue.suggestion}</div>` : ""}
|
|
1398
1437
|
</div>
|
|
1399
1438
|
`
|
|
1400
1439
|
).join("")}
|
|
1401
1440
|
</div>
|
|
1402
|
-
` : '<div class="section"><div class="section-title"
|
|
1441
|
+
` : '<div class="section"><div class="section-title">✅ 未发现明显的性能问题</div></div>'}
|
|
1403
1442
|
|
|
1404
|
-
<!--
|
|
1443
|
+
<!-- 优化建议 -->
|
|
1405
1444
|
${result.suggestions.length > 0 ? `
|
|
1406
1445
|
<div class="section">
|
|
1407
|
-
<div class="section-title"
|
|
1446
|
+
<div class="section-title">💡 优化建议</div>
|
|
1408
1447
|
<div class="suggestion-list">
|
|
1409
1448
|
${result.suggestions.map(
|
|
1410
1449
|
(suggestion, index) => `
|
|
@@ -1415,21 +1454,21 @@ var PerfReporter = class {
|
|
|
1415
1454
|
</div>
|
|
1416
1455
|
` : ""}
|
|
1417
1456
|
|
|
1418
|
-
<!--
|
|
1457
|
+
<!-- 优化示例 -->
|
|
1419
1458
|
${result.optimizationExamples && result.optimizationExamples.length > 0 ? `
|
|
1420
1459
|
<div class="section">
|
|
1421
|
-
<div class="section-title"
|
|
1460
|
+
<div class="section-title">🔧 优化示例</div>
|
|
1422
1461
|
${result.optimizationExamples.map(
|
|
1423
1462
|
(example) => `
|
|
1424
1463
|
<div class="optimization-example">
|
|
1425
1464
|
<div class="example-header">
|
|
1426
1465
|
<div class="example-title">
|
|
1427
1466
|
${example.title}
|
|
1428
|
-
<span class="priority-badge priority-${example.priority}">${example.priority === "high" ? "
|
|
1467
|
+
<span class="priority-badge priority-${example.priority}">${example.priority === "high" ? "高优先级" : example.priority === "medium" ? "中优先级" : "低优先级"}</span>
|
|
1429
1468
|
</div>
|
|
1430
1469
|
<div class="example-meta">
|
|
1431
|
-
<span
|
|
1432
|
-
<span
|
|
1470
|
+
<span>💪 ${example.difficulty}</span>
|
|
1471
|
+
<span>📈 ${example.impact}</span>
|
|
1433
1472
|
</div>
|
|
1434
1473
|
</div>
|
|
1435
1474
|
<div class="example-desc">${example.description}</div>
|
|
@@ -1439,7 +1478,7 @@ var PerfReporter = class {
|
|
|
1439
1478
|
</div>
|
|
1440
1479
|
${example.relatedFiles && example.relatedFiles.length > 0 ? `
|
|
1441
1480
|
<div class="related-files">
|
|
1442
|
-
<strong
|
|
1481
|
+
<strong>相关文件:</strong> ${example.relatedFiles.join(", ")}
|
|
1443
1482
|
</div>
|
|
1444
1483
|
` : ""}
|
|
1445
1484
|
</div>
|
|
@@ -1448,10 +1487,10 @@ var PerfReporter = class {
|
|
|
1448
1487
|
</div>
|
|
1449
1488
|
` : ""}
|
|
1450
1489
|
|
|
1451
|
-
<!-- AI
|
|
1490
|
+
<!-- AI 分析 -->
|
|
1452
1491
|
${result.aiAnalysis ? `
|
|
1453
1492
|
<div class="section">
|
|
1454
|
-
<div class="section-title"
|
|
1493
|
+
<div class="section-title">🤖 AI 分析</div>
|
|
1455
1494
|
<div class="ai-analysis">${this.escapeHtml(result.aiAnalysis)}</div>
|
|
1456
1495
|
</div>
|
|
1457
1496
|
` : ""}
|
|
@@ -1465,38 +1504,38 @@ var PerfReporter = class {
|
|
|
1465
1504
|
</body>
|
|
1466
1505
|
</html>
|
|
1467
1506
|
`.trim();
|
|
1468
|
-
const reportsDir =
|
|
1469
|
-
if (!
|
|
1470
|
-
|
|
1471
|
-
}
|
|
1472
|
-
const reportPath =
|
|
1473
|
-
|
|
1474
|
-
console.log(
|
|
1475
|
-
|
|
1507
|
+
const reportsDir = import_path4.default.resolve(process.cwd(), "ai-reports");
|
|
1508
|
+
if (!import_fs4.default.existsSync(reportsDir)) {
|
|
1509
|
+
import_fs4.default.mkdirSync(reportsDir, { recursive: true });
|
|
1510
|
+
}
|
|
1511
|
+
const reportPath = import_path4.default.resolve(reportsDir, "performance-report.html");
|
|
1512
|
+
import_fs4.default.writeFileSync(reportPath, html, "utf-8");
|
|
1513
|
+
console.log(import_picocolors.default.green(`
|
|
1514
|
+
📄 性能报告已生成: ${import_picocolors.default.cyan(reportPath)}
|
|
1476
1515
|
`));
|
|
1477
1516
|
}
|
|
1478
1517
|
/**
|
|
1479
1518
|
* 生成 JSON 报告
|
|
1480
1519
|
*/
|
|
1481
1520
|
async generateJSONReport(result) {
|
|
1482
|
-
const reportsDir =
|
|
1483
|
-
if (!
|
|
1484
|
-
|
|
1521
|
+
const reportsDir = import_path4.default.resolve(process.cwd(), "ai-reports");
|
|
1522
|
+
if (!import_fs4.default.existsSync(reportsDir)) {
|
|
1523
|
+
import_fs4.default.mkdirSync(reportsDir, { recursive: true });
|
|
1485
1524
|
}
|
|
1486
|
-
const reportPath =
|
|
1487
|
-
|
|
1488
|
-
console.log(
|
|
1525
|
+
const reportPath = import_path4.default.resolve(reportsDir, "performance-report.json");
|
|
1526
|
+
import_fs4.default.writeFileSync(reportPath, JSON.stringify(result, null, 2), "utf-8");
|
|
1527
|
+
console.log(import_picocolors.default.green(`📄 JSON 报告已生成: ${import_picocolors.default.cyan(reportPath)}`));
|
|
1489
1528
|
}
|
|
1490
1529
|
/**
|
|
1491
1530
|
* 获取严重程度图标
|
|
1492
1531
|
*/
|
|
1493
1532
|
getSeverityIcon(severity) {
|
|
1494
1533
|
const icons = {
|
|
1495
|
-
high: "
|
|
1496
|
-
medium: "
|
|
1497
|
-
low: "
|
|
1534
|
+
high: "🔴",
|
|
1535
|
+
medium: "🟡",
|
|
1536
|
+
low: "🔵"
|
|
1498
1537
|
};
|
|
1499
|
-
return icons[severity] || "
|
|
1538
|
+
return icons[severity] || "⚪";
|
|
1500
1539
|
}
|
|
1501
1540
|
/**
|
|
1502
1541
|
* 转义 HTML
|
|
@@ -1541,19 +1580,19 @@ function vitePluginAIPerfAnalyzer(options = {}) {
|
|
|
1541
1580
|
enforce: "post",
|
|
1542
1581
|
configResolved(config) {
|
|
1543
1582
|
if (!enabled) return;
|
|
1544
|
-
console.log(
|
|
1545
|
-
console.log(
|
|
1546
|
-
console.log(`
|
|
1547
|
-
console.log(`
|
|
1583
|
+
console.log(import_picocolors2.default.cyan("\n⚡ AI 性能分析插件已启动..."));
|
|
1584
|
+
console.log(`📊 分析阈值:`);
|
|
1585
|
+
console.log(` 单文件: ${import_picocolors2.default.yellow(threshold.bundleSize + "KB")}`);
|
|
1586
|
+
console.log(` 总大小: ${import_picocolors2.default.yellow(threshold.totalSize + "MB")}`);
|
|
1548
1587
|
console.log(
|
|
1549
|
-
` Chunk
|
|
1588
|
+
` Chunk数: ${import_picocolors2.default.yellow((threshold.chunkCount ?? 10).toString())}`
|
|
1550
1589
|
);
|
|
1551
|
-
console.log(
|
|
1590
|
+
console.log(`🔑 API Key: ${apiKey ? "已配置" : "未配置"}
|
|
1552
1591
|
`);
|
|
1553
1592
|
},
|
|
1554
1593
|
async closeBundle() {
|
|
1555
1594
|
if (!enabled) return;
|
|
1556
|
-
console.log("\n
|
|
1595
|
+
console.log("\n⚡ 正在分析构建产物...\n");
|
|
1557
1596
|
try {
|
|
1558
1597
|
analysisResult = await analyzer.analyze();
|
|
1559
1598
|
await reporter.generate(analysisResult, output);
|
|
@@ -1561,14 +1600,14 @@ function vitePluginAIPerfAnalyzer(options = {}) {
|
|
|
1561
1600
|
reporter.printConsole(analysisResult);
|
|
1562
1601
|
}
|
|
1563
1602
|
} catch (error) {
|
|
1564
|
-
console.error("
|
|
1603
|
+
console.error("❌ 性能分析失败:", error.message);
|
|
1565
1604
|
}
|
|
1566
1605
|
}
|
|
1567
1606
|
};
|
|
1568
1607
|
}
|
|
1569
1608
|
var index_default = vitePluginAIPerfAnalyzer;
|
|
1570
|
-
|
|
1571
|
-
exports
|
|
1572
|
-
|
|
1573
|
-
|
|
1609
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1610
|
+
0 && (module.exports = {
|
|
1611
|
+
vitePluginAIPerfAnalyzer
|
|
1612
|
+
});
|
|
1574
1613
|
//# sourceMappingURL=index.js.map
|