@devflow-tools/plugin-performance 0.12.0 → 0.12.6

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 DELETED
@@ -1,1603 +0,0 @@
1
- export const performancePlugin = {
2
- meta: {
3
- name: "performance",
4
- version: "0.1.0",
5
- description: "Web 性能专家 — Core Web Vitals、Bundle 优化、加载策略、渲染性能",
6
- icon: "⚡",
7
- tags: ["performance", "web-vitals", "optimization", "lighthouse", "bundle"],
8
- },
9
- knowledge: {
10
- sources: [
11
- {
12
- name: "web-vitals-docs",
13
- urls: [
14
- "https://web.dev/vitals/",
15
- "https://web.dev/learn-core-web-vitals/",
16
- "https://developer.chrome.com/docs/lighthouse/overview/",
17
- ],
18
- pluginName: "performance",
19
- version: "2024",
20
- },
21
- ],
22
- },
23
- memory: {
24
- project: {
25
- stack: ["Lighthouse", "Webpack", "Vite", "Chrome DevTools"],
26
- conventions: [
27
- { key: "vitals-target", value: "LCP < 2.5s, FID < 100ms, CLS < 0.1, INP < 200ms", source: "manual", confidence: 0.95 },
28
- { key: "bundle-budget", value: "首屏 JS < 200KB, CSS < 50KB, 图片 < 200KB", source: "manual", confidence: 0.85 },
29
- { key: "lazy-load", value: "图片/iframe/视频使用 loading=lazy,组件使用动态 import", source: "manual", confidence: 0.9 },
30
- { key: "font-strategy", value: "使用 font-display: swap + 子集化字体,避免 FOIT", source: "manual", confidence: 0.8 },
31
- ],
32
- },
33
- global: [
34
- {
35
- id: "performance:core-web-vitals",
36
- source: "performance",
37
- title: "Core Web Vitals 指标",
38
- content: "LCP(最大内容绘制)< 2.5s 衡量加载性能。FID(首次输入延迟)< 100ms 衡量交互性,即将被 INP 替代。CLS(累积布局偏移)< 0.1 衡量视觉稳定性。INP < 200ms 衡量整体响应。",
39
- embedding: [],
40
- },
41
- {
42
- id: "performance:bundle-optimization",
43
- source: "performance",
44
- title: "Bundle 优化策略",
45
- content: "代码分割(React.lazy/dynamic import)。Tree shaking 移除未使用代码。Dead code elimination。依赖去重(webpack dedupe)。Module concatenation。使用 webpack-bundle-analyzer 可视化分析。",
46
- embedding: [],
47
- },
48
- {
49
- id: "performance:caching-strategies",
50
- source: "performance",
51
- title: "缓存策略",
52
- content: "Cache-Control 设置强缓存(immutable + max-age)。CDN 边缘缓存。Service Worker 离线缓存。API 响应缓存(stale-while-revalidate)。资源使用内容哈希实现长期缓存。",
53
- embedding: [],
54
- },
55
- {
56
- id: "performance:rendering-performance",
57
- source: "performance",
58
- title: "渲染性能优化",
59
- content: "避免 layout thrashing(批量读写 DOM)。使用 requestAnimationFrame 做动画。Web Worker 执行耗时计算。will-change 提示浏览器优化。CSS containment 隔离重排范围。避免强制同步布局。",
60
- embedding: [],
61
- },
62
- ],
63
- },
64
- workflow: {
65
- steps: [],
66
- templates: [
67
- // ── 性能审计 ──
68
- {
69
- name: "performance-audit",
70
- description: "性能审计:Lighthouse → Web Vitals → Bundle → 优化方案",
71
- version: "2.0.0",
72
- steps: [
73
- {
74
- id: "run-lighthouse",
75
- title: "运行 Lighthouse 审计",
76
- instruction: [
77
- "## 运行 Lighthouse 审计获取各项分数",
78
- "",
79
- "使用 Lighthouse 对目标 URL 进行完整审计,获取 Performance、Accessibility、Best Practices、SEO 四项得分。",
80
- "",
81
- "**执行动作**:",
82
- "1. 确认目标 URL(本地开发服务器或生产地址):",
83
- " ```bash",
84
- " # 若本地开发服务器未启动,先启动",
85
- " npm run dev -- --port 3000 &",
86
- " sleep 5",
87
- " ```",
88
- "2. 运行 Lighthouse CLI 生成完整报告:",
89
- " ```bash",
90
- " npx lighthouse http://localhost:3000 \\",
91
- " --output=json --output=html \\",
92
- " --output-path=./reports/lighthouse-report \\",
93
- " --chrome-flags='--headless' \\",
94
- " --quiet",
95
- " ```",
96
- "3. 提取关键分数:",
97
- " ```bash",
98
- " node -e \"const r = require('./reports/lighthouse-report.json'); console.log(JSON.stringify({performance: r.categories.performance.score, accessibility: r.categories.accessibility.score, bestPractices: r.categories['best-practices'].score, seo: r.categories.seo.score}, null, 2))\"",
99
- " ```",
100
- "4. 提取 Core Web Vitals 审计结果:",
101
- " ```bash",
102
- " node -e \"const r = require('./reports/lighthouse-report.json'); const audits = ['largest-contentful-paint', 'total-blocking-time', 'cumulative-layout-shift', 'speed-index', 'interactive']; audits.forEach(a => console.log(a + ':', JSON.stringify({score: r.audits[a].score, value: r.audits[a].numericValue, displayValue: r.audits[a].displayValue})))\"",
103
- " ```",
104
- "",
105
- "**产出格式**:",
106
- "```",
107
- "lighthouse_report:",
108
- " url: 'http://localhost:3000'",
109
- " scores: { performance: 0.68, accessibility: 0.85, best_practices: 0.92, seo: 0.90 }",
110
- " vitals: {",
111
- " LCP: { value_ms: 3200, score: 0.45, status: 'needs-improvement' },",
112
- " TBT: { value_ms: 380, score: 0.55, status: 'needs-improvement' },",
113
- " CLS: { value: 0.18, score: 0.40, status: 'poor' },",
114
- " SI: { value_ms: 4500, score: 0.50, status: 'needs-improvement' },",
115
- " TTI: { value_ms: 5800, score: 0.42, status: 'poor' }",
116
- " }",
117
- " report_path: './reports/lighthouse-report.html'",
118
- "```",
119
- ].join("\n"),
120
- suggestedTools: ["Bash"],
121
- output: "Lighthouse 报告(Performance/Accessibility/BestPractices/SEO 分数 + Web Vitals 指标)",
122
- },
123
- {
124
- id: "measure-vitals",
125
- title: "测量真实用户 Core Web Vitals",
126
- instruction: [
127
- "## 测量真实用户 Core Web Vitals(LCP/INP/CLS)",
128
- "",
129
- "结合 CrUX(Chrome User Experience Report)真实用户数据或 web-vitals 库的实地测量,评估实际用户体验。",
130
- "",
131
- "**执行动作**:",
132
- "1. 查询 CrUX 真实用户数据(需要页面已在 production 且被 Chrome 用户使用):",
133
- " ```bash",
134
- " curl -s 'https://chromeuxreport.googleapis.com/v1/records:queryRecord?key=$GOOGLE_API_KEY' \\",
135
- " -H 'Content-Type: application/json' \\",
136
- " --data '{\"origin\": \"https://example.com\"}'",
137
- " ```",
138
- "2. 若无 CrUX 数据,使用 web-vitals 库在页面加载时实地测量:",
139
- " ```bash",
140
- " cat > /tmp/measure-vitals.js << 'EOF'",
141
- " const { WebVitalMetric } = require('web-vitals');",
142
- " // 或直接在浏览器 console 中注入 web-vitals 脚本测量",
143
- " EOF",
144
- " ```",
145
- "3. 评估各指标是否达标:",
146
- " - **LCP**(最大内容绘制):< 2.5s = Good, 2.5-4.0s = Needs Improvement, > 4.0s = Poor",
147
- " - **INP**(交互到下一帧绘制):< 200ms = Good, 200-500ms = Needs Improvement, > 500ms = Poor",
148
- " - **CLS**(累积布局偏移):< 0.1 = Good, 0.1-0.25 = Needs Improvement, > 0.25 = Poor",
149
- "4. 与 Lighthouse 实验室数据(`run-lighthouse`)对比,判断「实验室 vs 实地」的差异",
150
- "",
151
- "**产出格式**:",
152
- "```",
153
- "web_vitals_real_user:",
154
- " origin: 'https://example.com'",
155
- " LCP: { p75_ms: 2800, status: 'needs-improvement', target: '< 2500ms' }",
156
- " INP: { p75_ms: 180, status: 'good', target: '< 200ms' }",
157
- " CLS: { p75_value: 0.15, status: 'needs-improvement', target: '< 0.1' }",
158
- " data_source: 'crux | field-measurement'",
159
- " lab_vs_field_gap: 'LCP lab=3200ms field=2800ms (lab worse due to throttled CPU)'",
160
- "```",
161
- ].join("\n"),
162
- suggestedTools: ["Bash", "Read"],
163
- output: "真实用户 Core Web Vitals(LCP/INP/CLS p75 值)及与实验室数据的对比",
164
- depends: ["run-lighthouse"],
165
- },
166
- {
167
- id: "analyze-bundle",
168
- title: "分析打包产物",
169
- instruction: [
170
- "## 分析打包产物(大小、重复模块、未使用代码)",
171
- "",
172
- "对构建产物进行完整分析,找出体积大、重复引入、未 tree-shake 的模块。",
173
- "",
174
- "**执行动作**:",
175
- "1. 运行生产构建并定位产物目录:",
176
- " ```bash",
177
- " npm run build",
178
- " find dist/ build/ .next/ -type f \\( -name '*.js' -o -name '*.css' \\) | head -50",
179
- " ```",
180
- "2. 使用 webpack-bundle-analyzer 生成静态报告(若项目使用 webpack):",
181
- " ```bash",
182
- " npx webpack-bundle-analyzer stats.json dist/ --mode=static --report=reports/bundle-report.html",
183
- " # 若项目使用 source-map-explorer",
184
- " npx source-map-explorer 'dist/**/*.js' --html reports/source-map-report.html",
185
- " ```",
186
- "3. 统计 bundle 总体积及按类型分类:",
187
- " ```bash",
188
- " find dist/ -type f \\( -name '*.js' -o -name '*.css' \\) -exec du -h {} + | sort -rh | head -20",
189
- " echo '--- by type ---'",
190
- " find dist/ -name '*.js' -exec cat {} + | wc -c | awk '{printf \"JS total: %.2f KB\\n\", $1/1024}'",
191
- " find dist/ -name '*.css' -exec cat {} + | wc -c | awk '{printf \"CSS total: %.2f KB\\n\", $1/1024}'",
192
- " ```",
193
- "4. 识别最大的 chunk 和模块:",
194
- " ```bash",
195
- " find dist/ -name '*.js' -exec du -h {} + | sort -rh | head -10",
196
- " ```",
197
- "5. 检查重复依赖(同一包被打包多次):",
198
- " ```bash",
199
- " npm ls --all --parseable 2>/dev/null | awk -F'node_modules/' '{print $NF}' | sort | uniq -d",
200
- " ```",
201
- "6. 检查是否有未 tree-shake 的库(如 moment.js, lodash, rxjs 全量导入):",
202
- " ```bash",
203
- " grep -rnE \"import (moment|lodash|rxjs) from ['\\\"]\" src/ --include='*.ts' --include='*.tsx' --include='*.js'",
204
- " ```",
205
- "7. 测试 gzip 和 brotli 后的传输大小:",
206
- " ```bash",
207
- " for f in $(find dist/ -name '*.js' | head -10); do",
208
- " printf \"%s raw=%s gzip=%s brotli=%s\\n\" \"$f\" \\",
209
- " \"$(wc -c < \"$f\" | awk '{printf \"%.1fKB\",$1/1024}')\" \\",
210
- " \"$(gzip -c \"$f\" | wc -c | awk '{printf \"%.1fKB\",$1/1024}')\" \\",
211
- " \"$(brotli -c \"$f\" | wc -c | awk '{printf \"%.1fKB\",$1/1024}')\"",
212
- " done",
213
- " ```",
214
- "",
215
- "**产出格式**:",
216
- "```",
217
- "bundle_analysis:",
218
- " total: { js: '450KB', css: '80KB', js_gzip: '142KB', css_gzip: '18KB' }",
219
- " top_chunks: [",
220
- " { file: 'dist/main.abc123.js', size: '180KB', gzip: '56KB' },",
221
- " { file: 'dist/vendor.def456.js', size: '120KB', gzip: '38KB' }",
222
- " ]",
223
- " duplicate_modules: ['lodash', 'moment/locale/*']",
224
- " not_tree_shaken: ['moment (full)', 'lodash (full, not lodash-es)']",
225
- " budget_status: { js_200KB_limit: 'FAIL (142KB gzip > 50KB css, but js ok)' }",
226
- "```",
227
- ].join("\n"),
228
- suggestedTools: ["Bash", "Read"],
229
- output: "Bundle 分析报告(总体积、Top chunks、重复模块、未 tree-shake 库、传输大小)",
230
- },
231
- {
232
- id: "find-bottleneck",
233
- title: "定位性能瓶颈",
234
- instruction: [
235
- "## 定位性能瓶颈(最大资源、阻塞渲染、长任务)",
236
- "",
237
- "综合 `run-lighthouse` 和 `analyze-bundle` 的结果,定位最影响性能的关键瓶颈。",
238
- "",
239
- "**执行动作**:",
240
- "1. 从 Lighthouse 报告提取「诊断审计」中得分最低的项:",
241
- " ```bash",
242
- " node -e \"",
243
- " const r = require('./reports/lighthouse-report.json');",
244
- " const failed = Object.entries(r.audits)",
245
- " .filter(([_, a]) => a.score !== null && a.score < 0.5 && a.details)",
246
- " .map(([k, a]) => ({ id: k, title: a.title, score: a.score, displayValue: a.displayValue }));",
247
- " console.log(JSON.stringify(failed.sort((x, y) => x.score - y.score).slice(0, 10), null, 2));",
248
- " \"",
249
- " ```",
250
- "2. 检查关键渲染路径阻塞项(render-blocking resources):",
251
- " ```bash",
252
- " node -e \"const r = require('./reports/lighthouse-report.json'); console.log(JSON.stringify(r.audits['render-blocking-resources']?.details?.items ?? [], null, 2))\"",
253
- " ```",
254
- "3. 检查主线程长任务(Long Tasks > 50ms):",
255
- " ```bash",
256
- " node -e \"const r = require('./reports/lighthouse-report.json'); console.log(JSON.stringify(r.audits['mainthread-work-breakdown']?.details?.items ?? [], null, 2))\"",
257
- " ```",
258
- "4. 检查最大资源(LCP 元素相关资源):",
259
- " ```bash",
260
- " node -e \"const r = require('./reports/lighthouse-report.json'); console.log('LCP element:', JSON.stringify(r.audits['largest-contentful-paint-element']?.details?.items, null, 2)); console.log('Largest resources:', JSON.stringify(r.audits['resource-summary']?.details?.items, null, 2))\"",
261
- " ```",
262
- "5. 检查图片优化机会(未用 WebP/AVIF、过大):",
263
- " ```bash",
264
- " node -e \"const r = require('./reports/lighthouse-report.json'); console.log('Modern image formats:', JSON.stringify(r.audits['modern-image-formats']?.details?.items ?? [], null, 2)); console.log('Offscreen images:', JSON.stringify(r.audits['offscreen-images']?.details?.items ?? [], null, 2))\"",
265
- " ```",
266
- "6. 检查缓存策略缺失:",
267
- " ```bash",
268
- " node -e \"const r = require('./reports/lighthouse-report.json'); console.log('Cache policy:', JSON.stringify(r.audits['uses-long-cache-ttl']?.details?.items ?? [], null, 2))\"",
269
- " ```",
270
- "7. 按「潜在节省毫秒数」对所有瓶颈排序,列出 Top 5 优化目标",
271
- "",
272
- "**产出格式**:",
273
- "```",
274
- "bottlenecks:",
275
- " critical_render_blocking: [",
276
- " { url: '/css/main.css', wasted_ms: 680 }",
277
- " ]",
278
- " long_tasks: [",
279
- " { group: 'Script Evaluation', duration_ms: 320 }",
280
- " ]",
281
- " largest_resources: [",
282
- " { url: '/images/hero.jpg', size_kb: 850, resource_type: 'image' }",
283
- " ]",
284
- " image_optimization_opportunity: {",
285
- " modern_formats_savings_kb: 420,",
286
- " offscreen_savings_kb: 180",
287
- " }",
288
- " cache_misses: [",
289
- " { url: '/fonts/inter.woff2', cache_lifetime_ms: 0 }",
290
- " ]",
291
- " top_5_priorities: [",
292
- " { issue: 'Serve modern image formats', potential_savings_ms: 1200 },",
293
- " { issue: 'Eliminate render-blocking CSS', potential_savings_ms: 680 },",
294
- " { issue: 'Reduce main-thread work', potential_savings_ms: 500 },",
295
- " { issue: 'Defer offscreen images', potential_savings_ms: 300 },",
296
- " { issue: 'Enable static asset caching', potential_savings_ms: 200 }",
297
- " ]",
298
- "```",
299
- ].join("\n"),
300
- suggestedTools: ["Bash", "Read"],
301
- output: "按影响力排序的性能瓶颈清单(渲染阻塞、长任务、大资源、图片、缓存)",
302
- depends: ["run-lighthouse", "analyze-bundle"],
303
- },
304
- { id: "generate-report", stepTemplate: "generate-report", depends: ["measure-vitals", "find-bottleneck"] },
305
- {
306
- id: "generate-optimizations",
307
- title: "生成优化代码",
308
- instruction: [
309
- "## 生成优化代码(代码分割 / 懒加载 / 缓存策略 / 图片优化)",
310
- "",
311
- "根据 `generate-report` 列出的优化项,生成具体的代码和配置修改。",
312
- "",
313
- "**执行动作**:",
314
- "1. **代码分割**(路由级 + 组件级):",
315
- " ```tsx",
316
- " // React: 路由级 lazy loading",
317
- " const Dashboard = lazy(() => import('./pages/Dashboard'));",
318
- " const Settings = lazy(() => import('./pages/Settings'));",
319
- "",
320
- " // 组件级 lazy(大型 Modal/Drawer 仅在打开时加载)",
321
- " const HeavyModal = lazy(() => import('./components/HeavyModal'));",
322
- " ```",
323
- "2. **资源懒加载**:",
324
- " ```html",
325
- " <!-- 图片:WebP + srcset + lazy -->",
326
- " <img src=\"/hero.webp\"",
327
- " srcset=\"/hero-400.webp 400w, /hero-800.webp 800w, /hero-1200.webp 1200w\"",
328
- " sizes=\"(max-width: 600px) 400px, (max-width: 1000px) 800px, 1200px\"",
329
- " loading=\"lazy\" decoding=\"async\" alt=\"Hero\" />",
330
- " <!-- iframe / 视频 -->",
331
- " <iframe src=\"...\" loading=\"lazy\"></iframe>",
332
- " ```",
333
- "3. **关键资源 preload / preconnect**:",
334
- " ```html",
335
- " <!-- 关键 CSS 预加载 -->",
336
- " <link rel=\"preload\" href=\"/css/critical.css\" as=\"style\" />",
337
- " <!-- 关键字体预加载 -->",
338
- " <link rel=\"preload\" href=\"/fonts/inter.woff2\" as=\"font\" type=\"font/woff2\" crossorigin />",
339
- " <!-- 第三方域名预连接 -->",
340
- " <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />",
341
- " <link rel=\"dns-prefetch\" href=\"https://analytics.example.com\" />",
342
- " ```",
343
- "4. **缓存策略配置**(静态资源 + HTML):",
344
- " ```nginx",
345
- " # 静态资源(带 content hash):强缓存 1 年",
346
- " location ~* \\.(js|css|png|jpg|webp|woff2)$ {",
347
- " add_header Cache-Control \"public, max-age=31536000, immutable\";",
348
- " }",
349
- " # HTML:短期缓存 + stale-while-revalidate",
350
- " location / {",
351
- " add_header Cache-Control \"public, max-age=3600, stale-while-revalidate=86400\";",
352
- " }",
353
- " ```",
354
- "5. **Tree shaking 清理**:",
355
- " ```json",
356
- " // package.json 声明 sideEffects",
357
- " { \"sideEffects\": [\"*.css\", \"./src/polyfills.ts\"] }",
358
- " ```",
359
- " ```ts",
360
- " // 用具名导入替换全量导入",
361
- " // ❌ import _ from 'lodash';",
362
- " // ✅ import { debounce } from 'lodash-es';",
363
- " // ❌ import moment from 'moment';",
364
- " // ✅ import dayjs from 'dayjs'; (体积从 300KB → 2KB)",
365
- " ```",
366
- "6. 列出所有修改并评估每项优化的预期收益",
367
- "",
368
- "**产出格式**:",
369
- "```",
370
- "optimizations_generated: [",
371
- " { type: 'code-splitting', target: 'routes/Dashboard', expected_savings_kb: 85 },",
372
- " { type: 'image-modern-format', target: 'hero.jpg', expected_savings_kb: 420 },",
373
- " { type: 'lazy-load', target: 'offscreen images', expected_savings_kb: 180 },",
374
- " { type: 'preload-critical', target: 'critical CSS + font', expected_savings_ms: 680 },",
375
- " { type: 'cache-strategy', target: 'static assets', expected_improvement: 'repeat-visit TTI -40%' },",
376
- " { type: 'tree-shaking', target: 'lodash -> lodash-es', expected_savings_kb: 95 }",
377
- "]",
378
- "files_modified: ['src/App.tsx', 'src/pages/*', 'public/index.html', 'nginx.conf', 'package.json']",
379
- "```",
380
- ].join("\n"),
381
- suggestedTools: ["Read", "Bash", "Write"],
382
- output: "生成的优化代码清单(代码分割、懒加载、preload、缓存、tree shaking)",
383
- depends: ["generate-report"],
384
- },
385
- {
386
- id: "re-measure",
387
- title: "重新测量优化效果",
388
- instruction: [
389
- "## 重新运行 Lighthouse 确认优化效果",
390
- "",
391
- "在 `generate-optimizations` 应用后,重新运行 Lighthouse 并对比前后数据。",
392
- "",
393
- "**执行动作**:",
394
- "1. 重新构建项目:",
395
- " ```bash",
396
- " npm run build",
397
- " ```",
398
- "2. 重启本地服务器(若需要)并运行 Lighthouse:",
399
- " ```bash",
400
- " npx lighthouse http://localhost:3000 \\",
401
- " --output=json --output-path=./reports/lighthouse-after.json \\",
402
- " --chrome-flags='--headless' --quiet",
403
- " ```",
404
- "3. 提取关键指标:",
405
- " ```bash",
406
- " node -e \"",
407
- " const before = require('./reports/lighthouse-report.json');",
408
- " const after = require('./reports/lighthouse-after.json');",
409
- " const diff = (key) => ({",
410
- " before: before.categories[key].score,",
411
- " after: after.categories[key].score,",
412
- " delta: +(after.categories[key].score - before.categories[key].score).toFixed(2)",
413
- " });",
414
- " console.log(JSON.stringify({",
415
- " performance: diff('performance'),",
416
- " accessibility: diff('accessibility'),",
417
- " bestPractices: diff('best-practices'),",
418
- " seo: diff('seo')",
419
- " }, null, 2));",
420
- " \"",
421
- " ```",
422
- "4. 对比 Web Vitals 变化:",
423
- " ```bash",
424
- " node -e \"",
425
- " const before = require('./reports/lighthouse-report.json');",
426
- " const after = require('./reports/lighthouse-after.json');",
427
- " const vitals = ['largest-contentful-paint', 'total-blocking-time', 'cumulative-layout-shift', 'speed-index'];",
428
- " vitals.forEach(v => console.log(v, ':', before.audits[v].numericValue, '->', after.audits[v].numericValue));",
429
- " \"",
430
- " ```",
431
- "5. 验证目标是否达成:",
432
- " - LCP < 2.5s ✅/❌",
433
- " - INP < 200ms ✅/❌",
434
- " - CLS < 0.1 ✅/❌",
435
- " - Performance score ≥ 90 ✅/❌",
436
- "6. 若某些指标未改善或反而下降,分析原因并记录",
437
- "",
438
- "**产出格式**:",
439
- "```",
440
- "measurement_comparison:",
441
- " performance_score: { before: 0.68, after: 0.92, delta: +0.24 }",
442
- " LCP_ms: { before: 3200, after: 1800, delta: -1400, target_met: true }",
443
- " TBT_ms: { before: 380, after: 120, delta: -260, target_met: true }",
444
- " CLS: { before: 0.18, after: 0.05, delta: -0.13, target_met: true }",
445
- " SI_ms: { before: 4500, after: 2200, delta: -2300 }",
446
- " bundle_size_kb: { before: 142, after: 88, delta: -54 }",
447
- " all_targets_met: true",
448
- "```",
449
- ].join("\n"),
450
- suggestedTools: ["Bash"],
451
- output: "优化后 Lighthouse 重新测量结果及前后对比",
452
- depends: ["generate-optimizations"],
453
- },
454
- {
455
- id: "compare-report",
456
- title: "生成优化前后对比报告",
457
- instruction: [
458
- "## 生成优化前后对比报告",
459
- "",
460
- "汇总所有数据,生成一份清晰的优化前后对比报告。",
461
- "",
462
- "**执行动作**:",
463
- "1. 汇总 `re-measure` 中收集的对比数据",
464
- "2. 生成 Markdown 报告:",
465
- " ```bash",
466
- " cat > reports/performance-comparison.md << 'EOF'",
467
- " # 性能优化对比报告",
468
- " ",
469
- " ## Lighthouse 分数对比",
470
- " | 指标 | 优化前 | 优化后 | 提升 |",
471
- " |---|---|---|---|",
472
- " | Performance | 68 | 92 | +24 |",
473
- " | Accessibility | 85 | 85 | — |",
474
- " | Best Practices | 92 | 95 | +3 |",
475
- " | SEO | 90 | 90 | — |",
476
- " ",
477
- " ## Core Web Vitals 对比",
478
- " | 指标 | 目标 | 优化前 | 优化后 | 状态 |",
479
- " |---|---|---|---|---|",
480
- " | LCP | < 2.5s | 3.2s | 1.8s | ✅ |",
481
- " | INP | < 200ms | 280ms | 120ms | ✅ |",
482
- " | CLS | < 0.1 | 0.18 | 0.05 | ✅ |",
483
- " ",
484
- " ## Bundle 体积对比",
485
- " | 类型 | 优化前 (gzip) | 优化后 (gzip) | 节省 |",
486
- " |---|---|---|---|",
487
- " | JS | 142KB | 88KB | -38% |",
488
- " | CSS | 18KB | 14KB | -22% |",
489
- " ",
490
- " ## 优化措施",
491
- " 1. 路由级代码分割",
492
- " 2. 图片 WebP 转换 + srcset",
493
- " 3. 关键资源 preload",
494
- " 4. 静态资源强缓存",
495
- " 5. lodash → lodash-es tree-shaking",
496
- " ",
497
- " ## 结论",
498
- " Performance 分数从 68 提升至 92,全部 Core Web Vitals 达标。",
499
- " EOF",
500
- " ```",
501
- "3. 输出报告路径及关键结论",
502
- "",
503
- "**产出格式**:",
504
- "```",
505
- "comparison_report:",
506
- " report_path: 'reports/performance-comparison.md'",
507
- " summary: {",
508
- " performance_score: '68 → 92 (+24)',",
509
- " lcp: '3.2s → 1.8s ✅',",
510
- " inp: '280ms → 120ms ✅',",
511
- " cls: '0.18 → 0.05 ✅',",
512
- " bundle_size: '142KB → 88KB gzip (-38%)'",
513
- " }",
514
- " all_targets_met: true",
515
- " next_steps: ['Monitor CrUX data over next 28 days', 'Set up Lighthouse CI in PR checks']",
516
- "```",
517
- ].join("\n"),
518
- suggestedTools: ["Read", "Write"],
519
- output: "优化前后对比报告(Lighthouse 分数、Web Vitals、Bundle 体积、结论)",
520
- depends: ["re-measure"],
521
- },
522
- ],
523
- triggers: [{ type: "cli", command: "audit:performance" }, { type: "mcp", tool: "audit_performance" }],
524
- },
525
- // ── Bundle 优化 ──
526
- {
527
- name: "bundle-optimize",
528
- description: "Bundle 优化:分析 → 代码分割 → Tree Shaking → 验证",
529
- version: "2.0.0",
530
- steps: [
531
- {
532
- id: "analyze-chunks",
533
- title: "分析当前构建产物 chunk 划分",
534
- instruction: [
535
- "## 分析当前构建产物(chunk 划分、重复依赖)",
536
- "",
537
- "深入分析 webpack/vite/rollup 的构建产物,理解每个 chunk 的内容和依赖关系。",
538
- "",
539
- "**执行动作**:",
540
- "1. 运行生产构建并确保生成 stats 文件:",
541
- " ```bash",
542
- " # webpack 项目",
543
- " npx webpack --profile --json > stats.json",
544
- " # 或 vite 项目",
545
- " npx vite build --mode production",
546
- " ```",
547
- "2. 分析每个 chunk 的大小和内容:",
548
- " ```bash",
549
- " find dist/ -name '*.js' -exec du -h {} + | sort -rh",
550
- " # 使用 webpack-bundle-analyzer 可视化",
551
- " npx webpack-bundle-analyzer stats.json dist/ --mode=static --report=reports/bundle-chunks.html",
552
- " ```",
553
- "3. 识别重复打包的依赖(同一包出现在多个 chunk):",
554
- " ```bash",
555
- " node -e \"",
556
- " const stats = require('./stats.json');",
557
- " const dupes = new Map();",
558
- " stats.chunks.forEach(c => c.modules.forEach(m => {",
559
- " if (m.name.includes('node_modules')) {",
560
- " const pkg = m.name.match(/node_modules\\/([^/]+)/)?.[1];",
561
- " if (pkg) {",
562
- " if (!dupes.has(pkg)) dupes.set(pkg, []);",
563
- " dupes.get(pkg).push(c.id);",
564
- " }",
565
- " }",
566
- " }));",
567
- " [...dupes.entries()].filter(([_, chunks]) => chunks.length > 1).forEach(([p, c]) => console.log(p, 'in chunks:', c));",
568
- " \"",
569
- " ```",
570
- "4. 检查 splitChunks 当前配置:",
571
- " ```bash",
572
- " grep -A 30 'splitChunks' webpack.config.* 2>/dev/null || echo 'No custom splitChunks config'",
573
- " ```",
574
- "5. 记录:入口 chunk 大小、异步 chunk 列表、vendor chunk 划分情况",
575
- "",
576
- "**产出格式**:",
577
- "```",
578
- "chunk_analysis:",
579
- " entry_chunks: [{ name: 'main.js', size: '180KB', modules: 450 }]",
580
- " async_chunks: [",
581
- " { name: 'vendors-react.js', size: '42KB', content: 'react + react-dom' },",
582
- " { name: 'vendors-lodash.js', size: '24KB', content: 'lodash (full)' }",
583
- " ]",
584
- " duplicated_deps: [",
585
- " { package: 'lodash', in_chunks: ['main', 'page-dashboard'], total_size: '48KB' }",
586
- " ]",
587
- " current_split_chunks_config: 'default (no customization)'",
588
- "```",
589
- ].join("\n"),
590
- suggestedTools: ["Bash", "Read"],
591
- output: "当前构建产物的 chunk 清单、重复依赖、splitChunks 配置",
592
- },
593
- {
594
- id: "find-large-deps",
595
- title: "识别大体积依赖",
596
- instruction: [
597
- "## 识别大体积依赖和重复引入的模块",
598
- "",
599
- "扫描 node_modules 找出体积大、被多次引入、或可替换为轻量替代的依赖。",
600
- "",
601
- "**执行动作**:",
602
- "1. 列出 node_modules 中按大小排序的依赖:",
603
- " ```bash",
604
- " du -sh node_modules/* 2>/dev/null | sort -rh | head -20",
605
- " ```",
606
- "2. 使用 `npm ls` 查看依赖树,识别重复安装(多版本)的包:",
607
- " ```bash",
608
- " npm ls --all 2>/dev/null | grep -E 'deduped|INVALID' | head -20",
609
- " # 查找同一包的多版本",
610
- " find node_modules -maxdepth 3 -name 'package.json' -exec grep -l '\"name\"' {} + | xargs -I {} sh -c 'grep -H \"\\\"name\\\":\" {} | grep -v node_modules/[^/]*/node_modules' | sort",
611
- " ```",
612
- "3. 识别可替换为轻量替代的大依赖(参考 bundlephobia):",
613
- " | 当前依赖 | 大小 (gzip) | 轻量替代 | 替代大小 |",
614
- " |---|---|---|---|",
615
- " | moment | 233KB | dayjs | 2KB |",
616
- " | lodash | 98KB | lodash-es (tree-shakeable) | ~20KB used |",
617
- " | rxjs | 210KB | 直接使用 Promise | 0KB |",
618
- " | axios | 13KB | fetch (native) | 0KB |",
619
- " ```bash",
620
- " grep -rE '\"(moment|lodash|rxjs|axios|ramda)\"' package.json",
621
- " ```",
622
- "4. 检查源码中的全量导入 vs 按需导入:",
623
- " ```bash",
624
- " # 全量导入(坏)",
625
- " grep -rnE \"import (moment|lodash|_) from\" src/ --include='*.ts' --include='*.tsx' --include='*.js'",
626
- " # 按需导入(好)",
627
- " grep -rnE \"import \\{ .+ \\} from '(lodash-es|dayjs)'\" src/",
628
- " ```",
629
- "5. 评估每个大依赖的「必要性 vs 体积成本」,决定是否替换/移除",
630
- "",
631
- "**产出格式**:",
632
- "```",
633
- "large_dependencies:",
634
- " replaceable: [",
635
- " { package: 'moment', size_kb: 233, replacement: 'dayjs', replacement_size_kb: 2, effort: 'low' },",
636
- " { package: 'lodash', size_kb: 98, replacement: 'lodash-es + named imports', replacement_size_kb: 20, effort: 'medium' }",
637
- " ]",
638
- " duplicated: [",
639
- " { package: 'is-buffer', versions: ['2.0.0', '1.1.5'], total_size_kb: 8 }",
640
- " ]",
641
- " full_import_found: [",
642
- " { file: 'src/utils/data.ts', line: 3, import: 'import _ from \"lodash\"' }",
643
- " ]",
644
- " estimated_savings_kb: 310",
645
- "```",
646
- ].join("\n"),
647
- suggestedTools: ["Bash", "Read"],
648
- output: "大体积依赖清单、重复依赖、可替换的轻量替代、全量导入位置",
649
- depends: ["analyze-chunks"],
650
- },
651
- {
652
- id: "suggest-split",
653
- title: "建议代码分割方案",
654
- instruction: [
655
- "## 建议代码分割方案(路由级 / 组件级 / 依赖级)",
656
- "",
657
- "根据 `analyze-chunks` 和 `find-large-deps` 的结果,提出具体的代码分割方案。",
658
- "",
659
- "**分割策略**:",
660
- "| 层级 | 策略 | 适用场景 |",
661
- "|---|---|---|",
662
- "| **路由级** | React.lazy + 动态 import 路由组件 | SPA 每个路由一个 chunk |",
663
- "| **组件级** | lazy 大型 Modal/Drawer/Dialog | 仅在用户触发时加载 |",
664
- "| **依赖级** | splitChunks.cacheGroups 拆分大依赖 | 大依赖单独成 chunk 便于缓存 |",
665
- "",
666
- "**执行动作**:",
667
- "1. 识别路由级分割候选:",
668
- " ```bash",
669
- " grep -rnE '(Route|path:)' src/ --include='*.tsx' --include='*.ts' | grep -v 'lazy' | head -20",
670
- " ```",
671
- "2. 识别组件级分割候选(大组件、Modal、Drawer、图表):",
672
- " ```bash",
673
- " find src/components -name '*.tsx' -exec wc -l {} + | sort -rn | head -10",
674
- " grep -rnE '(Modal|Drawer|Dialog|Chart|Editor)' src/ --include='*.tsx' | head -10",
675
- " ```",
676
- "3. 评估每个分割候选的收益:",
677
- " - 是否在首屏使用?不在首屏 → 优先分割",
678
- " - 体积多大?> 20KB gzip → 优先分割",
679
- " - 用户触发频率?低频率 → 分割收益高",
680
- "4. 设计 splitChunks 配置(webpack 项目)",
681
- "5. 列出按优先级排序的分割建议清单",
682
- "",
683
- "**产出格式**:",
684
- "```",
685
- "split_recommendations:",
686
- " route_level: [",
687
- " { route: '/dashboard', component: 'Dashboard.tsx', estimated_size_kb: 85, priority: 'high' },",
688
- " { route: '/settings', component: 'Settings.tsx', estimated_size_kb: 42, priority: 'medium' }",
689
- " ]",
690
- " component_level: [",
691
- " { component: 'HeavyChart.tsx', used_in: ['Dashboard'], estimated_size_kb: 120, priority: 'high' },",
692
- " { component: 'RichTextEditor.tsx', used_in: ['PostEditor'], estimated_size_kb: 95, priority: 'medium' }",
693
- " ]",
694
- " dependency_level: [",
695
- " { dep: 'lodash', strategy: 'switch to lodash-es + named imports', savings_kb: 78 },",
696
- " { dep: 'moment', strategy: 'replace with dayjs', savings_kb: 231 }",
697
- " ]",
698
- " total_estimated_initial_savings_kb: 210",
699
- "```",
700
- ].join("\n"),
701
- suggestedTools: ["Bash", "Read"],
702
- output: "按优先级排序的代码分割建议(路由级/组件级/依赖级)",
703
- depends: ["analyze-chunks", "find-large-deps"],
704
- },
705
- {
706
- id: "apply-splitting",
707
- title: "应用代码分割和动态导入",
708
- instruction: [
709
- "## 应用代码分割和动态导入",
710
- "",
711
- "根据 `suggest-split` 的方案,逐个应用代码分割。",
712
- "",
713
- "**执行动作**:",
714
- "1. **路由级分割**(React + React Router):",
715
- " ```tsx",
716
- " // 修改前",
717
- " import Dashboard from './pages/Dashboard';",
718
- " import Settings from './pages/Settings';",
719
- "",
720
- " // 修改后",
721
- " import { lazy, Suspense } from 'react';",
722
- " const Dashboard = lazy(() => import('./pages/Dashboard'));",
723
- " const Settings = lazy(() => import('./pages/Settings'));",
724
- "",
725
- " // 路由配置包裹 Suspense",
726
- " <Suspense fallback={<PageSkeleton />}>",
727
- " <Routes>",
728
- " <Route path=\"/dashboard\" element={<Dashboard />} />",
729
- " <Route path=\"/settings\" element={<Settings />} />",
730
- " </Routes>",
731
- " </Suspense>",
732
- " ```",
733
- "",
734
- "2. **组件级分割**(大型 Modal/Drawer/图表):",
735
- " ```tsx",
736
- " // 修改前",
737
- " import { HeavyChart } from './HeavyChart';",
738
- "",
739
- " // 修改后(仅在需要时加载)",
740
- " const HeavyChart = lazy(() => import('./HeavyChart').then(m => ({ default: m.HeavyChart })));",
741
- "",
742
- " // 使用",
743
- " {showChart && (",
744
- " <Suspense fallback={<ChartSkeleton />}>",
745
- " <HeavyChart data={data} />",
746
- " </Suspense>",
747
- " )}",
748
- " ```",
749
- "",
750
- "3. **webpack splitChunks 配置**:",
751
- " ```javascript",
752
- " // webpack.config.js",
753
- " optimization: {",
754
- " splitChunks: {",
755
- " chunks: 'all',",
756
- " cacheGroups: {",
757
- " vendor: {",
758
- " test: /[\\\\/]node_modules[\\\\/]/,",
759
- " name(module) {",
760
- " const pkg = module.context.match(/[\\\\/]node_modules[\\\\/](.*?)(?:[\\\\/]|$)/)?.[1];",
761
- " return `vendor/${pkg.replace('@', '')}`;",
762
- " },",
763
- " minSize: 20000,",
764
- " priority: 10,",
765
- " reuseExistingChunk: true,",
766
- " },",
767
- " common: {",
768
- " minChunks: 2,",
769
- " minSize: 10000,",
770
- " priority: 5,",
771
- " reuseExistingChunk: true,",
772
- " },",
773
- " },",
774
- " },",
775
- " }",
776
- " ```",
777
- "",
778
- "4. **Next.js 项目**(自动路由级分割,仅需组件级):",
779
- " ```tsx",
780
- " import dynamic from 'next/dynamic';",
781
- " const HeavyChart = dynamic(() => import('./HeavyChart'), {",
782
- " loading: () => <ChartSkeleton />,",
783
- " ssr: false, // 纯客户端组件关闭 SSR",
784
- " });",
785
- " ```",
786
- "",
787
- "5. 应用完成后运行构建验证:",
788
- " ```bash",
789
- " npm run build",
790
- " ```",
791
- "",
792
- "**产出格式**:",
793
- "```",
794
- "splitting_applied: [",
795
- " { type: 'route-level', component: 'Dashboard', chunk: 'pages-Dashboard-abc12.js', size_kb: 85 },",
796
- " { type: 'route-level', component: 'Settings', chunk: 'pages-Settings-def34.js', size_kb: 42 },",
797
- " { type: 'component-level', component: 'HeavyChart', chunk: 'HeavyChart-ghi56.js', size_kb: 120 },",
798
- " { type: 'dependency-level', config: 'splitChunks.cacheGroups.vendor', new_chunks: ['vendor/lodash', 'vendor/dayjs'] }",
799
- "]",
800
- "files_modified: ['src/App.tsx', 'src/pages/*', 'webpack.config.js']",
801
- "build_status: 'pass'",
802
- "```",
803
- ].join("\n"),
804
- suggestedTools: ["Read", "Bash", "Write"],
805
- output: "应用的代码分割清单(路由/组件/依赖级)、修改的文件、构建状态",
806
- depends: ["suggest-split"],
807
- },
808
- {
809
- id: "apply-tree-shaking",
810
- title: "配置 Tree Shaking 和副作用声明",
811
- instruction: [
812
- "## 配置 Tree Shaking 和副作用声明",
813
- "",
814
- "确保 webpack/rollup 能正确 tree-shake 未使用的导出,减少最终包体积。",
815
- "",
816
- "**执行动作**:",
817
- "1. **package.json 声明 sideEffects**:",
818
- " ```json",
819
- " {",
820
- " \"sideEffects\": [",
821
- " \"*.css\",",
822
- " \"*.scss\",",
823
- " \"./src/polyfills.ts\",",
824
- " \"./src/globalStyles.ts\"",
825
- " ]",
826
- " }",
827
- " ```",
828
- " - 明确列出确实有副作用的文件(CSS 导入、polyfill)",
829
- " - 其他所有文件声明为无副作用,允许打包工具移除未使用的导出",
830
- "",
831
- "2. **ESM 命名导入**(替换 default 全量导入):",
832
- " ```ts",
833
- " // ❌ 全量导入(不可 tree-shake)",
834
- " import _ from 'lodash';",
835
- " import moment from 'moment';",
836
- "",
837
- " // ✅ 命名导入(可 tree-shake)",
838
- " import { debounce, throttle } from 'lodash-es';",
839
- " import dayjs from 'dayjs';",
840
- "",
841
- " // ✅ 子路径导入(旧版 lodash 兜底)",
842
- " import debounce from 'lodash/debounce';",
843
- " ```",
844
- "",
845
- "3. **检查所有导入改为 ESM**:",
846
- " ```bash",
847
- " grep -rnE \"const .+ = require\\('\" src/ --include='*.ts' --include='*.tsx'",
848
- " # 改为 import ... from '...'",
849
- " ```",
850
- "",
851
- "4. **webpack 配置 usedExports 和 providedExports**(确认启用):",
852
- " ```javascript",
853
- " // webpack.config.js (production mode 默认启用,但应确认)",
854
- " mode: 'production',",
855
- " optimization: {",
856
- " usedExports: true,",
857
- " providedExports: true,",
858
- " sideEffects: true,",
859
- " }",
860
- " ```",
861
- "",
862
- "5. **验证 tree-shake 效果**:",
863
- " ```bash",
864
- " # 构建后检查大依赖是否被拆分(lodash 应该只剩用到的方法)",
865
- " npx source-map-explorer 'dist/**/*.js' | grep -i lodash",
866
- " ```",
867
- "",
868
- "6. 运行构建验证:",
869
- " ```bash",
870
- " npm run build",
871
- " ```",
872
- "",
873
- "**产出格式**:",
874
- "```",
875
- "tree_shaking_config:",
876
- " package_json_side_effects: ['*.css', '*.scss', './src/polyfills.ts']",
877
- " imports_migrated: [",
878
- " { file: 'src/utils/data.ts', before: \"import _ from 'lodash'\", after: \"import { debounce } from 'lodash-es'\" }",
879
- " ]",
880
- " webpack_optimization: { usedExports: true, providedExports: true, sideEffects: true }",
881
- " build_status: 'pass'",
882
- "```",
883
- ].join("\n"),
884
- suggestedTools: ["Read", "Bash", "Write"],
885
- output: "tree shaking 配置(sideEffects、ESM 导入、webpack 优化选项)",
886
- depends: ["apply-splitting"],
887
- },
888
- {
889
- id: "rebuild-measure",
890
- title: "重新构建并对比包体积",
891
- instruction: [
892
- "## 重新构建并对比包体积",
893
- "",
894
- "在 `apply-splitting` 和 `apply-tree-shaking` 后重新构建,对比优化前后的 bundle 大小。",
895
- "",
896
- "**执行动作**:",
897
- "1. 运行完整生产构建:",
898
- " ```bash",
899
- " npm run build",
900
- " ```",
901
- "2. 统计优化后总大小(按类型):",
902
- " ```bash",
903
- " echo '--- After optimization ---'",
904
- " find dist/ -name '*.js' -exec cat {} + | wc -c | awk '{printf \"JS total: %.2f KB\\n\", $1/1024}'",
905
- " find dist/ -name '*.css' -exec cat {} + | wc -c | awk '{printf \"CSS total: %.2f KB\\n\", $1/1024}'",
906
- " echo '--- gzip ---'",
907
- " find dist/ -name '*.js' -exec gzip -c {} + | wc -c | awk '{printf \"JS gzip: %.2f KB\\n\", $1/1024}'",
908
- " ```",
909
- "3. 对比 Top chunks:",
910
- " ```bash",
911
- " find dist/ -name '*.js' -exec du -h {} + | sort -rh | head -10",
912
- " ```",
913
- "4. 验证首屏 JS 是否达标(目标 < 200KB gzip):",
914
- " ```bash",
915
- " # 识别 entry chunk 并计算 gzip 大小",
916
- " for f in $(find dist/ -name 'main*.js' -o -name 'index*.js'); do",
917
- " gzip -c \"$f\" | wc -c | awk -v f=\"$f\" '{printf \"%s gzip: %.2f KB\\n\", f, $1/1024}'",
918
- " done",
919
- " ```",
920
- "5. 生成前后对比表:",
921
- " | 指标 | 优化前 | 优化后 | 提升 |",
922
- " |---|---|---|---|",
923
- " | JS 总体积 (gzip) | 142KB | 68KB | -52% |",
924
- " | CSS 总体积 (gzip) | 18KB | 14KB | -22% |",
925
- " | 首屏 JS (gzip) | 120KB | 55KB | -54% |",
926
- " | Chunk 数量 | 3 | 8 | +5 (拆分更细) |",
927
- "",
928
- "**产出格式**:",
929
- "```",
930
- "bundle_comparison:",
931
- " before: { js_raw_kb: 450, js_gzip_kb: 142, css_gzip_kb: 18, initial_js_gzip_kb: 120 }",
932
- " after: { js_raw_kb: 280, js_gzip_kb: 68, css_gzip_kb: 14, initial_js_gzip_kb: 55 }",
933
- " delta: { js_gzip: '-52%', css_gzip: '-22%', initial_js: '-54%' }",
934
- " budget_status: {",
935
- " js_200KB_limit: 'PASS (55KB < 200KB)',",
936
- " css_50KB_limit: 'PASS (14KB < 50KB)'",
937
- " }",
938
- " chunks_count: { before: 3, after: 8 }",
939
- " conclusion: 'All bundle budgets met. Initial JS reduced from 120KB to 55KB gzip.'",
940
- "```",
941
- ].join("\n"),
942
- suggestedTools: ["Bash"],
943
- output: "优化前后 bundle 大小对比(总体积、首屏 JS、chunk 数量、预算状态)",
944
- depends: ["apply-tree-shaking"],
945
- },
946
- ],
947
- triggers: [{ type: "cli", command: "optimize:bundle" }],
948
- },
949
- // ── 加载速度优化 ──
950
- {
951
- name: "load-speed-optimize",
952
- description: "加载速度优化:资源加载策略 → 预加载 → 缓存",
953
- version: "2.0.0",
954
- steps: [
955
- {
956
- id: "audit-resources",
957
- title: "审计页面加载资源",
958
- instruction: [
959
- "## 审计页面加载资源(大小、优先级、阻塞)",
960
- "",
961
- "列出页面加载时的所有资源,按大小、类型、优先级排序,找出优化机会。",
962
- "",
963
- "**执行动作**:",
964
- "1. 用 Lighthouse 审计资源使用情况:",
965
- " ```bash",
966
- " npx lighthouse http://localhost:3000 --output=json --output-path=./reports/load-audit.json --chrome-flags='--headless' --quiet",
967
- " ```",
968
- "2. 提取资源分类汇总(按类型):",
969
- " ```bash",
970
- " node -e \"const r = require('./reports/load-audit.json'); console.log(JSON.stringify(r.audits['resource-summary']?.details?.items ?? [], null, 2))\"",
971
- " ```",
972
- "3. 列出最大资源 Top 20:",
973
- " ```bash",
974
- " node -e \"",
975
- " const r = require('./reports/load-audit.json');",
976
- " const net = r.audits['network-requests']?.details?.items ?? [];",
977
- " net.sort((a, b) => (b.transferSize || 0) - (a.transferSize || 0));",
978
- " net.slice(0, 20).forEach(n => console.log(n.url, 'size:', (n.transferSize/1024).toFixed(1)+'KB', 'priority:', n.priority));",
979
- " \"",
980
- " ```",
981
- "4. 识别关键渲染路径资源(阻塞首屏):",
982
- " ```bash",
983
- " node -e \"const r = require('./reports/load-audit.json'); console.log(JSON.stringify(r.audits['render-blocking-resources']?.details?.items ?? [], null, 2))\"",
984
- " ```",
985
- "5. 检查资源压缩情况(是否启用 gzip/brotli):",
986
- " ```bash",
987
- " node -e \"const r = require('./reports/load-audit.json'); console.log(JSON.stringify(r.audits['uses-text-compression']?.details?.items ?? [], null, 2))\"",
988
- " ```",
989
- "6. 汇总各类资源的数量、总大小、潜在可节省大小",
990
- "",
991
- "**产出格式**:",
992
- "```",
993
- "resource_audit:",
994
- " by_type: {",
995
- " js: { count: 12, total_kb: 450, gzip_kb: 142 },",
996
- " css: { count: 3, total_kb: 80, gzip_kb: 18 },",
997
- " image: { count: 25, total_kb: 2800, gzip_kb: 2600 },",
998
- " font: { count: 4, total_kb: 320, gzip_kb: 180 },",
999
- " other: { count: 5, total_kb: 45, gzip_kb: 20 }",
1000
- " }",
1001
- " top_5_largest: [",
1002
- " { url: '/images/hero.jpg', size_kb: 850, priority: 'High' },",
1003
- " { url: '/fonts/inter.woff2', size_kb: 180, priority: 'High' }",
1004
- " ]",
1005
- " render_blocking: [",
1006
- " { url: '/css/main.css', wasted_ms: 680 }",
1007
- " ]",
1008
- " missing_compression: [",
1009
- " { url: '/js/vendor.js', potential_savings_kb: 95 }",
1010
- " ]",
1011
- "```",
1012
- ].join("\n"),
1013
- suggestedTools: ["Bash", "Read"],
1014
- output: "页面加载资源清单(按类型统计、Top 大资源、渲染阻塞项、未压缩资源)",
1015
- },
1016
- {
1017
- id: "check-priority",
1018
- title: "检查关键资源加载优先级",
1019
- instruction: [
1020
- "## 检查关键资源是否优先加载(preload / preconnect)",
1021
- "",
1022
- "分析首屏关键资源(LCP 图像、关键 CSS、关键字体、关键 JS)的加载优先级和时机。",
1023
- "",
1024
- "**执行动作**:",
1025
- "1. 识别 LCP 元素及其资源:",
1026
- " ```bash",
1027
- " node -e \"const r = require('./reports/load-audit.json'); console.log(JSON.stringify(r.audits['largest-contentful-paint-element']?.details?.items ?? [], null, 2))\"",
1028
- " ```",
1029
- "2. 检查关键 CSS 是否被 render-blocking:",
1030
- " ```bash",
1031
- " node -e \"const r = require('./reports/load-audit.json'); const items = r.audits['render-blocking-resources']?.details?.items ?? []; items.filter(i => i.url.endsWith('.css')).forEach(i => console.log('Render-blocking CSS:', i.url, 'wasted:', i.wastedMs + 'ms'))\"",
1032
- " ```",
1033
- "3. 检查字体加载策略(是否预加载、是否 preconnect):",
1034
- " ```bash",
1035
- " # 检查 HTML 中的 preload/preconnect",
1036
- " grep -nE '<link[^>]*(rel=\"preload\"|rel=\"preconnect\"|rel=\"dns-prefetch\")' public/index.html 2>/dev/null",
1037
- " # 检查字体文件是否被列为 render-blocking",
1038
- " node -e \"const r = require('./reports/load-audit.json'); const items = r.audits['render-blocking-resources']?.details?.items ?? []; items.filter(i => /\\.(woff2?|ttf|otf)/.test(i.url)).forEach(i => console.log('Render-blocking font:', i.url))\"",
1039
- " ```",
1040
- "4. 检查 LCP 图像是否被 preload(首屏大图应该 preload):",
1041
- " ```bash",
1042
- " # 获取 LCP 图像 URL",
1043
- " LCP_URL=$(node -e \"const r = require('./reports/load-audit.json'); const items = r.audits['largest-contentful-paint-element']?.details?.items ?? []; const lcp = items.find(i => i.items); if (lcp) console.log(lcp.items[0]?.value || lcp.items[0]?.url)\")",
1044
- " echo \"LCP image: $LCP_URL\"",
1045
- " # 检查是否已 preload",
1046
- " grep -n \"preload.*$LCP_URL\" public/index.html 2>/dev/null || echo 'NOT PRELOADED — needs <link rel=preload>'",
1047
- " ```",
1048
- "5. 列出缺失的 preload / preconnect 机会",
1049
- "",
1050
- "**产出格式**:",
1051
- "```",
1052
- "priority_audit:",
1053
- " lcp_element: { tag: 'img', url: '/images/hero.webp', size_kb: 420 }",
1054
- " lcp_preloaded: false",
1055
- " render_blocking_css: [{ url: '/css/main.css', wasted_ms: 680 }]",
1056
- " render_blocking_fonts: [{ url: '/fonts/inter.woff2', wasted_ms: 420 }]",
1057
- " missing_preloads: [",
1058
- " { url: '/images/hero.webp', type: 'image', priority: 'high' },",
1059
- " { url: '/fonts/inter.woff2', type: 'font', priority: 'high' }",
1060
- " ]",
1061
- " missing_preconnects: [",
1062
- " { origin: 'https://fonts.googleapis.com', priority: 'medium' }",
1063
- " ]",
1064
- "```",
1065
- ].join("\n"),
1066
- suggestedTools: ["Bash", "Read"],
1067
- output: "关键资源优先级审计(LCP 资源 preload 状态、render-blocking 列表、缺失的 preconnect)",
1068
- depends: ["audit-resources"],
1069
- },
1070
- {
1071
- id: "check-images",
1072
- title: "检查图片格式与懒加载",
1073
- instruction: [
1074
- "## 检查图片格式(WebP/AVIF)、尺寸(srcset)、懒加载",
1075
- "",
1076
- "审计页面所有图片,识别格式老旧、尺寸过大、未使用 srcset、未懒加载的问题。",
1077
- "",
1078
- "**执行动作**:",
1079
- "1. 从 Lighthouse 提取图片优化机会:",
1080
- " ```bash",
1081
- " # 应使用现代格式的图片",
1082
- " node -e \"const r = require('./reports/load-audit.json'); console.log('Modern formats:', JSON.stringify(r.audits['modern-image-formats']?.details?.items ?? [], null, 2))\"",
1083
- " # 应懒加载的屏外图片",
1084
- " node -e \"const r = require('./reports/load-audit.json'); console.log('Offscreen images:', JSON.stringify(r.audits['offscreen-images']?.details?.items ?? [], null, 2))\"",
1085
- " # 尺寸过大的图片",
1086
- " node -e \"const r = require('./reports/load-audit.json'); console.log('Oversized images:', JSON.stringify(r.audits['uses-responsive-images']?.details?.items ?? [], null, 2))\"",
1087
- " ```",
1088
- "2. 扫描源码中的图片用法:",
1089
- " ```bash",
1090
- " # 列出所有 <img> 标签及其属性",
1091
- " grep -rnE '<img ' src/ --include='*.tsx' --include='*.jsx' --include='*.html' | head -30",
1092
- " # 检查缺失 loading=lazy 的图片",
1093
- " grep -rnE '<img ' src/ --include='*.tsx' --include='*.jsx' --include='*.html' | grep -v 'loading=' | head -20",
1094
- " # 检查缺失 srcset 的图片",
1095
- " grep -rnE '<img ' src/ --include='*.tsx' --include='*.jsx' --include='*.html' | grep -v 'srcSet' | head -20",
1096
- " ```",
1097
- "3. 检查图片格式(找出 jpg/png 可转 WebP/AVIF):",
1098
- " ```bash",
1099
- " find public/ src/ -type f \\( -name '*.jpg' -o -name '*.jpeg' -o -name '*.png' \\) -exec du -h {} + | sort -rh | head -20",
1100
- " ```",
1101
- "4. 检查 Next.js 项目是否使用 next/image:",
1102
- " ```bash",
1103
- " grep -rnE \"import Image from 'next/image'\" src/ --include='*.tsx' --include='*.jsx' | wc -l",
1104
- " grep -rnE '<img ' src/ --include='*.tsx' --include='*.jsx' | grep -v 'next/image' | head -10",
1105
- " ```",
1106
- "5. 评估图片优化总收益(按潜在节省 KB 排序)",
1107
- "",
1108
- "**产出格式**:",
1109
- "```",
1110
- "image_audit:",
1111
- " not_modern_format: [",
1112
- " { url: '/images/hero.jpg', size_kb: 850, webp_estimate_kb: 420, avif_estimate_kb: 280 }",
1113
- " ]",
1114
- " not_lazy_loaded: [",
1115
- " { file: 'src/components/Gallery.tsx', line: 25, url: '/images/photo-3.jpg', below_fold: true }",
1116
- " ]",
1117
- " not_using_srcset: [",
1118
- " { file: 'src/components/Hero.tsx', line: 12, url: '/images/hero.jpg' }",
1119
- " ]",
1120
- " oversized: [",
1121
- " { url: '/images/thumbnail.jpg', rendered_size: '200x200', actual_size: '1200x1200' }",
1122
- " ]",
1123
- " not_using_next_image: 15",
1124
- " total_potential_savings_kb: 1250",
1125
- "```",
1126
- ].join("\n"),
1127
- suggestedTools: ["Bash", "Read"],
1128
- output: "图片审计报告(现代格式、懒加载、srcset、尺寸、next/image 使用情况)",
1129
- depends: ["audit-resources"],
1130
- },
1131
- {
1132
- id: "check-fonts",
1133
- title: "检查字体加载策略",
1134
- instruction: [
1135
- "## 检查字体加载策略(font-display / 子集化 / 预加载)",
1136
- "",
1137
- "审计字体文件的加载方式,确保字体不会阻塞渲染或导致 FOIT(Flash of Invisible Text)。",
1138
- "",
1139
- "**执行动作**:",
1140
- "1. 检查 CSS 中的 `@font-face` 声明及 `font-display` 值:",
1141
- " ```bash",
1142
- " grep -rnA 5 '@font-face' src/ public/ --include='*.css' --include='*.scss'",
1143
- " # 检查是否使用 font-display: swap(避免 FOIT)",
1144
- " grep -rnA 5 '@font-face' src/ public/ --include='*.css' | grep -E 'font-display' | grep -v 'swap'",
1145
- " ```",
1146
- "2. 检查字体文件大小(子集化后的字体应 < 100KB):",
1147
- " ```bash",
1148
- " find public/ src/ -type f \\( -name '*.woff2' -o -name '*.woff' -o -name '*.ttf' \\) -exec du -h {} + | sort -rh",
1149
- " ```",
1150
- "3. 检查字体 preload 状态:",
1151
- " ```bash",
1152
- " grep -nE 'preload.*font|preload.*woff' public/index.html 2>/dev/null || echo 'No font preload found'",
1153
- " ```",
1154
- "4. 检查字体 preconnect(对 Google Fonts 等第三方字体源):",
1155
- " ```bash",
1156
- " grep -nE 'preconnect.*fonts\\.(googleapis|gstatic)' public/index.html 2>/dev/null || echo 'No font preconnect found'",
1157
- " ```",
1158
- "5. 检查是否使用 unicode-range 做字体子集化:",
1159
- " ```bash",
1160
- " grep -rn 'unicode-range' src/ public/ --include='*.css'",
1161
- " ```",
1162
- "6. 评估字体优化机会:",
1163
- " - 缺失 `font-display: swap` → 会导致 FOIT",
1164
- " - 字体未预加载 → LCP 受影响",
1165
- " - 字体文件过大(> 200KB)→ 应子集化(只包含需要的字符)",
1166
- " - 缺少 unicode-range → 下载了不需要的字符集",
1167
- "",
1168
- "**产出格式**:",
1169
- "```",
1170
- "font_audit:",
1171
- " fonts_used: [",
1172
- " { family: 'Inter', file: '/fonts/inter.woff2', size_kb: 180, display: 'swap', preloaded: false }",
1173
- " ]",
1174
- " issues: [",
1175
- " { type: 'missing-preload', font: 'Inter', impact: 'LCP delayed by ~400ms' },",
1176
- " { type: 'oversized', font: 'Inter', size_kb: 180, recommended_kb: 50, fix: 'subset to Latin + Common' },",
1177
- " { type: 'missing-preconnect', origin: 'https://fonts.googleapis.com' }",
1178
- " ]",
1179
- " total_potential_savings_kb: 130",
1180
- "```",
1181
- ].join("\n"),
1182
- suggestedTools: ["Bash", "Read"],
1183
- output: "字体审计报告(font-display、文件大小、preload/preconnect 状态、子集化机会)",
1184
- depends: ["audit-resources"],
1185
- },
1186
- { id: "generate-fix", stepTemplate: "generate-fix", depends: ["check-priority", "check-images", "check-fonts"] },
1187
- {
1188
- id: "measure-improvement",
1189
- title: "测量优化前后 LCP 和 Speed Index",
1190
- instruction: [
1191
- "## 测量优化前后 LCP 和 Speed Index",
1192
- "",
1193
- "在 `generate-fix` 应用后重新运行 Lighthouse,对比加载速度的关键指标。",
1194
- "",
1195
- "**执行动作**:",
1196
- "1. 重新构建并运行 Lighthouse:",
1197
- " ```bash",
1198
- " npm run build",
1199
- " npx lighthouse http://localhost:3000 --output=json --output-path=./reports/load-after.json --chrome-flags='--headless' --quiet",
1200
- " ```",
1201
- "2. 提取关键加载指标:",
1202
- " ```bash",
1203
- " node -e \"",
1204
- " const before = require('./reports/load-audit.json');",
1205
- " const after = require('./reports/load-after.json');",
1206
- " const metrics = ['first-contentful-paint', 'largest-contentful-paint', 'speed-index', 'interactive', 'total-blocking-time'];",
1207
- " metrics.forEach(m => console.log(m, ':', before.audits[m].numericValue, '->', after.audits[m].numericValue));",
1208
- " \"",
1209
- " ```",
1210
- "3. 对比目标值:",
1211
- " | 指标 | 目标 | 优化前 | 优化后 | 状态 |",
1212
- " |---|---|---|---|---|",
1213
- " | LCP | < 2.5s | ? | ? | ✅/❌ |",
1214
- " | Speed Index | < 3.4s | ? | ? | ✅/❌ |",
1215
- " | TTI | < 3.8s | ? | ? | ✅/❌ |",
1216
- " | FCP | < 1.8s | ? | ? | ✅/❌ |",
1217
- "4. 对比资源加载数量与总大小:",
1218
- " ```bash",
1219
- " node -e \"",
1220
- " const before = require('./reports/load-audit.json');",
1221
- " const after = require('./reports/load-after.json');",
1222
- " const sum = (r) => (r.audits['network-requests']?.details?.items ?? []).reduce((acc, n) => acc + (n.transferSize || 0), 0);",
1223
- " console.log('Total transfer size:', (sum(before)/1024).toFixed(1), 'KB ->', (sum(after)/1024).toFixed(1), 'KB');",
1224
- " \"",
1225
- " ```",
1226
- "5. 确认所有目标达成;若有未达标项,列出待进一步优化的方向",
1227
- "",
1228
- "**产出格式**:",
1229
- "```",
1230
- "load_speed_comparison:",
1231
- " FCP_ms: { before: 2800, after: 1200, target: '< 1800', met: true }",
1232
- " LCP_ms: { before: 4200, after: 1800, target: '< 2500', met: true }",
1233
- " SI_ms: { before: 5200, after: 2100, target: '< 3400', met: true }",
1234
- " TTI_ms: { before: 6800, after: 2400, target: '< 3800', met: true }",
1235
- " total_transfer_kb: { before: 3200, after: 1800, delta: '-44%' }",
1236
- " all_targets_met: true",
1237
- "```",
1238
- ].join("\n"),
1239
- suggestedTools: ["Bash"],
1240
- output: "加载速度优化前后对比(FCP/LCP/SI/TTI、资源总传输大小、目标达成状态)",
1241
- depends: ["generate-fix"],
1242
- },
1243
- ],
1244
- triggers: [{ type: "cli", command: "optimize:load-speed" }],
1245
- },
1246
- // ── 运行时性能优化 ──
1247
- {
1248
- name: "runtime-optimize",
1249
- description: "运行时性能优化:长任务 → 渲染卡顿 → 内存泄漏",
1250
- version: "2.0.0",
1251
- steps: [
1252
- {
1253
- id: "record-trace",
1254
- title: "录制 Performance Trace",
1255
- instruction: [
1256
- "## 录制 Performance Trace 捕获运行时行为",
1257
- "",
1258
- "使用 Chrome DevTools 或 puppeteer 录制页面运行时行为,捕获长任务、布局抖动、内存使用等数据。",
1259
- "",
1260
- "**执行动作**:",
1261
- "1. 手动录制(Chrome DevTools):",
1262
- " - 打开 DevTools → Performance 面板",
1263
- " - 点击 Record(或 Ctrl+E)",
1264
- " - 执行需要分析的交互操作(滚动、点击、输入等)10-20 秒",
1265
- " - 点击 Stop 结束录制",
1266
- " - 保存 trace:点击「Save profile」按钮导出 `.json` 文件到 `reports/trace-before.json`",
1267
- "",
1268
- "2. 自动化录制(使用 puppeteer):",
1269
- " ```bash",
1270
- " cat > /tmp/record-trace.js << 'EOF'",
1271
- " const puppeteer = require('puppeteer');",
1272
- " (async () => {",
1273
- " const browser = await puppeteer.launch({ headless: 'new' });",
1274
- " const page = await browser.newPage();",
1275
- " await page.tracing.start({ path: 'reports/trace-before.json', screenshots: true });",
1276
- " await page.goto('http://localhost:3000', { waitUntil: 'networkidle0' });",
1277
- " // 执行典型交互",
1278
- " await page.evaluate(() => {",
1279
- " window.scrollTo(0, document.body.scrollHeight);",
1280
- " setTimeout(() => window.scrollTo(0, 0), 1000);",
1281
- " });",
1282
- " await new Promise(r => setTimeout(r, 3000));",
1283
- " await page.tracing.stop();",
1284
- " await browser.close();",
1285
- " console.log('Trace saved to reports/trace-before.json');",
1286
- " })();",
1287
- " EOF",
1288
- " node /tmp/record-trace.js",
1289
- " ```",
1290
- "3. 在 chrome://tracing 中打开 trace 文件查看",
1291
- "",
1292
- "**产出格式**:",
1293
- "```",
1294
- "trace_recorded:",
1295
- " file: 'reports/trace-before.json'",
1296
- " duration_ms: 15000",
1297
- " url: 'http://localhost:3000'",
1298
- " interactions_captured: ['scroll', 'click', 'input']",
1299
- " file_size_mb: 12.5",
1300
- "```",
1301
- ].join("\n"),
1302
- suggestedTools: ["Bash", "Read", "Write"],
1303
- output: "Performance trace 文件(含 10-20 秒运行时行为,包括滚动/点击/输入等交互)",
1304
- },
1305
- {
1306
- id: "find-long-tasks",
1307
- title: "识别长任务",
1308
- instruction: [
1309
- "## 识别长任务(> 50ms 阻塞主线程)",
1310
- "",
1311
- "分析 trace 文件找出所有阻塞主线程超过 50ms 的 Long Task,评估其影响。",
1312
- "",
1313
- "**执行动作**:",
1314
- "1. 从 trace 文件中提取所有主线程任务:",
1315
- " ```bash",
1316
- " node -e \"",
1317
- " const fs = require('fs');",
1318
- " const trace = JSON.parse(fs.readFileSync('reports/trace-before.json', 'utf8'));",
1319
- " const events = trace.traceEvents || trace;",
1320
- " const longTasks = events",
1321
- " .filter(e => e.name === 'RunTask' && e.dur > 50000) // 50ms = 50000 microseconds",
1322
- " .map(e => ({ start: e.ts, duration_ms: (e.dur / 1000).toFixed(1), thread: e.tid }))",
1323
- " .sort((a, b) => b.duration_ms - a.duration_ms);",
1324
- " console.log('Long tasks found:', longTasks.length);",
1325
- " longTasks.slice(0, 20).forEach(t => console.log(' ', t.duration_ms + 'ms @', t.start));",
1326
- " \"",
1327
- " ```",
1328
- "2. 识别每个长任务内部的「罪魁祸首」(哪个函数耗时最长):",
1329
- " - 在 DevTools Performance 面板 Bottom-Up 标签查看",
1330
- " - 或调用栈中 Flame Chart 里最宽的色块",
1331
- "3. 分类每个长任务的性质:",
1332
- " | 类别 | 表现 | 修复方向 |",
1333
- " |---|---|---|",
1334
- " | Script Evaluation | 大段 JS 执行 | 拆分到 Web Worker 或分块执行 |",
1335
- " | Layout / Recalculate Style | DOM 读写交错 | 批量读写,使用 requestAnimationFrame |",
1336
- " | Paint / Composite | 大量重绘 | 减少动画的 paint 范围,使用 transform |",
1337
- " | Parse HTML / Style | 大 HTML/CSS 解析 | 减小 HTML 大小,CSS 提取关键路径 |",
1338
- "4. 按持续时间排序列出 Top 10 长任务",
1339
- "",
1340
- "**产出格式**:",
1341
- "```",
1342
- "long_tasks:",
1343
- " count: 12",
1344
- " total_blocked_ms: 1850",
1345
- " top_10: [",
1346
- " { start_ms: 1200, duration_ms: 380, category: 'Script Evaluation', culprit: 'React.render / reconcile' },",
1347
- " { start_ms: 2100, duration_ms: 220, category: 'Layout', culprit: 'forced reflow in scroll handler' },",
1348
- " { start_ms: 3500, duration_ms: 180, category: 'Script Evaluation', culprit: 'JSON.parse of large dataset' }",
1349
- " ]",
1350
- " breakdown_by_category: {",
1351
- " script_evaluation_ms: 1200,",
1352
- " layout_ms: 380,",
1353
- " paint_ms: 180,",
1354
- " other_ms: 90",
1355
- " }",
1356
- "```",
1357
- ].join("\n"),
1358
- suggestedTools: ["Bash", "Read"],
1359
- output: "长任务清单(> 50ms 的主线程阻塞任务、分类、罪魁祸首函数)",
1360
- depends: ["record-trace"],
1361
- },
1362
- {
1363
- id: "find-layout-thrash",
1364
- title: "检测 layout thrashing",
1365
- instruction: [
1366
- "## 检测 layout thrashing(强制同步布局)",
1367
- "",
1368
- "找出代码中「批量读 DOM 属性 → 写入 → 又读」的强制同步布局模式。",
1369
- "",
1370
- "**执行动作**:",
1371
- "1. 在 DevTools Performance 面板的 Summary 中查看「Forced Reflow」事件数量",
1372
- "2. 在 trace 文件中找出所有 `Layout` 事件,特别关注那些由 JS 触发的:",
1373
- " ```bash",
1374
- " node -e \"",
1375
- " const fs = require('fs');",
1376
- " const trace = JSON.parse(fs.readFileSync('reports/trace-before.json', 'utf8'));",
1377
- " const events = trace.traceEvents || trace;",
1378
- " const forcedReflows = events",
1379
- " .filter(e => (e.name === 'Layout' || e.name === 'UpdateLayoutTree') && e.args?.beginData?.stackTrace)",
1380
- " .map(e => ({",
1381
- " start: e.ts,",
1382
- " duration_ms: (e.dur / 1000).toFixed(1),",
1383
- " stack: e.args.beginData.stackTrace.slice(0, 3).map(f => f.functionName || f.url + ':' + f.lineNumber).join(' <- ')",
1384
- " }));",
1385
- " console.log('Forced reflows:', forcedReflows.length);",
1386
- " forcedReflows.slice(0, 10).forEach(r => console.log(' ', r.duration_ms + 'ms', r.stack));",
1387
- " \"",
1388
- " ```",
1389
- "3. 在源码中搜索常见的 thrashing 模式:",
1390
- " ```bash",
1391
- " # 触发 layout 的读取(offsetTop/offsetHeight/getBoundingClientRect/scrollTop 等)",
1392
- " grep -rnE '\\.(offsetTop|offsetHeight|offsetWidth|clientHeight|clientWidth|scrollHeight|scrollTop|getBoundingClientRect|getComputedStyle)\\(' src/ --include='*.ts' --include='*.tsx' --include='*.js'",
1393
- " # 紧跟这些读取的写入(.style.* =、.classList.add/remove、innerHTML)",
1394
- " grep -rnE '\\.(style|classList|innerHTML)\\s*[.=]' src/ --include='*.ts' --include='*.tsx' --include='*.js'",
1395
- " ```",
1396
- "4. 识别循环中的 thrashing(最严重):",
1397
- " ```bash",
1398
- " # 查找循环内的 layout 读取 + 样式写入",
1399
- " grep -rnB 2 -A 2 '\\.(offsetHeight|offsetWidth|getBoundingClientRect)' src/ --include='*.ts' --include='*.tsx' --include='*.js' | grep -E 'for|while|forEach|map'",
1400
- " ```",
1401
- "5. 列出所有 thrashing 嫌疑代码及严重度",
1402
- "",
1403
- "**产出格式**:",
1404
- "```",
1405
- "layout_thrash_audit:",
1406
- " forced_reflows_in_trace: 8",
1407
- " hotspots: [",
1408
- " { file: 'src/components/List.tsx', line: 45, pattern: 'loop + offsetHeight read + style.height write', severity: 'high' },",
1409
- " { file: 'src/utils/scroll.ts', line: 12, pattern: 'scroll handler + getBoundingClientRect', severity: 'medium' }",
1410
- " ]",
1411
- " common_patterns_found: {",
1412
- " inside_loop: 2,",
1413
- " in_scroll_handler: 1,",
1414
- " read_write_interleaved: 3",
1415
- " }",
1416
- "```",
1417
- ].join("\n"),
1418
- suggestedTools: ["Bash", "Read"],
1419
- output: "Layout thrashing 嫌疑代码(强制同步布局位置、模式、严重度)",
1420
- depends: ["record-trace"],
1421
- },
1422
- {
1423
- id: "find-memory-leaks",
1424
- title: "检测内存泄漏",
1425
- instruction: [
1426
- "## 检测内存泄漏(heap snapshot 对比)",
1427
- "",
1428
- "通过多次堆快照对比,检测页面在操作后是否有持续增长的对象(疑似内存泄漏)。",
1429
- "",
1430
- "**执行动作**:",
1431
- "1. 使用 puppeteer 自动采集多个堆快照:",
1432
- " ```bash",
1433
- " cat > /tmp/heap-snapshot.js << 'EOF'",
1434
- " const puppeteer = require('puppeteer');",
1435
- " (async () => {",
1436
- " const browser = await puppeteer.launch({ headless: 'new' });",
1437
- " const page = await browser.newPage();",
1438
- " const client = await page.target().createCDPSession();",
1439
- " await client.send('HeapProfiler.enable');",
1440
- "",
1441
- " await page.goto('http://localhost:3000', { waitUntil: 'networkidle0' });",
1442
- "",
1443
- " // 1) 初始快照",
1444
- " const snap1 = await client.send('HeapProfiler.takeHeapSnapshot', { reportProgress: false });",
1445
- " require('fs').writeFileSync('reports/heap-1-initial.json', JSON.stringify(snap1));",
1446
- "",
1447
- " // 2) 执行典型操作(打开/关闭 Modal 10 次)",
1448
- " for (let i = 0; i < 10; i++) {",
1449
- " await page.click('[data-testid=\"open-modal\"]');",
1450
- " await page.waitForSelector('[data-testid=\"modal\"]');",
1451
- " await page.click('[data-testid=\"close-modal\"]');",
1452
- " await new Promise(r => setTimeout(r, 200));",
1453
- " }",
1454
- "",
1455
- " // 3) 强制 GC 后再拍快照",
1456
- " await client.send('HeapProfiler.collectGarbage');",
1457
- " const snap2 = await client.send('HeapProfiler.takeHeapSnapshot', { reportProgress: false });",
1458
- " require('fs').writeFileSync('reports/heap-2-after-operations.json', JSON.stringify(snap2));",
1459
- "",
1460
- " await browser.close();",
1461
- " })();",
1462
- " EOF",
1463
- " node /tmp/heap-snapshot.js",
1464
- " ```",
1465
- "2. 手动在 DevTools Memory 面板对比:",
1466
- " - 打开 DevTools → Memory → Heap snapshot",
1467
- " - 拍第一个快照(初始状态)",
1468
- " - 执行操作(打开/关闭组件多次)",
1469
- " - 手动触发 GC(🧹 按钮)",
1470
- " - 拍第二个快照",
1471
- " - 选择「Comparison」视图,对比快照 1 vs 快照 2",
1472
- " - 按 `# Delta` 或 `Size Delta` 排序",
1473
- "3. 查找可疑增长:",
1474
- " - EventListener 数量持续增长(事件监听未清理)",
1475
- " - detached DOM 节点(从 DOM 移除但 JS 仍持有引用)",
1476
- " - 闭包持有的大对象",
1477
- "4. 在源码中查找常见的泄漏模式:",
1478
- " ```bash",
1479
- " # addEventListener 无对应 removeEventListener",
1480
- " grep -rn 'addEventListener' src/ --include='*.ts' --include='*.tsx' | wc -l",
1481
- " grep -rn 'removeEventListener' src/ --include='*.ts' --include='*.tsx' | wc -l",
1482
- " # setInterval / setTimeout 无 clearInterval / clearTimeout",
1483
- " grep -rnE '(setInterval|setTimeout)' src/ --include='*.ts' --include='*.tsx' | wc -l",
1484
- " grep -rnE '(clearInterval|clearTimeout)' src/ --include='*.ts' --include='*.tsx' | wc -l",
1485
- " # 全局变量或闭包持有大引用",
1486
- " grep -rnE '(window\\.|global\\.|let .*= \\[|let .*= \\{)' src/ --include='*.ts' --include='*.tsx' | head -20",
1487
- " ```",
1488
- "",
1489
- "**产出格式**:",
1490
- "```",
1491
- "memory_leak_audit:",
1492
- " heap_growth: {",
1493
- " initial_mb: 42.5,",
1494
- " after_operations_mb: 58.2,",
1495
- " delta_mb: 15.7,",
1496
- " status: 'LEAK_SUSPECTED'",
1497
- " }",
1498
- " top_growing_constructors: [",
1499
- " { constructor: 'EventListener', delta: +240, likely_leak: true },",
1500
- " { constructor: 'HTMLDivElement', delta: +85, likely_leak: true, note: 'detached nodes' },",
1501
- " { constructor: 'Object', delta: +520, likely_leak: false }",
1502
- " ]",
1503
- " suspicious_patterns: [",
1504
- " { file: 'src/components/Modal.tsx', issue: 'addEventListener on mount, no remove on unmount' },",
1505
- " { file: 'src/hooks/useInterval.ts', issue: 'setInterval not cleared on unmount' }",
1506
- " ]",
1507
- "```",
1508
- ].join("\n"),
1509
- suggestedTools: ["Bash", "Read"],
1510
- output: "堆快照对比结果(内存增长、增长最快的构造器、可疑泄漏代码位置)",
1511
- },
1512
- { id: "generate-fix", stepTemplate: "generate-fix", depends: ["find-long-tasks", "find-layout-thrash", "find-memory-leaks"] },
1513
- {
1514
- id: "verify",
1515
- title: "重新录制确认优化效果",
1516
- instruction: [
1517
- "## 重新录制 Performance Trace 确认优化效果",
1518
- "",
1519
- "在 `generate-fix` 应用后,用相同条件重新录制 trace,对比优化前后。",
1520
- "",
1521
- "**执行动作**:",
1522
- "1. 重新构建并运行:",
1523
- " ```bash",
1524
- " npm run build",
1525
- " ```",
1526
- "2. 用相同的 puppeteer 脚本重新录制 trace:",
1527
- " ```bash",
1528
- " node /tmp/record-trace.js # 输出 reports/trace-after.json",
1529
- " ```",
1530
- "3. 提取优化后的长任务数据:",
1531
- " ```bash",
1532
- " node -e \"",
1533
- " const fs = require('fs');",
1534
- " const before = JSON.parse(fs.readFileSync('reports/trace-before.json', 'utf8'));",
1535
- " const after = JSON.parse(fs.readFileSync('reports/trace-after.json', 'utf8'));",
1536
- " const countLong = (t) => (t.traceEvents || t).filter(e => e.name === 'RunTask' && e.dur > 50000).length;",
1537
- " const sumLong = (t) => (t.traceEvents || t).filter(e => e.name === 'RunTask' && e.dur > 50000).reduce((a, e) => a + e.dur, 0) / 1000;",
1538
- " console.log('Long tasks:', countLong(before), '->', countLong(after));",
1539
- " console.log('Total blocked ms:', sumLong(before).toFixed(0), '->', sumLong(after).toFixed(0));",
1540
- " \"",
1541
- " ```",
1542
- "4. 验证各项指标:",
1543
- " - **长任务**:数量减少 ≥ 50%,无 > 200ms 的任务",
1544
- " - **Layout thrashing**:forced reflow 次数减少 ≥ 80%",
1545
- " - **内存**:10 次操作后堆增长 < 5MB(之前 > 15MB)",
1546
- " - **FPS**:交互期间 FPS ≥ 55(之前可能 < 30)",
1547
- "5. 在 DevTools Performance 面板查看 Summary 饼图对比:",
1548
- " | 指标 | 优化前 | 优化后 |",
1549
- " |---|---|---|",
1550
- " | Scripting | 45% | 22% |",
1551
- " | Rendering | 18% | 8% |",
1552
- " | Painting | 12% | 5% |",
1553
- " | Idle | 15% | 55% |",
1554
- "6. 功能回归测试:确认所有交互(点击、滚动、输入)仍正常工作",
1555
- "",
1556
- "**产出格式**:",
1557
- "```",
1558
- "runtime_optimization_comparison:",
1559
- " long_tasks: { count: { before: 12, after: 4 }, blocked_ms: { before: 1850, after: 280 } }",
1560
- " forced_reflows: { before: 8, after: 1 }",
1561
- " heap_growth_after_operations_mb: { before: 15.7, after: 2.1 }",
1562
- " avg_fps_during_interaction: { before: 32, after: 58 }",
1563
- " summary_breakdown: {",
1564
- " scripting: '45% -> 22%',",
1565
- " rendering: '18% -> 8%',",
1566
- " idle: '15% -> 55%'",
1567
- " }",
1568
- " regression_check: 'all interactions still work'",
1569
- " all_targets_met: true",
1570
- "```",
1571
- ].join("\n"),
1572
- suggestedTools: ["Bash", "Read"],
1573
- output: "运行时优化前后对比(长任务数量/时长、forced reflow、堆增长、FPS、Summary 饼图)",
1574
- depends: ["generate-fix"],
1575
- },
1576
- ],
1577
- triggers: [{ type: "cli", command: "optimize:runtime" }],
1578
- },
1579
- ],
1580
- },
1581
- prompt: {
1582
- system: "你是一个 Web 性能专家。精通 Core Web Vitals、Bundle 优化和运行时性能。所有优化建议必须有数据依据。",
1583
- rules: [
1584
- "Core Web Vitals 目标:LCP < 2.5s, INP < 200ms, CLS < 0.1",
1585
- "首屏 JS < 200KB, CSS < 50KB",
1586
- "图片使用 WebP/AVIF 格式 + srcset + loading=lazy",
1587
- "字体使用 font-display: swap + 子集化",
1588
- "关键资源使用 preload/preconnect 优先加载",
1589
- "使用代码分割和动态导入按需加载",
1590
- "所有优化建议必须有 Lighthouse/Performance 数据支持",
1591
- "不使用 setTimeout 做动画,使用 requestAnimationFrame",
1592
- ],
1593
- },
1594
- tools: [
1595
- {
1596
- name: "performance_audit_performance",
1597
- description: "性能审计:Lighthouse → Web Vitals → Bundle → 优化方案",
1598
- inputSchema: { type: "object", properties: { projectRoot: { type: "string" } }, required: ["projectRoot"] },
1599
- handler: async (input) => { const { auditPerformance } = await import('./analyzers/audit.js'); return auditPerformance(input.projectRoot); },
1600
- },
1601
- ],
1602
- };
1603
- //# sourceMappingURL=index.js.map