@moontra/moonui-pro
Version:
Premium React components for MoonUI - Advanced UI library with 50+ pro components including performance, interactive, and gesture components
389 lines (333 loc) • 11.7 kB
text/typescript
"use client";
import * as React from "react";
// Performance profiler implementation
const performanceProfiler = {
getPerformanceSummary: () => ({}),
getMeasurements: (type: string) => [] as any[],
measureComponent: (name: string, callback: () => void) => {
const start = performance.now();
callback();
const duration = performance.now() - start;
return { name, duration };
}
};
// Performance optimization hooks
export function usePerformanceOptimizer() {
const [optimizations, setOptimizations] = React.useState<OptimizationSuggestion[]>([]);
const [isAnalyzing, setIsAnalyzing] = React.useState(false);
const analyzePerformance = React.useCallback(async () => {
setIsAnalyzing(true);
try {
const metrics = performanceProfiler.getPerformanceSummary();
const suggestions: OptimizationSuggestion[] = [];
// Analyze component render times
const componentMetrics = performanceProfiler.getMeasurements('component');
if (componentMetrics.length > 0) {
const slowComponents = componentMetrics
.filter(m => m.duration > 16.67) // Slower than 60fps
.sort((a, b) => b.duration - a.duration)
.slice(0, 5);
slowComponents.forEach(component => {
suggestions.push({
type: 'component',
severity: component.duration > 50 ? 'high' : 'medium',
title: `Slow component: ${component.name}`,
description: `Component takes ${component.duration.toFixed(2)}ms to render`,
solution: 'Consider using React.memo, useMemo, or useCallback',
impact: 'high',
});
});
}
// Analyze memory usage
if ('memory' in performance) {
const memory = (performance as any).memory;
const usagePercent = (memory.usedJSHeapSize / memory.totalJSHeapSize) * 100;
if (usagePercent > 80) {
suggestions.push({
type: 'memory',
severity: 'high',
title: 'High memory usage',
description: `Memory usage is at ${usagePercent.toFixed(1)}%`,
solution: 'Review for memory leaks, optimize data structures, use lazy loading',
impact: 'high',
});
}
}
// Analyze layout shifts
const layoutShifts = performanceProfiler.getMeasurements('layout-shift');
if (layoutShifts.length > 0) {
const totalShift = layoutShifts.reduce((sum, shift) => sum + (shift.value || 0), 0);
if (totalShift > 0.1) {
suggestions.push({
type: 'layout',
severity: 'medium',
title: 'Layout shifts detected',
description: `Cumulative Layout Shift: ${totalShift.toFixed(3)}`,
solution: 'Add dimensions to images, reserve space for dynamic content',
impact: 'medium',
});
}
}
// Analyze bundle size (if available)
if (typeof window !== 'undefined' && window.performance) {
const resources = performance.getEntriesByType('resource') as PerformanceResourceTiming[];
const jsResources = resources.filter(r => r.name.includes('.js'));
const largeResources = jsResources.filter(r => r.transferSize > 100000); // >100KB
if (largeResources.length > 0) {
suggestions.push({
type: 'bundle',
severity: 'medium',
title: 'Large JavaScript bundles',
description: `${largeResources.length} JavaScript files exceed 100KB`,
solution: 'Implement code splitting, lazy loading, and tree shaking',
impact: 'medium',
});
}
}
setOptimizations(suggestions);
} catch (error) {
console.error('Performance analysis failed:', error);
} finally {
setIsAnalyzing(false);
}
}, []);
return {
optimizations,
isAnalyzing,
analyzePerformance,
};
}
// Hook for render optimization
export function useRenderOptimization<T>(
value: T,
dependencies: React.DependencyList
): T {
const memoizedValue = React.useMemo(() => value, dependencies);
React.useEffect(() => {
if (process.env.NODE_ENV === 'development') {
const componentName = 'useRenderOptimization';
performanceProfiler.measureComponent(componentName, () => {
// Measure the impact of memoization
});
}
}, dependencies);
return memoizedValue;
}
// Hook for debounced performance tracking
export function useDebouncePerformance<T extends (...args: any[]) => void>(
callback: T,
delay: number = 300
): T {
const timeoutRef = React.useRef<NodeJS.Timeout>();
const debouncedCallback = React.useCallback(
(...args: Parameters<T>) => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
performanceProfiler.measureComponent('debounced-callback', () => {
callback(...args);
});
}, delay);
},
[callback, delay]
) as T;
React.useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
return debouncedCallback;
}
// Hook for throttled performance tracking
export function useThrottlePerformance<T extends (...args: any[]) => void>(
callback: T,
delay: number = 100
): T {
const lastCallRef = React.useRef<number>(0);
const throttledCallback = React.useCallback(
(...args: Parameters<T>) => {
const now = Date.now();
if (now - lastCallRef.current >= delay) {
lastCallRef.current = now;
performanceProfiler.measureComponent('throttled-callback', () => {
callback(...args);
});
}
},
[callback, delay]
) as T;
return throttledCallback;
}
// Hook for measuring component lifecycle
export function useComponentLifecycleTracking(componentName: string) {
const mountTime = React.useRef<number>(0);
const updateCount = React.useRef<number>(0);
React.useEffect(() => {
// Component mount
mountTime.current = performance.now();
performanceProfiler.measureComponent(`${componentName}-mount`, () => {
// Measure mount time
});
return () => {
// Component unmount
const lifetime = performance.now() - mountTime.current;
performanceProfiler.measureComponent(`${componentName}-unmount`, () => {
// Record component lifetime
});
};
}, [componentName]);
React.useEffect(() => {
// Component update
updateCount.current++;
if (updateCount.current > 1) {
performanceProfiler.measureComponent(`${componentName}-update`, () => {
// Measure update time
});
}
});
return {
mountTime: mountTime.current,
updateCount: updateCount.current,
};
}
// Hook for virtual scrolling optimization
export function useVirtualScrolling<T>(
items: T[],
itemHeight: number,
containerHeight: number,
overscan: number = 3
) {
const [scrollTop, setScrollTop] = React.useState(0);
const visibleRange = React.useMemo(() => {
const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan);
const endIndex = Math.min(
items.length - 1,
Math.ceil((scrollTop + containerHeight) / itemHeight) + overscan
);
return { startIndex, endIndex };
}, [scrollTop, itemHeight, containerHeight, overscan, items.length]);
const visibleItems = React.useMemo(() => {
return items.slice(visibleRange.startIndex, visibleRange.endIndex + 1);
}, [items, visibleRange]);
const totalHeight = items.length * itemHeight;
const offsetY = visibleRange.startIndex * itemHeight;
return {
visibleItems,
totalHeight,
offsetY,
startIndex: visibleRange.startIndex,
endIndex: visibleRange.endIndex,
setScrollTop,
};
}
// Hook for image lazy loading optimization
export function useLazyImageOptimization(src: string, threshold: number = 0.1) {
const [isLoaded, setIsLoaded] = React.useState(false);
const [isIntersecting, setIsIntersecting] = React.useState(false);
const imgRef = React.useRef<HTMLImageElement>(null);
React.useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsIntersecting(true);
// Measure image load time
const startTime = performance.now();
const img = new Image();
img.onload = () => {
const loadTime = performance.now() - startTime;
performanceProfiler.measureComponent('image-load', () => {
// Record image load time
});
setIsLoaded(true);
};
img.src = src;
observer.disconnect();
}
},
{ threshold }
);
if (imgRef.current) {
observer.observe(imgRef.current);
}
return () => observer.disconnect();
}, [src, threshold]);
return {
imgRef,
isLoaded,
isIntersecting,
shouldLoad: isIntersecting,
};
}
// Performance optimization interfaces
interface OptimizationSuggestion {
type: 'component' | 'memory' | 'layout' | 'bundle' | 'network';
severity: 'low' | 'medium' | 'high';
title: string;
description: string;
solution: string;
impact: 'low' | 'medium' | 'high';
}
// Hook for automatic performance optimization
export function useAutoPerformanceOptimization() {
const [isOptimizing, setIsOptimizing] = React.useState(false);
const [optimizations, setOptimizations] = React.useState<string[]>([]);
const applyOptimizations = React.useCallback(async () => {
setIsOptimizing(true);
const applied: string[] = [];
try {
// Apply automatic optimizations
// 1. Prefetch critical resources
const criticalResources = document.querySelectorAll('link[rel="preload"]');
if (criticalResources.length === 0) {
// Add preload hints for critical resources
const cssLinks = document.querySelectorAll('link[rel="stylesheet"]');
cssLinks.forEach(link => {
const preloadLink = document.createElement('link');
preloadLink.rel = 'preload';
preloadLink.href = (link as HTMLLinkElement).href;
preloadLink.as = 'style';
document.head.appendChild(preloadLink);
});
applied.push('Added preload hints for CSS');
}
// 2. Optimize images
const images = document.querySelectorAll('img:not([loading])');
images.forEach(img => {
(img as HTMLImageElement).loading = 'lazy';
});
if (images.length > 0) {
applied.push(`Applied lazy loading to ${images.length} images`);
}
// 3. Add intersection observers for below-fold content
const belowFoldElements = document.querySelectorAll('[data-optimize="lazy"]');
belowFoldElements.forEach(element => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
element.classList.add('optimized');
observer.disconnect();
}
},
{ threshold: 0.1 }
);
observer.observe(element);
});
if (belowFoldElements.length > 0) {
applied.push(`Added intersection observers to ${belowFoldElements.length} elements`);
}
setOptimizations(applied);
} catch (error) {
console.error('Auto-optimization failed:', error);
} finally {
setIsOptimizing(false);
}
}, []);
return {
isOptimizing,
optimizations,
applyOptimizations,
};
}