Skip to content

Commit 6afcf20

Browse files
committed
refactor: replace WebVitalsTracker with checkWebVitals for improved performance reporting
1 parent 9e6f420 commit 6afcf20

3 files changed

Lines changed: 106 additions & 88 deletions

File tree

packages/javascript/src/addons/webVitals.ts

Lines changed: 0 additions & 84 deletions
This file was deleted.

packages/javascript/src/catcher.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ import type { HawkJavaScriptEvent } from './types';
1818
import { isErrorProcessed, markErrorAsProcessed } from './utils/event';
1919
import { ConsoleCatcher } from './addons/consoleCatcher';
2020
import { BreadcrumbManager } from './addons/breadcrumbs';
21-
import { WebVitalsTracker } from './addons/webVitals';
21+
import { checkWebVitals } from './utils/webVitals';
22+
import type { WebVitalsReport } from './utils/webVitals';
2223
import { validateUser, validateContext, isValidEventPayload } from './utils/validation';
2324

2425
/**
@@ -179,11 +180,14 @@ export default class Catcher {
179180
}
180181

181182
/**
182-
* Initialize Web Vitals tracking
183+
* Initialize Web Vitals check
183184
*/
184185
if (settings.webVitals) {
185-
new WebVitalsTracker({
186-
sendEvent: (message, context) => this.send(message, context),
186+
checkWebVitals((report: WebVitalsReport) => {
187+
this.send(`Poor Web Vitals: ${report.summary}`, {
188+
webVitals: report.metrics as unknown as Json,
189+
poorCount: report.poorCount,
190+
});
187191
});
188192
}
189193

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/**
2+
* @file Web Vitals check — collects metrics and reports poor ones via callback
3+
*/
4+
import log from './log';
5+
6+
type WebVitalRating = 'good' | 'needs-improvement' | 'poor';
7+
8+
export interface WebVitalMetric {
9+
name: string;
10+
value: number;
11+
rating: WebVitalRating;
12+
delta: number;
13+
}
14+
15+
export interface WebVitalsReport {
16+
summary: string;
17+
poorCount: number;
18+
metrics: Record<string, WebVitalMetric>;
19+
}
20+
21+
const METRIC_THRESHOLDS: Record<string, [good: number, poor: number]> = {
22+
LCP: [2500, 4000],
23+
FCP: [1800, 3000],
24+
TTFB: [800, 1800],
25+
INP: [200, 500],
26+
CLS: [0.1, 0.25],
27+
};
28+
29+
const TOTAL_METRICS = 5;
30+
31+
function formatValue(name: string, value: number): string {
32+
return name === 'CLS' ? value.toFixed(3) : `${Math.round(value)}ms`;
33+
}
34+
35+
/**
36+
* Collects all Core Web Vitals. Calls back with a report only if
37+
* at least one metric is rated "poor". Does nothing if all are good.
38+
*
39+
* @param sendReport - called once with poor metrics report
40+
*/
41+
export function checkWebVitals(sendReport: (report: WebVitalsReport) => void): void {
42+
void import('web-vitals').then(({ onCLS, onINP, onLCP, onFCP, onTTFB }) => {
43+
const collected: WebVitalMetric[] = [];
44+
let reported = false;
45+
46+
function tryReport(): void {
47+
if (reported || collected.length < TOTAL_METRICS) {
48+
return;
49+
}
50+
51+
reported = true;
52+
53+
const poor = collected.filter((m) => m.rating === 'poor');
54+
55+
if (poor.length === 0) {
56+
return;
57+
}
58+
59+
const summary = poor
60+
.map((m) => {
61+
const thresholds = METRIC_THRESHOLDS[m.name];
62+
const threshold = thresholds ? ` (poor > ${formatValue(m.name, thresholds[1])})` : '';
63+
64+
return `${m.name} = ${formatValue(m.name, m.value)}${threshold}`;
65+
})
66+
.join(', ');
67+
68+
const metrics: Record<string, WebVitalMetric> = {};
69+
70+
for (const m of collected) {
71+
metrics[m.name] = m;
72+
}
73+
74+
sendReport({ summary, poorCount: poor.length, metrics });
75+
}
76+
77+
function collect(metric: { name: string; value: number; rating: WebVitalRating; delta: number }): void {
78+
collected.push({
79+
name: metric.name,
80+
value: metric.value,
81+
rating: metric.rating,
82+
delta: metric.delta,
83+
});
84+
tryReport();
85+
}
86+
87+
onCLS((m) => collect(m));
88+
onINP((m) => collect(m));
89+
onLCP((m) => collect(m));
90+
onFCP((m) => collect(m));
91+
onTTFB((m) => collect(m));
92+
}).catch(() => {
93+
log(
94+
'web-vitals package is required for Web Vitals tracking. Install it with: npm i web-vitals',
95+
'warn',
96+
);
97+
});
98+
}

0 commit comments

Comments
 (0)