feat(performance): implement comprehensive performance improvements for Worklenz frontend
- Introduced a new document summarizing performance optimizations across the application. - Applied React.memo(), useMemo(), and useCallback() to key components to minimize unnecessary re-renders and optimize rendering performance. - Implemented a route preloading system to enhance navigation speed and user experience. - Added performance monitoring utilities for development to track component render times and function execution. - Enhanced lazy loading and suspense boundaries for better loading states. - Conducted production optimizations, including TypeScript error fixes and memory management improvements. - Memoized style and configuration objects to reduce garbage collection pressure and improve memory usage.
This commit is contained in:
181
worklenz-frontend/src/utils/routePreloader.ts
Normal file
181
worklenz-frontend/src/utils/routePreloader.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
import React from 'react';
|
||||
|
||||
/**
|
||||
* Route preloader utility to prefetch components and improve navigation performance
|
||||
*/
|
||||
|
||||
interface PreloadableRoute {
|
||||
path: string;
|
||||
loader: () => Promise<any>;
|
||||
priority: 'high' | 'medium' | 'low';
|
||||
}
|
||||
|
||||
class RoutePreloader {
|
||||
private preloadedRoutes = new Set<string>();
|
||||
private preloadQueue: PreloadableRoute[] = [];
|
||||
private isPreloading = false;
|
||||
|
||||
/**
|
||||
* Register a route for preloading
|
||||
*/
|
||||
public registerRoute(path: string, loader: () => Promise<any>, priority: 'high' | 'medium' | 'low' = 'medium'): void {
|
||||
if (this.preloadedRoutes.has(path)) return;
|
||||
|
||||
this.preloadQueue.push({ path, loader, priority });
|
||||
this.sortQueue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Preload a specific route immediately
|
||||
*/
|
||||
public async preloadRoute(path: string, loader: () => Promise<any>): Promise<void> {
|
||||
if (this.preloadedRoutes.has(path)) return;
|
||||
|
||||
try {
|
||||
await loader();
|
||||
this.preloadedRoutes.add(path);
|
||||
} catch (error) {
|
||||
console.warn(`Failed to preload route: ${path}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start preloading routes in the queue
|
||||
*/
|
||||
public async startPreloading(): Promise<void> {
|
||||
if (this.isPreloading || this.preloadQueue.length === 0) return;
|
||||
|
||||
this.isPreloading = true;
|
||||
|
||||
// Use requestIdleCallback if available, otherwise setTimeout
|
||||
const scheduleWork = (callback: () => void) => {
|
||||
if ('requestIdleCallback' in window) {
|
||||
requestIdleCallback(callback, { timeout: 1000 });
|
||||
} else {
|
||||
setTimeout(callback, 0);
|
||||
}
|
||||
};
|
||||
|
||||
const processQueue = async () => {
|
||||
while (this.preloadQueue.length > 0) {
|
||||
const route = this.preloadQueue.shift();
|
||||
if (!route) break;
|
||||
|
||||
if (this.preloadedRoutes.has(route.path)) continue;
|
||||
|
||||
try {
|
||||
await route.loader();
|
||||
this.preloadedRoutes.add(route.path);
|
||||
} catch (error) {
|
||||
console.warn(`Failed to preload route: ${route.path}`, error);
|
||||
}
|
||||
|
||||
// Yield control back to the browser
|
||||
await new Promise<void>(resolve => scheduleWork(() => resolve()));
|
||||
}
|
||||
|
||||
this.isPreloading = false;
|
||||
};
|
||||
|
||||
scheduleWork(processQueue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Preload routes on user interaction (hover, focus)
|
||||
*/
|
||||
public preloadOnInteraction(element: HTMLElement, path: string, loader: () => Promise<any>): void {
|
||||
if (this.preloadedRoutes.has(path)) return;
|
||||
|
||||
let preloadTriggered = false;
|
||||
|
||||
const handleInteraction = () => {
|
||||
if (preloadTriggered) return;
|
||||
preloadTriggered = true;
|
||||
|
||||
this.preloadRoute(path, loader);
|
||||
|
||||
// Clean up listeners
|
||||
element.removeEventListener('mouseenter', handleInteraction);
|
||||
element.removeEventListener('focus', handleInteraction);
|
||||
element.removeEventListener('touchstart', handleInteraction);
|
||||
};
|
||||
|
||||
element.addEventListener('mouseenter', handleInteraction, { passive: true });
|
||||
element.addEventListener('focus', handleInteraction, { passive: true });
|
||||
element.addEventListener('touchstart', handleInteraction, { passive: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Preload routes when the browser is idle
|
||||
*/
|
||||
public preloadOnIdle(): void {
|
||||
if ('requestIdleCallback' in window) {
|
||||
requestIdleCallback(() => {
|
||||
this.startPreloading();
|
||||
}, { timeout: 2000 });
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
this.startPreloading();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a route is already preloaded
|
||||
*/
|
||||
public isRoutePreloaded(path: string): boolean {
|
||||
return this.preloadedRoutes.has(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all preloaded routes
|
||||
*/
|
||||
public clearPreloaded(): void {
|
||||
this.preloadedRoutes.clear();
|
||||
this.preloadQueue = [];
|
||||
}
|
||||
|
||||
private sortQueue(): void {
|
||||
const priorityOrder = { high: 0, medium: 1, low: 2 };
|
||||
this.preloadQueue.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
|
||||
}
|
||||
}
|
||||
|
||||
// Create default instance
|
||||
export const routePreloader = new RoutePreloader();
|
||||
|
||||
/**
|
||||
* React hook to preload routes on component mount
|
||||
*/
|
||||
export function useRoutePreloader(routes: Array<{ path: string; loader: () => Promise<any>; priority?: 'high' | 'medium' | 'low' }>): void {
|
||||
React.useEffect(() => {
|
||||
routes.forEach(route => {
|
||||
routePreloader.registerRoute(route.path, route.loader, route.priority);
|
||||
});
|
||||
|
||||
// Start preloading after a short delay to not interfere with initial render
|
||||
const timer = setTimeout(() => {
|
||||
routePreloader.preloadOnIdle();
|
||||
}, 100);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [routes]);
|
||||
}
|
||||
|
||||
/**
|
||||
* React hook to preload a route on element interaction
|
||||
*/
|
||||
export function usePreloadOnHover(path: string, loader: () => Promise<any>) {
|
||||
const elementRef = React.useRef<HTMLElement>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
const element = elementRef.current;
|
||||
if (!element) return;
|
||||
|
||||
routePreloader.preloadOnInteraction(element, path, loader);
|
||||
}, [path, loader]);
|
||||
|
||||
return elementRef;
|
||||
}
|
||||
|
||||
export default routePreloader;
|
||||
Reference in New Issue
Block a user