omgkit 2.0.6 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -3
- package/plugin/agents/architect.md +357 -43
- package/plugin/agents/code-reviewer.md +481 -22
- package/plugin/agents/debugger.md +397 -30
- package/plugin/agents/docs-manager.md +431 -23
- package/plugin/agents/fullstack-developer.md +395 -34
- package/plugin/agents/git-manager.md +438 -20
- package/plugin/agents/oracle.md +329 -53
- package/plugin/agents/planner.md +275 -32
- package/plugin/agents/researcher.md +343 -21
- package/plugin/agents/scout.md +423 -18
- package/plugin/agents/sprint-master.md +418 -48
- package/plugin/agents/tester.md +551 -26
- package/plugin/skills/backend/api-architecture/SKILL.md +857 -0
- package/plugin/skills/backend/caching-strategies/SKILL.md +755 -0
- package/plugin/skills/backend/event-driven-architecture/SKILL.md +753 -0
- package/plugin/skills/backend/real-time-systems/SKILL.md +635 -0
- package/plugin/skills/databases/database-optimization/SKILL.md +571 -0
- package/plugin/skills/devops/monorepo-management/SKILL.md +595 -0
- package/plugin/skills/devops/observability/SKILL.md +622 -0
- package/plugin/skills/devops/performance-profiling/SKILL.md +905 -0
- package/plugin/skills/frontend/advanced-ui-design/SKILL.md +426 -0
- package/plugin/skills/integrations/ai-integration/SKILL.md +730 -0
- package/plugin/skills/integrations/payment-integration/SKILL.md +735 -0
- package/plugin/skills/methodology/problem-solving/SKILL.md +355 -0
- package/plugin/skills/methodology/research-validation/SKILL.md +668 -0
- package/plugin/skills/methodology/sequential-thinking/SKILL.md +260 -0
- package/plugin/skills/mobile/mobile-development/SKILL.md +756 -0
- package/plugin/skills/security/security-hardening/SKILL.md +633 -0
- package/plugin/skills/tools/document-processing/SKILL.md +916 -0
- package/plugin/skills/tools/image-processing/SKILL.md +748 -0
- package/plugin/skills/tools/mcp-development/SKILL.md +883 -0
- package/plugin/skills/tools/media-processing/SKILL.md +831 -0
|
@@ -0,0 +1,905 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: performance-profiling
|
|
3
|
+
description: Browser automation, Chrome DevTools Protocol, Lighthouse audits, and Core Web Vitals optimization
|
|
4
|
+
category: devops
|
|
5
|
+
triggers:
|
|
6
|
+
- performance profiling
|
|
7
|
+
- lighthouse audit
|
|
8
|
+
- core web vitals
|
|
9
|
+
- page speed
|
|
10
|
+
- chrome devtools
|
|
11
|
+
- performance optimization
|
|
12
|
+
- bundle analysis
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Performance Profiling
|
|
16
|
+
|
|
17
|
+
Master **browser performance profiling** using Chrome DevTools Protocol, Lighthouse, and Core Web Vitals monitoring. This skill enables automated performance audits, bottleneck identification, and optimization guidance.
|
|
18
|
+
|
|
19
|
+
## Purpose
|
|
20
|
+
|
|
21
|
+
Performance directly impacts user experience and business metrics:
|
|
22
|
+
|
|
23
|
+
- Run automated Lighthouse audits with detailed analysis
|
|
24
|
+
- Monitor Core Web Vitals (LCP, FID, CLS, INP)
|
|
25
|
+
- Identify JavaScript performance bottlenecks
|
|
26
|
+
- Analyze network waterfalls and resource loading
|
|
27
|
+
- Detect memory leaks and optimize memory usage
|
|
28
|
+
- Create performance budgets and regression testing
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
### 1. Lighthouse Automation
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import lighthouse from 'lighthouse';
|
|
36
|
+
import * as chromeLauncher from 'chrome-launcher';
|
|
37
|
+
|
|
38
|
+
interface LighthouseConfig {
|
|
39
|
+
url: string;
|
|
40
|
+
categories?: ('performance' | 'accessibility' | 'best-practices' | 'seo')[];
|
|
41
|
+
device?: 'mobile' | 'desktop';
|
|
42
|
+
throttling?: ThrottlingConfig;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface LighthouseResult {
|
|
46
|
+
scores: Record<string, number>;
|
|
47
|
+
metrics: PerformanceMetrics;
|
|
48
|
+
opportunities: Opportunity[];
|
|
49
|
+
diagnostics: Diagnostic[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Run Lighthouse audit
|
|
53
|
+
async function runLighthouseAudit(config: LighthouseConfig): Promise<LighthouseResult> {
|
|
54
|
+
const chrome = await chromeLauncher.launch({ chromeFlags: ['--headless'] });
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const options = {
|
|
58
|
+
port: chrome.port,
|
|
59
|
+
output: 'json',
|
|
60
|
+
onlyCategories: config.categories || ['performance'],
|
|
61
|
+
formFactor: config.device || 'mobile',
|
|
62
|
+
throttling: config.throttling || {
|
|
63
|
+
rttMs: 150,
|
|
64
|
+
throughputKbps: 1638,
|
|
65
|
+
cpuSlowdownMultiplier: 4,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const result = await lighthouse(config.url, options);
|
|
70
|
+
const lhr = result?.lhr;
|
|
71
|
+
|
|
72
|
+
if (!lhr) throw new Error('Lighthouse failed to generate report');
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
scores: {
|
|
76
|
+
performance: lhr.categories.performance?.score ?? 0,
|
|
77
|
+
accessibility: lhr.categories.accessibility?.score ?? 0,
|
|
78
|
+
bestPractices: lhr.categories['best-practices']?.score ?? 0,
|
|
79
|
+
seo: lhr.categories.seo?.score ?? 0,
|
|
80
|
+
},
|
|
81
|
+
metrics: extractMetrics(lhr),
|
|
82
|
+
opportunities: extractOpportunities(lhr),
|
|
83
|
+
diagnostics: extractDiagnostics(lhr),
|
|
84
|
+
};
|
|
85
|
+
} finally {
|
|
86
|
+
await chrome.kill();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Extract key metrics
|
|
91
|
+
function extractMetrics(lhr: LH.Result): PerformanceMetrics {
|
|
92
|
+
const audits = lhr.audits;
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
// Core Web Vitals
|
|
96
|
+
lcp: {
|
|
97
|
+
value: audits['largest-contentful-paint']?.numericValue ?? 0,
|
|
98
|
+
score: audits['largest-contentful-paint']?.score ?? 0,
|
|
99
|
+
displayValue: audits['largest-contentful-paint']?.displayValue ?? '',
|
|
100
|
+
},
|
|
101
|
+
fid: {
|
|
102
|
+
value: audits['max-potential-fid']?.numericValue ?? 0,
|
|
103
|
+
score: audits['max-potential-fid']?.score ?? 0,
|
|
104
|
+
displayValue: audits['max-potential-fid']?.displayValue ?? '',
|
|
105
|
+
},
|
|
106
|
+
cls: {
|
|
107
|
+
value: audits['cumulative-layout-shift']?.numericValue ?? 0,
|
|
108
|
+
score: audits['cumulative-layout-shift']?.score ?? 0,
|
|
109
|
+
displayValue: audits['cumulative-layout-shift']?.displayValue ?? '',
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
// Additional metrics
|
|
113
|
+
fcp: audits['first-contentful-paint']?.numericValue ?? 0,
|
|
114
|
+
ttfb: audits['server-response-time']?.numericValue ?? 0,
|
|
115
|
+
tti: audits['interactive']?.numericValue ?? 0,
|
|
116
|
+
tbt: audits['total-blocking-time']?.numericValue ?? 0,
|
|
117
|
+
speedIndex: audits['speed-index']?.numericValue ?? 0,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Extract optimization opportunities
|
|
122
|
+
function extractOpportunities(lhr: LH.Result): Opportunity[] {
|
|
123
|
+
const opportunityAudits = [
|
|
124
|
+
'render-blocking-resources',
|
|
125
|
+
'unused-css-rules',
|
|
126
|
+
'unused-javascript',
|
|
127
|
+
'modern-image-formats',
|
|
128
|
+
'offscreen-images',
|
|
129
|
+
'unminified-css',
|
|
130
|
+
'unminified-javascript',
|
|
131
|
+
'efficient-animated-content',
|
|
132
|
+
'duplicated-javascript',
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
return opportunityAudits
|
|
136
|
+
.map(id => lhr.audits[id])
|
|
137
|
+
.filter(audit => audit && audit.score !== null && audit.score < 1)
|
|
138
|
+
.map(audit => ({
|
|
139
|
+
id: audit.id,
|
|
140
|
+
title: audit.title,
|
|
141
|
+
description: audit.description,
|
|
142
|
+
savings: audit.details?.overallSavingsMs ?? 0,
|
|
143
|
+
items: audit.details?.items ?? [],
|
|
144
|
+
}))
|
|
145
|
+
.sort((a, b) => b.savings - a.savings);
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### 2. Core Web Vitals Monitoring
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
import { onLCP, onFID, onCLS, onINP, onTTFB } from 'web-vitals';
|
|
153
|
+
|
|
154
|
+
interface WebVitalsReport {
|
|
155
|
+
lcp: VitalMetric;
|
|
156
|
+
fid: VitalMetric;
|
|
157
|
+
cls: VitalMetric;
|
|
158
|
+
inp: VitalMetric;
|
|
159
|
+
ttfb: VitalMetric;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
interface VitalMetric {
|
|
163
|
+
value: number;
|
|
164
|
+
rating: 'good' | 'needs-improvement' | 'poor';
|
|
165
|
+
entries: PerformanceEntry[];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Real User Monitoring (RUM) setup
|
|
169
|
+
function initWebVitalsMonitoring(onReport: (report: WebVitalsReport) => void): void {
|
|
170
|
+
const metrics: Partial<WebVitalsReport> = {};
|
|
171
|
+
|
|
172
|
+
const reportIfComplete = () => {
|
|
173
|
+
if (metrics.lcp && metrics.fid && metrics.cls && metrics.inp && metrics.ttfb) {
|
|
174
|
+
onReport(metrics as WebVitalsReport);
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
onLCP((metric) => {
|
|
179
|
+
metrics.lcp = {
|
|
180
|
+
value: metric.value,
|
|
181
|
+
rating: metric.rating,
|
|
182
|
+
entries: metric.entries,
|
|
183
|
+
};
|
|
184
|
+
reportIfComplete();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
onFID((metric) => {
|
|
188
|
+
metrics.fid = {
|
|
189
|
+
value: metric.value,
|
|
190
|
+
rating: metric.rating,
|
|
191
|
+
entries: metric.entries,
|
|
192
|
+
};
|
|
193
|
+
reportIfComplete();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
onCLS((metric) => {
|
|
197
|
+
metrics.cls = {
|
|
198
|
+
value: metric.value,
|
|
199
|
+
rating: metric.rating,
|
|
200
|
+
entries: metric.entries,
|
|
201
|
+
};
|
|
202
|
+
reportIfComplete();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
onINP((metric) => {
|
|
206
|
+
metrics.inp = {
|
|
207
|
+
value: metric.value,
|
|
208
|
+
rating: metric.rating,
|
|
209
|
+
entries: metric.entries,
|
|
210
|
+
};
|
|
211
|
+
reportIfComplete();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
onTTFB((metric) => {
|
|
215
|
+
metrics.ttfb = {
|
|
216
|
+
value: metric.value,
|
|
217
|
+
rating: metric.rating,
|
|
218
|
+
entries: metric.entries,
|
|
219
|
+
};
|
|
220
|
+
reportIfComplete();
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Web Vitals thresholds
|
|
225
|
+
const WEB_VITALS_THRESHOLDS = {
|
|
226
|
+
LCP: { good: 2500, poor: 4000 }, // milliseconds
|
|
227
|
+
FID: { good: 100, poor: 300 }, // milliseconds
|
|
228
|
+
CLS: { good: 0.1, poor: 0.25 }, // score
|
|
229
|
+
INP: { good: 200, poor: 500 }, // milliseconds
|
|
230
|
+
TTFB: { good: 800, poor: 1800 }, // milliseconds
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// Analyze and provide recommendations
|
|
234
|
+
function analyzeWebVitals(report: WebVitalsReport): VitalsAnalysis {
|
|
235
|
+
const issues: VitalIssue[] = [];
|
|
236
|
+
|
|
237
|
+
// LCP analysis
|
|
238
|
+
if (report.lcp.rating !== 'good') {
|
|
239
|
+
const lcpElement = report.lcp.entries[0]?.element;
|
|
240
|
+
issues.push({
|
|
241
|
+
metric: 'LCP',
|
|
242
|
+
value: report.lcp.value,
|
|
243
|
+
rating: report.lcp.rating,
|
|
244
|
+
element: lcpElement,
|
|
245
|
+
recommendations: getLCPRecommendations(report.lcp, lcpElement),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// CLS analysis
|
|
250
|
+
if (report.cls.rating !== 'good') {
|
|
251
|
+
const clsSources = report.cls.entries.flatMap(
|
|
252
|
+
entry => (entry as LayoutShift).sources || []
|
|
253
|
+
);
|
|
254
|
+
issues.push({
|
|
255
|
+
metric: 'CLS',
|
|
256
|
+
value: report.cls.value,
|
|
257
|
+
rating: report.cls.rating,
|
|
258
|
+
sources: clsSources,
|
|
259
|
+
recommendations: getCLSRecommendations(clsSources),
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// INP analysis
|
|
264
|
+
if (report.inp.rating !== 'good') {
|
|
265
|
+
issues.push({
|
|
266
|
+
metric: 'INP',
|
|
267
|
+
value: report.inp.value,
|
|
268
|
+
rating: report.inp.rating,
|
|
269
|
+
recommendations: getINPRecommendations(report.inp),
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
overallRating: calculateOverallRating(report),
|
|
275
|
+
issues,
|
|
276
|
+
score: calculateVitalsScore(report),
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// LCP optimization recommendations
|
|
281
|
+
function getLCPRecommendations(metric: VitalMetric, element?: Element): string[] {
|
|
282
|
+
const recommendations: string[] = [];
|
|
283
|
+
|
|
284
|
+
if (element?.tagName === 'IMG') {
|
|
285
|
+
recommendations.push(
|
|
286
|
+
'Add fetchpriority="high" to the LCP image',
|
|
287
|
+
'Preload the LCP image: <link rel="preload" as="image" href="...">',
|
|
288
|
+
'Use modern image formats (WebP, AVIF)',
|
|
289
|
+
'Ensure image is properly sized (no layout shift)',
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (metric.value > 4000) {
|
|
294
|
+
recommendations.push(
|
|
295
|
+
'Reduce server response time (TTFB)',
|
|
296
|
+
'Eliminate render-blocking resources',
|
|
297
|
+
'Consider server-side rendering for critical content',
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
recommendations.push(
|
|
302
|
+
'Minimize critical CSS',
|
|
303
|
+
'Use efficient cache policy for static assets',
|
|
304
|
+
'Consider using a CDN',
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
return recommendations;
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### 3. Chrome DevTools Protocol
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
import CDP from 'chrome-remote-interface';
|
|
315
|
+
|
|
316
|
+
interface PerformanceTrace {
|
|
317
|
+
timelineEvents: TimelineEvent[];
|
|
318
|
+
networkRequests: NetworkRequest[];
|
|
319
|
+
jsProfile: CPUProfile;
|
|
320
|
+
heapSnapshot: HeapSnapshot;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Connect to Chrome DevTools
|
|
324
|
+
async function connectToDevTools(port: number = 9222): Promise<CDP.Client> {
|
|
325
|
+
const client = await CDP({ port });
|
|
326
|
+
|
|
327
|
+
// Enable required domains
|
|
328
|
+
await Promise.all([
|
|
329
|
+
client.Page.enable(),
|
|
330
|
+
client.Network.enable(),
|
|
331
|
+
client.Performance.enable(),
|
|
332
|
+
client.Profiler.enable(),
|
|
333
|
+
client.HeapProfiler.enable(),
|
|
334
|
+
]);
|
|
335
|
+
|
|
336
|
+
return client;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Capture performance trace
|
|
340
|
+
async function capturePerformanceTrace(
|
|
341
|
+
client: CDP.Client,
|
|
342
|
+
url: string
|
|
343
|
+
): Promise<PerformanceTrace> {
|
|
344
|
+
const networkRequests: NetworkRequest[] = [];
|
|
345
|
+
const timelineEvents: TimelineEvent[] = [];
|
|
346
|
+
|
|
347
|
+
// Collect network requests
|
|
348
|
+
client.Network.requestWillBeSent(params => {
|
|
349
|
+
networkRequests.push({
|
|
350
|
+
requestId: params.requestId,
|
|
351
|
+
url: params.request.url,
|
|
352
|
+
method: params.request.method,
|
|
353
|
+
timestamp: params.timestamp,
|
|
354
|
+
type: params.type,
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
client.Network.responseReceived(params => {
|
|
359
|
+
const request = networkRequests.find(r => r.requestId === params.requestId);
|
|
360
|
+
if (request) {
|
|
361
|
+
request.status = params.response.status;
|
|
362
|
+
request.mimeType = params.response.mimeType;
|
|
363
|
+
request.encodedDataLength = params.response.encodedDataLength;
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// Start tracing
|
|
368
|
+
await client.Tracing.start({
|
|
369
|
+
categories: [
|
|
370
|
+
'devtools.timeline',
|
|
371
|
+
'v8.execute',
|
|
372
|
+
'blink.user_timing',
|
|
373
|
+
'loading',
|
|
374
|
+
'painting',
|
|
375
|
+
].join(','),
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// Navigate to page
|
|
379
|
+
await client.Page.navigate({ url });
|
|
380
|
+
await client.Page.loadEventFired();
|
|
381
|
+
|
|
382
|
+
// Wait for network idle
|
|
383
|
+
await waitForNetworkIdle(client);
|
|
384
|
+
|
|
385
|
+
// Stop tracing
|
|
386
|
+
const { value: traceData } = await client.Tracing.tracingComplete();
|
|
387
|
+
|
|
388
|
+
// Capture JS profile
|
|
389
|
+
await client.Profiler.start();
|
|
390
|
+
await new Promise(r => setTimeout(r, 2000)); // Profile for 2 seconds
|
|
391
|
+
const { profile: jsProfile } = await client.Profiler.stop();
|
|
392
|
+
|
|
393
|
+
// Capture heap snapshot
|
|
394
|
+
const heapChunks: string[] = [];
|
|
395
|
+
client.HeapProfiler.addHeapSnapshotChunk(({ chunk }) => heapChunks.push(chunk));
|
|
396
|
+
await client.HeapProfiler.takeHeapSnapshot();
|
|
397
|
+
const heapSnapshot = JSON.parse(heapChunks.join(''));
|
|
398
|
+
|
|
399
|
+
return {
|
|
400
|
+
timelineEvents: parseTraceData(traceData),
|
|
401
|
+
networkRequests,
|
|
402
|
+
jsProfile,
|
|
403
|
+
heapSnapshot,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Analyze network waterfall
|
|
408
|
+
function analyzeNetworkWaterfall(requests: NetworkRequest[]): WaterfallAnalysis {
|
|
409
|
+
const sorted = [...requests].sort((a, b) => a.timestamp - b.timestamp);
|
|
410
|
+
const totalTime = sorted[sorted.length - 1]?.endTime - sorted[0]?.timestamp;
|
|
411
|
+
|
|
412
|
+
// Identify critical path
|
|
413
|
+
const criticalPath = sorted.filter(r =>
|
|
414
|
+
r.type === 'Document' ||
|
|
415
|
+
(r.type === 'Script' && !r.url.includes('async')) ||
|
|
416
|
+
r.type === 'Stylesheet'
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
// Find blocking resources
|
|
420
|
+
const blockingResources = sorted.filter(r =>
|
|
421
|
+
r.renderBlocking ||
|
|
422
|
+
(r.type === 'Script' && !r.async && !r.defer)
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
// Calculate metrics
|
|
426
|
+
return {
|
|
427
|
+
totalRequests: requests.length,
|
|
428
|
+
totalTime,
|
|
429
|
+
totalSize: requests.reduce((sum, r) => sum + (r.encodedDataLength || 0), 0),
|
|
430
|
+
criticalPath,
|
|
431
|
+
blockingResources,
|
|
432
|
+
byType: groupByType(requests),
|
|
433
|
+
recommendations: generateWaterfallRecommendations(requests),
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Memory leak detection
|
|
438
|
+
async function detectMemoryLeaks(
|
|
439
|
+
client: CDP.Client,
|
|
440
|
+
actions: () => Promise<void>,
|
|
441
|
+
iterations: number = 3
|
|
442
|
+
): Promise<MemoryLeakReport> {
|
|
443
|
+
const snapshots: HeapSnapshot[] = [];
|
|
444
|
+
|
|
445
|
+
// Take initial snapshot
|
|
446
|
+
snapshots.push(await takeHeapSnapshot(client));
|
|
447
|
+
|
|
448
|
+
// Perform actions multiple times
|
|
449
|
+
for (let i = 0; i < iterations; i++) {
|
|
450
|
+
await actions();
|
|
451
|
+
await client.HeapProfiler.collectGarbage();
|
|
452
|
+
await new Promise(r => setTimeout(r, 100));
|
|
453
|
+
snapshots.push(await takeHeapSnapshot(client));
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Analyze heap growth
|
|
457
|
+
const heapGrowth = analyzeHeapGrowth(snapshots);
|
|
458
|
+
const retainedObjects = findRetainedObjects(snapshots);
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
hasLeak: heapGrowth.trend === 'increasing',
|
|
462
|
+
heapGrowth,
|
|
463
|
+
retainedObjects,
|
|
464
|
+
recommendations: generateMemoryRecommendations(retainedObjects),
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
### 4. Bundle Analysis
|
|
470
|
+
|
|
471
|
+
```typescript
|
|
472
|
+
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
|
|
473
|
+
import { visualizer } from 'rollup-plugin-visualizer';
|
|
474
|
+
|
|
475
|
+
interface BundleAnalysis {
|
|
476
|
+
totalSize: number;
|
|
477
|
+
gzipSize: number;
|
|
478
|
+
modules: ModuleInfo[];
|
|
479
|
+
duplicates: DuplicateModule[];
|
|
480
|
+
largestModules: ModuleInfo[];
|
|
481
|
+
recommendations: string[];
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Webpack bundle analysis
|
|
485
|
+
function createBundleAnalyzerConfig(): BundleAnalyzerPlugin {
|
|
486
|
+
return new BundleAnalyzerPlugin({
|
|
487
|
+
analyzerMode: 'json',
|
|
488
|
+
reportFilename: 'bundle-report.json',
|
|
489
|
+
generateStatsFile: true,
|
|
490
|
+
statsFilename: 'bundle-stats.json',
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Analyze bundle stats
|
|
495
|
+
async function analyzeBundleStats(statsPath: string): Promise<BundleAnalysis> {
|
|
496
|
+
const stats = JSON.parse(await fs.readFile(statsPath, 'utf-8'));
|
|
497
|
+
|
|
498
|
+
const modules = flattenModules(stats.modules);
|
|
499
|
+
const duplicates = findDuplicates(modules);
|
|
500
|
+
|
|
501
|
+
// Calculate sizes
|
|
502
|
+
const totalSize = modules.reduce((sum, m) => sum + m.size, 0);
|
|
503
|
+
const gzipSize = await calculateGzipSize(statsPath);
|
|
504
|
+
|
|
505
|
+
// Sort by size
|
|
506
|
+
const largestModules = [...modules]
|
|
507
|
+
.sort((a, b) => b.size - a.size)
|
|
508
|
+
.slice(0, 20);
|
|
509
|
+
|
|
510
|
+
return {
|
|
511
|
+
totalSize,
|
|
512
|
+
gzipSize,
|
|
513
|
+
modules,
|
|
514
|
+
duplicates,
|
|
515
|
+
largestModules,
|
|
516
|
+
recommendations: generateBundleRecommendations({
|
|
517
|
+
totalSize,
|
|
518
|
+
duplicates,
|
|
519
|
+
largestModules,
|
|
520
|
+
}),
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Find duplicate modules
|
|
525
|
+
function findDuplicates(modules: ModuleInfo[]): DuplicateModule[] {
|
|
526
|
+
const modulesByName = new Map<string, ModuleInfo[]>();
|
|
527
|
+
|
|
528
|
+
for (const module of modules) {
|
|
529
|
+
const name = getModuleName(module.identifier);
|
|
530
|
+
const existing = modulesByName.get(name) || [];
|
|
531
|
+
existing.push(module);
|
|
532
|
+
modulesByName.set(name, existing);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
return Array.from(modulesByName.entries())
|
|
536
|
+
.filter(([_, instances]) => instances.length > 1)
|
|
537
|
+
.map(([name, instances]) => ({
|
|
538
|
+
name,
|
|
539
|
+
instances: instances.length,
|
|
540
|
+
totalSize: instances.reduce((sum, m) => sum + m.size, 0),
|
|
541
|
+
versions: [...new Set(instances.map(m => getModuleVersion(m.identifier)))],
|
|
542
|
+
}))
|
|
543
|
+
.sort((a, b) => b.totalSize - a.totalSize);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Generate bundle recommendations
|
|
547
|
+
function generateBundleRecommendations(analysis: {
|
|
548
|
+
totalSize: number;
|
|
549
|
+
duplicates: DuplicateModule[];
|
|
550
|
+
largestModules: ModuleInfo[];
|
|
551
|
+
}): string[] {
|
|
552
|
+
const recommendations: string[] = [];
|
|
553
|
+
|
|
554
|
+
// Size recommendations
|
|
555
|
+
if (analysis.totalSize > 500 * 1024) {
|
|
556
|
+
recommendations.push(
|
|
557
|
+
'Consider code splitting to reduce initial bundle size',
|
|
558
|
+
'Use dynamic imports for routes and large components',
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Duplicate recommendations
|
|
563
|
+
if (analysis.duplicates.length > 0) {
|
|
564
|
+
recommendations.push(
|
|
565
|
+
`Found ${analysis.duplicates.length} duplicate packages - dedupe with npm/yarn`,
|
|
566
|
+
'Consider using webpack.optimize.ModuleConcatenationPlugin',
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
const biggestDupe = analysis.duplicates[0];
|
|
570
|
+
if (biggestDupe) {
|
|
571
|
+
recommendations.push(
|
|
572
|
+
`Largest duplicate: ${biggestDupe.name} (${formatBytes(biggestDupe.totalSize)})`,
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Large module recommendations
|
|
578
|
+
const veryLargeModules = analysis.largestModules.filter(m => m.size > 100 * 1024);
|
|
579
|
+
if (veryLargeModules.length > 0) {
|
|
580
|
+
recommendations.push(
|
|
581
|
+
'Large modules detected - consider lazy loading:',
|
|
582
|
+
...veryLargeModules.slice(0, 3).map(m =>
|
|
583
|
+
` - ${m.name}: ${formatBytes(m.size)}`
|
|
584
|
+
),
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return recommendations;
|
|
589
|
+
}
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
### 5. Performance Budget
|
|
593
|
+
|
|
594
|
+
```typescript
|
|
595
|
+
interface PerformanceBudget {
|
|
596
|
+
metrics: MetricBudget[];
|
|
597
|
+
resources: ResourceBudget[];
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
interface MetricBudget {
|
|
601
|
+
metric: string;
|
|
602
|
+
budget: number;
|
|
603
|
+
unit: 'ms' | 'score' | 'bytes';
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
interface ResourceBudget {
|
|
607
|
+
type: string;
|
|
608
|
+
budget: number;
|
|
609
|
+
count?: number;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// Default performance budget
|
|
613
|
+
const DEFAULT_BUDGET: PerformanceBudget = {
|
|
614
|
+
metrics: [
|
|
615
|
+
{ metric: 'LCP', budget: 2500, unit: 'ms' },
|
|
616
|
+
{ metric: 'FID', budget: 100, unit: 'ms' },
|
|
617
|
+
{ metric: 'CLS', budget: 0.1, unit: 'score' },
|
|
618
|
+
{ metric: 'TTI', budget: 3800, unit: 'ms' },
|
|
619
|
+
{ metric: 'TBT', budget: 200, unit: 'ms' },
|
|
620
|
+
],
|
|
621
|
+
resources: [
|
|
622
|
+
{ type: 'script', budget: 300 * 1024 },
|
|
623
|
+
{ type: 'stylesheet', budget: 50 * 1024 },
|
|
624
|
+
{ type: 'image', budget: 500 * 1024 },
|
|
625
|
+
{ type: 'font', budget: 100 * 1024 },
|
|
626
|
+
{ type: 'total', budget: 1000 * 1024 },
|
|
627
|
+
],
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
// Check budget violations
|
|
631
|
+
function checkPerformanceBudget(
|
|
632
|
+
metrics: PerformanceMetrics,
|
|
633
|
+
resources: ResourceMetrics,
|
|
634
|
+
budget: PerformanceBudget = DEFAULT_BUDGET
|
|
635
|
+
): BudgetReport {
|
|
636
|
+
const violations: BudgetViolation[] = [];
|
|
637
|
+
|
|
638
|
+
// Check metric budgets
|
|
639
|
+
for (const { metric, budget: limit, unit } of budget.metrics) {
|
|
640
|
+
const actual = metrics[metric.toLowerCase()];
|
|
641
|
+
if (actual > limit) {
|
|
642
|
+
violations.push({
|
|
643
|
+
type: 'metric',
|
|
644
|
+
name: metric,
|
|
645
|
+
budget: limit,
|
|
646
|
+
actual,
|
|
647
|
+
unit,
|
|
648
|
+
overBy: ((actual - limit) / limit * 100).toFixed(1) + '%',
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Check resource budgets
|
|
654
|
+
for (const { type, budget: limit, count } of budget.resources) {
|
|
655
|
+
const resource = resources[type];
|
|
656
|
+
if (resource.size > limit) {
|
|
657
|
+
violations.push({
|
|
658
|
+
type: 'resource',
|
|
659
|
+
name: type,
|
|
660
|
+
budget: limit,
|
|
661
|
+
actual: resource.size,
|
|
662
|
+
unit: 'bytes',
|
|
663
|
+
overBy: formatBytes(resource.size - limit),
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
if (count && resource.count > count) {
|
|
667
|
+
violations.push({
|
|
668
|
+
type: 'resource-count',
|
|
669
|
+
name: `${type} count`,
|
|
670
|
+
budget: count,
|
|
671
|
+
actual: resource.count,
|
|
672
|
+
unit: 'requests',
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
return {
|
|
678
|
+
passed: violations.length === 0,
|
|
679
|
+
violations,
|
|
680
|
+
summary: generateBudgetSummary(violations),
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// Performance regression test
|
|
685
|
+
async function runPerformanceRegression(
|
|
686
|
+
url: string,
|
|
687
|
+
baseline: PerformanceMetrics,
|
|
688
|
+
threshold: number = 0.1 // 10% regression threshold
|
|
689
|
+
): Promise<RegressionReport> {
|
|
690
|
+
const current = await runLighthouseAudit({ url });
|
|
691
|
+
const regressions: Regression[] = [];
|
|
692
|
+
|
|
693
|
+
for (const [metric, baselineValue] of Object.entries(baseline)) {
|
|
694
|
+
const currentValue = current.metrics[metric];
|
|
695
|
+
const change = (currentValue - baselineValue) / baselineValue;
|
|
696
|
+
|
|
697
|
+
if (change > threshold) {
|
|
698
|
+
regressions.push({
|
|
699
|
+
metric,
|
|
700
|
+
baseline: baselineValue,
|
|
701
|
+
current: currentValue,
|
|
702
|
+
change: `+${(change * 100).toFixed(1)}%`,
|
|
703
|
+
severity: change > 0.25 ? 'critical' : 'warning',
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
return {
|
|
709
|
+
passed: regressions.length === 0,
|
|
710
|
+
regressions,
|
|
711
|
+
metrics: current.metrics,
|
|
712
|
+
comparison: generateComparison(baseline, current.metrics),
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
### 6. Automated Performance CI/CD
|
|
718
|
+
|
|
719
|
+
```yaml
|
|
720
|
+
# .github/workflows/performance.yml
|
|
721
|
+
name: Performance Audit
|
|
722
|
+
|
|
723
|
+
on:
|
|
724
|
+
pull_request:
|
|
725
|
+
branches: [main]
|
|
726
|
+
push:
|
|
727
|
+
branches: [main]
|
|
728
|
+
|
|
729
|
+
jobs:
|
|
730
|
+
lighthouse:
|
|
731
|
+
runs-on: ubuntu-latest
|
|
732
|
+
steps:
|
|
733
|
+
- uses: actions/checkout@v4
|
|
734
|
+
|
|
735
|
+
- name: Setup Node.js
|
|
736
|
+
uses: actions/setup-node@v4
|
|
737
|
+
with:
|
|
738
|
+
node-version: '20'
|
|
739
|
+
|
|
740
|
+
- name: Install dependencies
|
|
741
|
+
run: npm ci
|
|
742
|
+
|
|
743
|
+
- name: Build
|
|
744
|
+
run: npm run build
|
|
745
|
+
|
|
746
|
+
- name: Start server
|
|
747
|
+
run: npm run start &
|
|
748
|
+
|
|
749
|
+
- name: Run Lighthouse
|
|
750
|
+
uses: treosh/lighthouse-ci-action@v10
|
|
751
|
+
with:
|
|
752
|
+
urls: |
|
|
753
|
+
http://localhost:3000
|
|
754
|
+
http://localhost:3000/products
|
|
755
|
+
budgetPath: ./performance-budget.json
|
|
756
|
+
uploadArtifacts: true
|
|
757
|
+
|
|
758
|
+
- name: Check performance budget
|
|
759
|
+
run: |
|
|
760
|
+
node scripts/check-performance-budget.js \
|
|
761
|
+
--budget ./performance-budget.json \
|
|
762
|
+
--results .lighthouseci/*.json
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
## Use Cases
|
|
766
|
+
|
|
767
|
+
### 1. E-commerce Performance Optimization
|
|
768
|
+
|
|
769
|
+
```typescript
|
|
770
|
+
// E-commerce specific performance audit
|
|
771
|
+
async function auditEcommerceSite(baseUrl: string): Promise<EcommerceAudit> {
|
|
772
|
+
const pages = [
|
|
773
|
+
{ name: 'Home', path: '/' },
|
|
774
|
+
{ name: 'Category', path: '/products' },
|
|
775
|
+
{ name: 'Product', path: '/products/1' },
|
|
776
|
+
{ name: 'Cart', path: '/cart' },
|
|
777
|
+
{ name: 'Checkout', path: '/checkout' },
|
|
778
|
+
];
|
|
779
|
+
|
|
780
|
+
const results = await Promise.all(
|
|
781
|
+
pages.map(async page => ({
|
|
782
|
+
...page,
|
|
783
|
+
audit: await runLighthouseAudit({
|
|
784
|
+
url: `${baseUrl}${page.path}`,
|
|
785
|
+
device: 'mobile',
|
|
786
|
+
}),
|
|
787
|
+
}))
|
|
788
|
+
);
|
|
789
|
+
|
|
790
|
+
return {
|
|
791
|
+
pages: results,
|
|
792
|
+
criticalPath: identifyCriticalPath(results),
|
|
793
|
+
conversionImpact: estimateConversionImpact(results),
|
|
794
|
+
recommendations: prioritizeRecommendations(results),
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
### 2. SPA Performance Monitoring
|
|
800
|
+
|
|
801
|
+
```typescript
|
|
802
|
+
// Single Page App performance monitoring
|
|
803
|
+
function initSPAPerformanceMonitoring(): void {
|
|
804
|
+
// Track route changes
|
|
805
|
+
let routeStartTime: number;
|
|
806
|
+
|
|
807
|
+
router.beforeEach(() => {
|
|
808
|
+
routeStartTime = performance.now();
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
router.afterEach((to) => {
|
|
812
|
+
const routeTime = performance.now() - routeStartTime;
|
|
813
|
+
|
|
814
|
+
// Track custom metric
|
|
815
|
+
performance.measure(`route-${to.name}`, {
|
|
816
|
+
start: routeStartTime,
|
|
817
|
+
duration: routeTime,
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
// Report to analytics
|
|
821
|
+
analytics.track('route_performance', {
|
|
822
|
+
route: to.name,
|
|
823
|
+
duration: routeTime,
|
|
824
|
+
timestamp: Date.now(),
|
|
825
|
+
});
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
// Monitor long tasks
|
|
829
|
+
const observer = new PerformanceObserver((list) => {
|
|
830
|
+
for (const entry of list.getEntries()) {
|
|
831
|
+
if (entry.duration > 50) {
|
|
832
|
+
console.warn('Long task detected:', entry.duration, 'ms');
|
|
833
|
+
analytics.track('long_task', {
|
|
834
|
+
duration: entry.duration,
|
|
835
|
+
startTime: entry.startTime,
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
observer.observe({ entryTypes: ['longtask'] });
|
|
842
|
+
}
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
## Best Practices
|
|
846
|
+
|
|
847
|
+
### Do's
|
|
848
|
+
|
|
849
|
+
- **Test in production-like conditions** - Use throttling and real device testing
|
|
850
|
+
- **Monitor continuously** - Set up RUM for ongoing performance tracking
|
|
851
|
+
- **Focus on user-centric metrics** - Prioritize Core Web Vitals
|
|
852
|
+
- **Automate testing** - Include performance tests in CI/CD
|
|
853
|
+
- **Set budgets** - Define and enforce performance budgets
|
|
854
|
+
- **Profile incrementally** - Test before and after changes
|
|
855
|
+
|
|
856
|
+
### Don'ts
|
|
857
|
+
|
|
858
|
+
- Don't test only on fast connections
|
|
859
|
+
- Don't ignore mobile performance
|
|
860
|
+
- Don't optimize prematurely without data
|
|
861
|
+
- Don't rely solely on synthetic testing
|
|
862
|
+
- Don't forget about third-party scripts
|
|
863
|
+
- Don't ignore cumulative layout shift
|
|
864
|
+
|
|
865
|
+
### Performance Checklist
|
|
866
|
+
|
|
867
|
+
```markdown
|
|
868
|
+
## Pre-Launch Performance Checklist
|
|
869
|
+
|
|
870
|
+
### Core Web Vitals
|
|
871
|
+
- [ ] LCP < 2.5s on mobile
|
|
872
|
+
- [ ] FID < 100ms
|
|
873
|
+
- [ ] CLS < 0.1
|
|
874
|
+
- [ ] INP < 200ms
|
|
875
|
+
|
|
876
|
+
### Resource Optimization
|
|
877
|
+
- [ ] Images optimized (WebP/AVIF, lazy loaded)
|
|
878
|
+
- [ ] JavaScript bundled and minified
|
|
879
|
+
- [ ] CSS optimized and critical CSS inlined
|
|
880
|
+
- [ ] Fonts optimized (preload, font-display)
|
|
881
|
+
|
|
882
|
+
### Caching
|
|
883
|
+
- [ ] Proper cache headers set
|
|
884
|
+
- [ ] Service worker for offline support
|
|
885
|
+
- [ ] CDN configured
|
|
886
|
+
|
|
887
|
+
### Monitoring
|
|
888
|
+
- [ ] RUM implemented
|
|
889
|
+
- [ ] Performance budget defined
|
|
890
|
+
- [ ] CI/CD performance tests
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
## Related Skills
|
|
894
|
+
|
|
895
|
+
- **frontend-design** - UI performance considerations
|
|
896
|
+
- **devops** - Infrastructure optimization
|
|
897
|
+
- **docker** - Container performance
|
|
898
|
+
- **kubernetes** - Scaling for performance
|
|
899
|
+
|
|
900
|
+
## Reference Resources
|
|
901
|
+
|
|
902
|
+
- [Web Vitals](https://web.dev/vitals/)
|
|
903
|
+
- [Lighthouse Documentation](https://developer.chrome.com/docs/lighthouse/)
|
|
904
|
+
- [Chrome DevTools Performance](https://developer.chrome.com/docs/devtools/performance/)
|
|
905
|
+
- [web.dev Performance](https://web.dev/learn/performance/)
|