Files
worklenz/worklenz-frontend/src/utils/performance-optimizer.ts
2025-07-03 01:31:05 +05:30

296 lines
8.0 KiB
TypeScript

// 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<T>(
items: T[],
operation: (item: T, index: number) => void,
chunkSize: number = 10,
delay: number = 16
): Promise<void> {
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<T extends (...args: any[]) => 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<T extends (...args: any[]) => 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<T>(
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;