frontend-performance-monitor
Version:
This is browser performance and error monitoring tool
153 lines (152 loc) • 5.8 kB
JavaScript
/**
* FrontendMonitor class for monitoring web performance and errors.
*/
export class FrontendMonitor {
constructor(reportUrl) {
this.isMonitoring = false;
this.clsValue = 0;
this.reportUrl = reportUrl;
this.performanceTiming = performance.timing;
}
// Initialize monitoring
init() {
if (this.isMonitoring)
return;
this.isMonitoring = true;
this.setupPerformanceObserver();
this.captureErrors();
window.addEventListener('load', () => this.reportPerformance());
}
// Performance metrics monitoring
setupPerformanceObserver() {
if (!('PerformanceObserver' in window))
return;
// Monitor FP/FCP
new PerformanceObserver(list => {
for (const entry of list.getEntriesByName('first-paint')) {
this.report({
type: 'performance',
data: { ...this.getBasicTiming(), fp: Math.round(entry.startTime) }
});
}
for (const entry of list.getEntriesByName('first-contentful-paint')) {
this.report({
type: 'performance',
data: { ...this.getBasicTiming(), fcp: Math.round(entry.startTime) }
});
}
}).observe({ type: 'paint', buffered: true });
// Monitor LCP
new PerformanceObserver(list => {
const entries = list.getEntries();
if (entries.length > 0) {
const lastEntry = entries[entries.length - 1];
const lcp = lastEntry.renderTime || lastEntry.loadTime;
this.report({
type: 'performance',
data: { ...this.getBasicTiming(), lcp: Math.round(lcp) }
});
}
}).observe({ type: 'largest-contentful-paint', buffered: true });
// Monitor CLS
new PerformanceObserver(list => {
const entries = list.getEntries();
for (const entry of entries) {
if (!entry.hadRecentInput) {
this.clsValue += entry.value;
}
}
this.report({
type: 'performance',
data: { ...this.getBasicTiming(), cls: this.clsValue }
});
}).observe({ type: 'layout-shift', buffered: true });
}
// Calculate basic performance metrics
getBasicTiming() {
const t = this.performanceTiming;
return {
dns: t.domainLookupEnd - t.domainLookupStart,
tcp: t.connectEnd - t.connectStart,
ttfb: t.responseStart - t.requestStart,
domParse: t.domComplete - t.domInteractive,
resources: t.loadEventStart - t.domContentLoadedEventEnd,
domReady: t.domContentLoadedEventEnd - t.navigationStart,
interactive: t.domInteractive - t.navigationStart,
load: t.loadEventEnd - t.navigationStart
};
}
// Error monitoring
captureErrors() {
// JavaScript runtime errors
window.addEventListener('error', event => {
var _a;
this.report({
type: 'error',
data: {
message: event.message,
filename: event.filename || window.location.href,
lineno: event.lineno || 0,
colno: event.colno || 0,
stack: (_a = event.error) === null || _a === void 0 ? void 0 : _a.stack,
type: 'js',
timestamp: Date.now()
}
});
}, true);
// Resource loading errors
window.addEventListener('error', event => {
const target = event.target;
if (target && (target.tagName === 'LINK' || target.tagName === 'SCRIPT' || target.tagName === 'IMG')) {
this.report({
type: 'error',
data: {
message: `Resource load error: ${target.src || target.href}`,
filename: target.src || target.href || '',
lineno: 0,
colno: 0,
type: 'resource',
timestamp: Date.now()
}
});
}
}, true);
// Uncaught promise rejections
window.addEventListener('unhandledrejection', event => {
var _a, _b;
this.report({
type: 'error',
data: {
message: ((_a = event.reason) === null || _a === void 0 ? void 0 : _a.message) || 'Unhandled promise rejection',
filename: window.location.href,
lineno: 0,
colno: 0,
stack: (_b = event.reason) === null || _b === void 0 ? void 0 : _b.stack,
type: 'promise',
timestamp: Date.now()
}
});
});
}
// Report core performance metrics
reportPerformance() {
this.report({
type: 'performance',
data: this.getBasicTiming()
});
}
// Data reporting method
report(data) {
if (navigator.sendBeacon) {
const blob = new Blob([JSON.stringify(data)], { type: 'application/json' });
navigator.sendBeacon(this.reportUrl, blob);
}
else {
// Fallback using XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.open('POST', this.reportUrl, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(data));
}
}
}