1 // Performance optimization utilities
2
3 // Preload critical routes and data
4 export function preloadCriticalRoutes() {
5 // Preload the most common pages users visit
6 const criticalImports = [
7 () => import("@/pages/device-finder"),
8 () => import("@/pages/troubleshooting"),
9 () => import("@/pages/repair-guide"),
10 ];
11
12 // Use requestIdleCallback to preload during idle time
13 if ('requestIdleCallback' in window) {
14 criticalImports.forEach((importFn, index) => {
15 window.requestIdleCallback(() => {
16 importFn().catch(() => {}); // Silent fail for preloading
17 }, { timeout: 1000 + (index * 500) });
18 });
19 } else {
20 // Fallback for browsers without requestIdleCallback
21 setTimeout(() => {
22 criticalImports.forEach((importFn) => {
23 importFn().catch(() => {});
24 });
25 }, 2000);
26 }
27 }
28
29 // Optimize images with lazy loading and proper sizing
30 export function setupImageOptimization() {
31 // Add loading="lazy" to all images that don't have it
32 const images = document.querySelectorAll('img:not([loading])');
33 images.forEach((img) => {
34 img.setAttribute('loading', 'lazy');
35 });
36 }
37
38 // Preconnect to external domains for faster resource loading
39 export function setupPreconnects() {
40 const preconnects = [
41 'https://fonts.googleapis.com',
42 'https://fonts.gstatic.com',
43 ];
44
45 preconnects.forEach((url) => {
46 const link = document.createElement('link');
47 link.rel = 'preconnect';
48 link.href = url;
49 link.crossOrigin = 'anonymous';
50 document.head.appendChild(link);
51 });
52 }
53
54 // Setup performance monitoring
55 export function setupPerformanceMonitoring() {
56 // Monitor Core Web Vitals
57 if ('web-vital' in window || typeof window !== 'undefined') {
58 // Track First Contentful Paint
59 const observer = new PerformanceObserver((list) => {
60 for (const entry of list.getEntries()) {
61 if (entry.name === 'first-contentful-paint') {
62 console.log('FCP:', entry.startTime);
63 }
64 }
65 });
66
67 try {
68 observer.observe({ entryTypes: ['paint'] });
69 } catch (e) {
70 // Browser doesn't support this API
71 }
72 }
73 }
74
75 // Debounce function for performance
76 export function debounce<T extends (...args: any[]) => any>(
77 func: T,
78 wait: number
79 ): (...args: Parameters<T>) => void {
80 let timeout: NodeJS.Timeout;
81 return (...args: Parameters<T>) => {
82 clearTimeout(timeout);
83 timeout = setTimeout(() => func(...args), wait);
84 };
85 }
86
87 // Throttle function for performance
88 export function throttle<T extends (...args: any[]) => any>(
89 func: T,
90 limit: number
91 ): (...args: Parameters<T>) => void {
92 let inThrottle = false;
93 return (...args: Parameters<T>) => {
94 if (!inThrottle) {
95 func(...args);
96 inThrottle = true;
97 setTimeout(() => inThrottle = false, limit);
98 }
99 };
100 }
101
102 // Enhanced image optimization with memory management
103 export function optimizeImageLoading() {
104 // Lazy load all images
105 const images = document.querySelectorAll('img');
106
107 const imageObserver = new IntersectionObserver((entries) => {
108 entries.forEach(entry => {
109 if (entry.isIntersecting) {
110 const img = entry.target as HTMLImageElement;
111
112 // Set loading attributes
113 if (!img.getAttribute('loading')) {
114 img.setAttribute('loading', 'lazy');
115 }
116
117 // Optimize image sizing
118 if (!img.style.maxWidth) {
119 img.style.maxWidth = '100%';
120 img.style.height = 'auto';
121 }
122
123 // Add error handling
124 img.onerror = () => {
125 img.style.display = 'none';
126 console.warn(`Failed to load image: ${img.src}`);
127 };
128
129 imageObserver.unobserve(img);
130 }
131 });
132 }, {
133 rootMargin: '50px',
134 threshold: 0.1
135 });
136
137 images.forEach(img => imageObserver.observe(img));
138
139 return () => imageObserver.disconnect();
140 }
141
142 // Memory-efficient list rendering with virtual scrolling
143 export function setupVirtualScrolling(containerRef: HTMLElement, itemHeight: number, totalItems: number) {
144 let startIndex = 0;
145 let endIndex = Math.min(Math.ceil(containerRef.clientHeight / itemHeight) + 5, totalItems);
146
147 const updateVisibleRange = () => {
148 const scrollTop = containerRef.scrollTop;
149 const containerHeight = containerRef.clientHeight;
150
151 startIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - 2);
152 endIndex = Math.min(totalItems, startIndex + Math.ceil(containerHeight / itemHeight) + 5);
153
154 return { startIndex, endIndex };
155 };
156
157 const handleScroll = throttle(() => {
158 updateVisibleRange();
159 // Trigger re-render callback if needed
160 }, 16); // 60fps
161
162 containerRef.addEventListener('scroll', handleScroll, { passive: true });
163
164 return {
165 getVisibleRange: updateVisibleRange,
166 cleanup: () => containerRef.removeEventListener('scroll', handleScroll)
167 };
168 }
169
170 // Memory leak detection and cleanup
171 export function detectMemoryLeaks() {
172 const initialMemory = (performance as any).memory?.usedJSHeapSize;
173 let checkCount = 0;
174
175 const memoryCheckInterval = setInterval(() => {
176 checkCount++;
177
178 if ('memory' in performance) {
179 const currentMemory = (performance as any).memory.usedJSHeapSize;
180 const memoryGrowth = currentMemory - (initialMemory || 0);
181
182 if (memoryGrowth > 50 * 1024 * 1024 && checkCount > 10) { // 50MB growth
183 console.warn('Potential memory leak detected:', {
184 growth: `${Math.round(memoryGrowth / 1024 / 1024)}MB`,
185 current: `${Math.round(currentMemory / 1024 / 1024)}MB`
186 });
187
188 // Suggest cleanup actions
189 console.log('Suggesting cleanup actions:');
190 console.log('- Clear unused query cache');
191 console.log('- Remove old localStorage data');
192 console.log('- Check for unremoved event listeners');
193 }
194 }
195
196 // Stop checking after 30 minutes
197 if (checkCount > 180) {
198 clearInterval(memoryCheckInterval);
199 }
200 }, 10000); // Check every 10 seconds
201
202 return () => clearInterval(memoryCheckInterval);
203 }
204
205 // Optimize bundle loading with intelligent preloading
206 export function optimizeBundleLoading() {
207 // Preload critical chunks during idle time
208 const criticalChunks = [
209 '/assets/repair-guides',
210 '/assets/device-finder',
211 '/assets/troubleshooting'
212 ];
213
214 if ('requestIdleCallback' in window) {
215 window.requestIdleCallback(() => {
216 criticalChunks.forEach((chunk, index) => {
217 setTimeout(() => {
218 const link = document.createElement('link');
219 link.rel = 'preload';
220 link.as = 'script';
221 link.href = chunk;
222 document.head.appendChild(link);
223 }, index * 200);
224 });
225 });
226 }
227 }
228
229 // Resource cleanup on route changes
230 export function cleanupOnRouteChange() {
231 // Cancel pending requests
232 const abortController = new AbortController();
233
234 // Clean up timers
235 const timers: NodeJS.Timeout[] = [];
236
237 const cleanup = () => {
238 abortController.abort();
239 timers.forEach(timer => clearTimeout(timer));
240 timers.length = 0;
241 };
242
243 // Listen for route changes
244 if (typeof window !== 'undefined') {
245 window.addEventListener('beforeunload', cleanup);
246 window.addEventListener('popstate', cleanup);
247 }
248
249 return {
250 addTimer: (timer: NodeJS.Timeout) => timers.push(timer),
251 signal: abortController.signal,
252 cleanup
253 };
254 }