// Performance optimization utility for reducing long tasks and improving frame rate import React from 'react'; interface PerformanceMetrics { longTaskCount: number; averageLongTaskDuration: number; frameRate: number; memoryUsage: number; layoutThrashingCount: number; } class PerformanceOptimizer { private longTaskObserver: PerformanceObserver | null = null; private frameRateObserver: PerformanceObserver | null = null; private layoutThrashingCount = 0; private metrics: PerformanceMetrics = { longTaskCount: 0, averageLongTaskDuration: 0, frameRate: 0, memoryUsage: 0, layoutThrashingCount: 0, }; constructor() { this.initializeObservers(); } private initializeObservers() { // Monitor long tasks if ('PerformanceObserver' in window) { this.longTaskObserver = new PerformanceObserver(list => { for (const entry of list.getEntries()) { if (entry.entryType === 'longtask') { this.metrics.longTaskCount++; this.metrics.averageLongTaskDuration = (this.metrics.averageLongTaskDuration * (this.metrics.longTaskCount - 1) + entry.duration) / this.metrics.longTaskCount; console.warn( `🚨 Long task detected: ${entry.duration.toFixed(2)}ms - Consider chunking this operation` ); } } }); this.longTaskObserver.observe({ entryTypes: ['longtask'] }); } // Monitor frame rate this.startFrameRateMonitoring(); } private startFrameRateMonitoring() { let frameCount = 0; let lastTime = performance.now(); const measureFrameRate = () => { frameCount++; const currentTime = performance.now(); if (currentTime - lastTime >= 1000) { this.metrics.frameRate = frameCount; frameCount = 0; lastTime = currentTime; if (this.metrics.frameRate < 30) { console.warn(`⚠️ Low frame rate detected: ${this.metrics.frameRate}fps`); } } requestAnimationFrame(measureFrameRate); }; requestAnimationFrame(measureFrameRate); } // Chunk large operations to prevent long tasks static chunkOperation( items: T[], operation: (item: T, index: number) => void, chunkSize: number = 10, delay: number = 16 ): Promise { return new Promise(resolve => { let index = 0; const processChunk = () => { const startTime = performance.now(); const endIndex = Math.min(index + chunkSize, items.length); // Process items in this chunk for (let i = index; i < endIndex; i++) { operation(items[i], i); } index = endIndex; // Check if we need to yield to prevent long tasks const processingTime = performance.now() - startTime; if (processingTime > 16) { console.warn( `⚠️ Chunk processing took ${processingTime.toFixed(2)}ms - consider smaller chunks` ); } if (index < items.length) { // Schedule next chunk with delay to prevent blocking setTimeout(processChunk, delay); } else { resolve(); } }; processChunk(); }); } // Optimize DOM operations to prevent layout thrashing static batchDOMOperations(operations: (() => void)[]): void { // Use requestAnimationFrame to batch DOM updates requestAnimationFrame(() => { // Force layout read first document.body.offsetHeight; // Perform all write operations operations.forEach(operation => operation()); }); } // Debounce function for expensive operations static debounce void>(func: T, delay: number): T { let timeoutId: NodeJS.Timeout; return ((...args: any[]) => { clearTimeout(timeoutId); timeoutId = setTimeout(() => func(...args), delay); }) as T; } // Throttle function for frequent operations static throttle void>(func: T, delay: number): T { let lastExecTime = 0; return ((...args: any[]) => { const currentTime = performance.now(); if (currentTime - lastExecTime > delay) { func(...args); lastExecTime = currentTime; } }) as T; } // Optimize list rendering with virtualization hints static optimizeListRendering( items: T[], renderItem: (item: T, index: number) => React.ReactNode, options: { chunkSize?: number; virtualizationThreshold?: number; overscanCount?: number; } = {} ): React.ReactNode[] { const { chunkSize = 50, virtualizationThreshold = 100, overscanCount = 5 } = options; // For small lists, render everything if (items.length <= virtualizationThreshold) { return items.map((item, index) => renderItem(item, index)); } // For large lists, use chunked rendering const chunks: React.ReactNode[] = []; for (let i = 0; i < items.length; i += chunkSize) { const chunk = items.slice(i, i + chunkSize); chunks.push( React.createElement( 'div', { key: `chunk-${i}`, className: 'virtualized-chunk' }, chunk.map((item, index) => renderItem(item, i + index)) ) ); } return chunks; } // Monitor memory usage static getMemoryUsage(): { used: number; total: number; limit: number } { if ('memory' in performance) { const memory = (performance as any).memory; return { used: memory.usedJSHeapSize / 1024 / 1024, total: memory.totalJSHeapSize / 1024 / 1024, limit: memory.jsHeapSizeLimit / 1024 / 1024, }; } return { used: 0, total: 0, limit: 0 }; } // Optimize scroll performance static optimizeScroll(container: HTMLElement, handler: (event: Event) => void): () => void { let ticking = false; const optimizedHandler = (event: Event) => { if (!ticking) { requestAnimationFrame(() => { handler(event); ticking = false; }); ticking = true; } }; container.addEventListener('scroll', optimizedHandler, { passive: true }); return () => { container.removeEventListener('scroll', optimizedHandler); }; } // Optimize hover performance static optimizeHover( element: HTMLElement, onEnter: () => void, onLeave: () => void, delay: number = 50 ): () => void { let enterTimeout: NodeJS.Timeout; let leaveTimeout: NodeJS.Timeout; const handleMouseEnter = () => { clearTimeout(leaveTimeout); enterTimeout = setTimeout(onEnter, delay); }; const handleMouseLeave = () => { clearTimeout(enterTimeout); leaveTimeout = setTimeout(onLeave, delay); }; element.addEventListener('mouseenter', handleMouseEnter, { passive: true }); element.addEventListener('mouseleave', handleMouseLeave, { passive: true }); return () => { clearTimeout(enterTimeout); clearTimeout(leaveTimeout); element.removeEventListener('mouseenter', handleMouseEnter); element.removeEventListener('mouseleave', handleMouseLeave); }; } // Get current performance metrics getMetrics(): PerformanceMetrics { const memory = PerformanceOptimizer.getMemoryUsage(); this.metrics.memoryUsage = memory.used; this.metrics.layoutThrashingCount = this.layoutThrashingCount; return { ...this.metrics }; } // Reset metrics resetMetrics(): void { this.metrics = { longTaskCount: 0, averageLongTaskDuration: 0, frameRate: 0, memoryUsage: 0, layoutThrashingCount: 0, }; } // Cleanup observers destroy(): void { if (this.longTaskObserver) { this.longTaskObserver.disconnect(); } if (this.frameRateObserver) { this.frameRateObserver.disconnect(); } } } // Create global instance const performanceOptimizer = new PerformanceOptimizer(); // Auto-cleanup on page unload window.addEventListener('beforeunload', () => { performanceOptimizer.destroy(); }); export { PerformanceOptimizer, performanceOptimizer }; export default PerformanceOptimizer;