claritykit-svelte
Version:
A comprehensive Svelte component library focused on accessibility, ADHD-optimized design, developer experience, and full SSR compatibility
390 lines (389 loc) • 13.3 kB
JavaScript
/**
* Performance Monitoring Utilities for ClarityKit
*
* Provides comprehensive performance monitoring including Core Web Vitals,
* component render times, and bundle size tracking.
*/
class PerformanceMonitor {
constructor() {
Object.defineProperty(this, "metrics", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
Object.defineProperty(this, "componentMetrics", {
enumerable: true,
configurable: true,
writable: true,
value: new Map()
});
Object.defineProperty(this, "observers", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
Object.defineProperty(this, "isEnabled", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
this.isEnabled = typeof window !== 'undefined' &&
'PerformanceObserver' in window &&
process.env.NODE_ENV !== 'production';
if (this.isEnabled) {
this.initializeObservers();
}
}
/**
* Initialize performance observers for Core Web Vitals
*/
initializeObservers() {
// Largest Contentful Paint
try {
const lcpObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
this.metrics.lcp = lastEntry.startTime;
this.reportMetric('lcp', lastEntry.startTime);
});
lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] });
this.observers.push(lcpObserver);
}
catch (error) {
console.warn('LCP observer not supported:', error);
}
// First Input Delay
try {
const fidObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach((entry) => {
const fid = entry.processingStart - entry.startTime;
this.metrics.fid = fid;
this.reportMetric('fid', fid);
});
});
fidObserver.observe({ entryTypes: ['first-input'] });
this.observers.push(fidObserver);
}
catch (error) {
console.warn('FID observer not supported:', error);
}
// Cumulative Layout Shift
try {
let cumulativeScore = 0;
const clsObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach((entry) => {
if (!entry.hadRecentInput) {
cumulativeScore += entry.value;
}
});
this.metrics.cls = cumulativeScore;
this.reportMetric('cls', cumulativeScore);
});
clsObserver.observe({ entryTypes: ['layout-shift'] });
this.observers.push(clsObserver);
}
catch (error) {
console.warn('CLS observer not supported:', error);
}
// First Contentful Paint
try {
const fcpObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
this.metrics.fcp = lastEntry.startTime;
this.reportMetric('fcp', lastEntry.startTime);
});
fcpObserver.observe({ entryTypes: ['paint'] });
this.observers.push(fcpObserver);
}
catch (error) {
console.warn('FCP observer not supported:', error);
}
// Navigation timing for TTFB
try {
const navigationObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach((entry) => {
const ttfb = entry.responseStart - entry.requestStart;
this.metrics.ttfb = ttfb;
this.reportMetric('ttfb', ttfb);
});
});
navigationObserver.observe({ entryTypes: ['navigation'] });
this.observers.push(navigationObserver);
}
catch (error) {
console.warn('Navigation observer not supported:', error);
}
}
/**
* Track component performance
*/
trackComponent(name) {
if (!this.isEnabled) {
return new ComponentTracker(name, () => { });
}
const startTime = performance.now();
const startMemory = this.getMemoryUsage();
return new ComponentTracker(name, (metrics) => {
const existing = this.componentMetrics.get(name) || {
name,
renderTime: 0,
mountTime: 0,
updateCount: 0
};
const updated = {
...existing,
...metrics,
updateCount: existing.updateCount + 1
};
this.componentMetrics.set(name, updated);
this.reportComponentMetric(name, updated);
});
}
/**
* Get current memory usage
*/
getMemoryUsage() {
if ('memory' in performance) {
return performance.memory.usedJSHeapSize;
}
return 0;
}
/**
* Report performance metric
*/
reportMetric(name, value) {
if (process.env.NODE_ENV === 'development') {
console.log(`[Performance] ${name.toUpperCase()}: ${value.toFixed(2)}ms`);
}
// Send to analytics service in production
if (typeof window !== 'undefined' && window.gtag) {
window.gtag('event', 'performance_metric', {
metric_name: name,
metric_value: Math.round(value),
custom_map: { metric_name: 'custom_metric_name' }
});
}
}
/**
* Report component performance metric
*/
reportComponentMetric(name, metrics) {
if (process.env.NODE_ENV === 'development') {
console.log(`[Component Performance] ${name}:`, {
renderTime: `${metrics.renderTime.toFixed(2)}ms`,
mountTime: `${metrics.mountTime.toFixed(2)}ms`,
updateCount: metrics.updateCount,
memoryUsage: metrics.memoryUsage ? `${(metrics.memoryUsage / 1024 / 1024).toFixed(2)}MB` : 'N/A'
});
}
}
/**
* Get all collected metrics
*/
getMetrics() {
return { ...this.metrics };
}
/**
* Get component metrics
*/
getComponentMetrics() {
return Array.from(this.componentMetrics.values());
}
/**
* Generate performance report
*/
generateReport() {
const metrics = this.getMetrics();
const componentMetrics = this.getComponentMetrics();
let report = '# ClarityKit Performance Report\n\n';
// Core Web Vitals
report += '## Core Web Vitals\n\n';
if (metrics.lcp)
report += `- **LCP**: ${metrics.lcp.toFixed(2)}ms ${this.getScoreEmoji('lcp', metrics.lcp)}\n`;
if (metrics.fid)
report += `- **FID**: ${metrics.fid.toFixed(2)}ms ${this.getScoreEmoji('fid', metrics.fid)}\n`;
if (metrics.cls)
report += `- **CLS**: ${metrics.cls.toFixed(3)} ${this.getScoreEmoji('cls', metrics.cls)}\n`;
if (metrics.fcp)
report += `- **FCP**: ${metrics.fcp.toFixed(2)}ms ${this.getScoreEmoji('fcp', metrics.fcp)}\n`;
if (metrics.ttfb)
report += `- **TTFB**: ${metrics.ttfb.toFixed(2)}ms ${this.getScoreEmoji('ttfb', metrics.ttfb)}\n`;
// Component Performance
if (componentMetrics.length > 0) {
report += '\n## Component Performance\n\n';
componentMetrics
.sort((a, b) => b.renderTime - a.renderTime)
.forEach(metric => {
report += `- **${metric.name}**: ${metric.renderTime.toFixed(2)}ms render, ${metric.updateCount} updates\n`;
});
}
return report;
}
/**
* Get performance score emoji
*/
getScoreEmoji(metric, value) {
const thresholds = {
lcp: { good: 2500, poor: 4000 },
fid: { good: 100, poor: 300 },
cls: { good: 0.1, poor: 0.25 },
fcp: { good: 1800, poor: 3000 },
ttfb: { good: 800, poor: 1800 }
};
const threshold = thresholds[metric];
if (!threshold)
return '';
if (value <= threshold.good)
return '✅';
if (value <= threshold.poor)
return '⚠️';
return '❌';
}
/**
* Clean up observers
*/
destroy() {
this.observers.forEach(observer => observer.disconnect());
this.observers = [];
this.componentMetrics.clear();
}
}
/**
* Component performance tracker
*/
class ComponentTracker {
constructor(name, onComplete) {
Object.defineProperty(this, "startTime", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "startMemory", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "name", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "onComplete", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
this.name = name;
this.onComplete = onComplete;
this.startTime = performance.now();
this.startMemory = this.getMemoryUsage();
}
/**
* Mark component as mounted
*/
markMounted() {
const mountTime = performance.now() - this.startTime;
const memoryUsage = this.getMemoryUsage() - this.startMemory;
this.onComplete({
mountTime,
memoryUsage: memoryUsage > 0 ? memoryUsage : undefined
});
}
/**
* Mark render complete
*/
markRenderComplete() {
const renderTime = performance.now() - this.startTime;
this.onComplete({
renderTime
});
}
/**
* Get current memory usage
*/
getMemoryUsage() {
if (typeof performance !== 'undefined' && 'memory' in performance) {
return performance.memory.usedJSHeapSize;
}
return 0;
}
}
// Global performance monitor instance
export const performanceMonitor = new PerformanceMonitor();
/**
* Higher-order component for performance tracking
*/
export function withPerformanceTracking(Component, componentName) {
const name = componentName || Component.name || 'UnknownComponent';
return function TrackedComponent(props) {
const tracker = performanceMonitor.trackComponent(name);
// Track mount time
if (typeof window !== 'undefined') {
setTimeout(() => tracker.markMounted(), 0);
}
// Track render time
tracker.markRenderComplete();
return Component(props);
};
}
/**
* Performance measurement decorator for functions
*/
export function measurePerformance(target, propertyName, descriptor) {
const method = descriptor.value;
descriptor.value = function (...args) {
const startTime = performance.now();
const result = method.apply(this, args);
const endTime = performance.now();
if (process.env.NODE_ENV === 'development') {
console.log(`[Performance] ${target.constructor.name}.${propertyName}: ${(endTime - startTime).toFixed(2)}ms`);
}
return result;
};
return descriptor;
}
/**
* Bundle size analyzer (for build-time analysis)
*/
export class BundleSizeAnalyzer {
static analyzeBundleSize(bundlePath) {
// This would be implemented as a build-time tool
// For now, return mock data structure
return {
totalSize: 0,
gzippedSize: 0,
chunks: []
};
}
static generateSizeReport(metrics) {
let report = '# Bundle Size Report\n\n';
report += `**Total Size**: ${(metrics.totalSize / 1024).toFixed(2)} KB\n`;
report += `**Gzipped Size**: ${(metrics.gzippedSize / 1024).toFixed(2)} KB\n\n`;
if (metrics.chunks.length > 0) {
report += '## Chunks\n\n';
metrics.chunks
.sort((a, b) => b.size - a.size)
.forEach(chunk => {
report += `- **${chunk.name}**: ${(chunk.size / 1024).toFixed(2)} KB (${(chunk.gzippedSize / 1024).toFixed(2)} KB gzipped)\n`;
});
}
return report;
}
}
// Cleanup on page unload
if (typeof window !== 'undefined') {
window.addEventListener('beforeunload', () => {
performanceMonitor.destroy();
});
}