Skip to content

Commit 3263e0f

Browse files
Merge pull request #65 from Richiey1/main
feat: #51 Implement Advanced Performance Optimization
2 parents cfa10ad + 32b0805 commit 3263e0f

File tree

10 files changed

+484
-114
lines changed

10 files changed

+484
-114
lines changed

package-lock.json

Lines changed: 106 additions & 110 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"react-hook-form": "^7.60.0",
3838
"react-hot-toast": "^2.6.0",
3939
"react-icons": "^5.5.0",
40+
"react-intersection-observer": "^10.0.3",
4041
"recharts": "^2.15.4",
4142
"socket.io-client": "^4.8.3",
4243
"tailwind-merge": "^2.6.0",

src/app/layout.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { OfflineModeProvider } from "./context/OfflineModeContext";
66
import { I18nProvider } from "@/hooks/useInternationalization";
77
import { InternationalizationEngine } from "@/components/i18n/InternationalizationEngine";
88
import { CulturalAdaptationManager } from "@/components/i18n/CulturalAdaptationManager";
9+
import PerformanceMonitor from "@/components/performance/PerformanceMonitor";
10+
import PrefetchingEngine from "@/components/performance/PrefetchingEngine";
911

1012
const geistSans = Geist({
1113
variable: "--font-geist-sans",
@@ -37,6 +39,8 @@ export default function RootLayout({
3739
<CulturalAdaptationManager>
3840
<ThemeProvider>
3941
<OfflineModeProvider>
42+
<PerformanceMonitor />
43+
<PrefetchingEngine />
4044
{children}
4145
</OfflineModeProvider>
4246
</ThemeProvider>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
'use client';
2+
3+
import React, { Suspense, useEffect, useState } from 'react';
4+
import { useInView } from 'react-intersection-observer';
5+
6+
interface LazyLoadingManagerProps {
7+
children: React.ReactNode;
8+
fallback?: React.ReactNode;
9+
threshold?: number; // 0 to 1
10+
rootMargin?: string;
11+
componentName?: string;
12+
}
13+
14+
/**
15+
* Intelligent wrapper for lazy loading components when they enter the viewport.
16+
*/
17+
const LazyLoadingManager: React.FC<LazyLoadingManagerProps> = ({
18+
children,
19+
fallback = <div className="animate-pulse bg-gray-200 h-32 w-full rounded-md" />,
20+
threshold = 0.1,
21+
rootMargin = '200px',
22+
componentName = 'Component'
23+
}) => {
24+
const [hasBeenInView, setHasBeenInView] = useState(false);
25+
const { ref, inView } = useInView({
26+
threshold,
27+
rootMargin,
28+
triggerOnce: true
29+
});
30+
31+
useEffect(() => {
32+
if (inView && !hasBeenInView) {
33+
setHasBeenInView(true);
34+
console.log(`[LazyLoading] Triggering load for ${componentName}`);
35+
}
36+
}, [inView, hasBeenInView, componentName]);
37+
38+
return (
39+
<div ref={ref} className="w-full">
40+
{hasBeenInView ? (
41+
<Suspense fallback={fallback}>
42+
{children}
43+
</Suspense>
44+
) : (
45+
fallback
46+
)}
47+
</div>
48+
);
49+
};
50+
51+
export default LazyLoadingManager;
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
'use client';
2+
3+
import React, { useEffect, useState } from 'react';
4+
import { measureWebVitals, PerformanceMetric } from '../../utils/performanceUtils';
5+
6+
/**
7+
* Component to monitor and display performance metrics in real-time.
8+
* In a production environment, this could be hidden or restricted to admin users.
9+
*/
10+
const PerformanceMonitor: React.FC = () => {
11+
const [metrics, setMetrics] = useState<Record<string, PerformanceMetric>>({});
12+
const [isVisible, setIsVisible] = useState(false);
13+
14+
useEffect(() => {
15+
measureWebVitals((metric) => {
16+
setMetrics((prev: Record<string, PerformanceMetric>) => ({
17+
...prev,
18+
[metric.name]: metric,
19+
}));
20+
21+
// Logic for alerts based on thresholds
22+
if (metric.name === 'LCP' && metric.value > 2500) {
23+
console.warn(`[Performance Alert] LCP is high: ${metric.value.toFixed(2)}ms`);
24+
}
25+
if (metric.name === 'FID' && metric.value > 100) {
26+
console.warn(`[Performance Alert] FID is high: ${metric.value.toFixed(2)}ms`);
27+
}
28+
});
29+
}, []);
30+
31+
if (process.env.NODE_ENV === 'production' && !isVisible) return null;
32+
33+
return (
34+
<div className={`fixed bottom-4 right-4 z-50 p-4 rounded-lg bg-black/80 text-white text-xs font-mono shadow-xl transition-opacity ${isVisible ? 'opacity-100' : 'opacity-0 hover:opacity-100'}`}>
35+
<div className="flex justify-between items-center mb-2 border-b border-white/20 pb-1">
36+
<span className="font-bold ">🚀 Performance Monitor</span>
37+
<button onClick={() => setIsVisible(!isVisible)} className="ml-2 hover:text-blue-400">
38+
{isVisible ? 'Hide' : 'Show'}
39+
</button>
40+
</div>
41+
<div className="space-y-1">
42+
{Object.values(metrics).map((metric) => (
43+
<div key={metric.name} className="flex justify-between gap-4">
44+
<span>{metric.name}:</span>
45+
<span className={metric.value > 2000 ? 'text-red-400' : 'text-green-400'}>
46+
{metric.value.toFixed(2)}{metric.label || ''}
47+
</span>
48+
</div>
49+
))}
50+
</div>
51+
</div>
52+
);
53+
};
54+
55+
export default PerformanceMonitor;
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
'use client';
2+
3+
import React, { useCallback, useEffect } from 'react';
4+
import { useRouter } from 'next/navigation';
5+
import { isSlowConnection } from '../../utils/performanceUtils';
6+
7+
interface PrefetchingEngineProps {
8+
strategies?: ('hover' | 'proximity' | 'intent')[];
9+
}
10+
11+
/**
12+
* Engine to predictively prefetch routes based on user behavior and network conditions.
13+
*/
14+
const PrefetchingEngine: React.FC<PrefetchingEngineProps> = ({ strategies = ['hover'] }) => {
15+
const router = useRouter();
16+
17+
const handleIntent = useCallback((href: string) => {
18+
if (isSlowConnection()) {
19+
console.log(`[Prefetching] Slow connection detected. Skipping prefetch for: ${href}`);
20+
return;
21+
}
22+
23+
console.log(`[Prefetching] Predictive prefetch started for: ${href}`);
24+
router.prefetch(href);
25+
}, [router]);
26+
27+
useEffect(() => {
28+
if (!strategies.includes('hover')) return;
29+
30+
const handleMouseOver = (e: MouseEvent) => {
31+
const target = e.target as HTMLElement;
32+
const link = target.closest('a');
33+
34+
if (link && link.href && link.origin === window.location.origin) {
35+
const href = link.pathname;
36+
handleIntent(href);
37+
}
38+
};
39+
40+
document.addEventListener('mouseover', handleMouseOver);
41+
return () => document.removeEventListener('mouseover', handleMouseOver);
42+
}, [strategies, handleIntent]);
43+
44+
// Proximity strategy could be implemented with Intersection Observer on all links
45+
46+
return null; // Background component
47+
};
48+
49+
export default PrefetchingEngine;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
'use client';
2+
3+
import { useEffect, useRef } from 'react';
4+
5+
interface UsePerformanceOptions {
6+
componentName: string;
7+
threshold?: number; // threshold in ms for performance alerts
8+
}
9+
10+
/**
11+
* Hook to monitor component render performance and identify potential bottlenecks.
12+
*/
13+
export const usePerformanceOptimization = ({ componentName, threshold = 16 }: UsePerformanceOptions) => {
14+
const startTime = useRef(performance.now());
15+
16+
useEffect(() => {
17+
const endTime = performance.now();
18+
const duration = endTime - startTime.current;
19+
20+
if (duration > threshold) {
21+
console.warn(`[Performance Warning] Component "${componentName}" took ${duration.toFixed(2)}ms to mount. Threshold is ${threshold}ms.`);
22+
}
23+
24+
// Capture render count for optimization analysis
25+
// In a real scenario, this could be sent to a tracking service
26+
}, [componentName, threshold]);
27+
28+
// Utility to track interaction performance within the component
29+
const trackInteraction = (actionName: string, action: () => void) => {
30+
const start = performance.now();
31+
action();
32+
const end = performance.now();
33+
const duration = end - start;
34+
if (duration > threshold) {
35+
console.warn(`[Performance Warning] Interaction "${actionName}" in "${componentName}" took ${duration.toFixed(2)}ms.`);
36+
}
37+
};
38+
39+
return { trackInteraction };
40+
};

src/services/bundleOptimizer.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/**
2+
* Service for bundle analysis and dynamic loading optimization.
3+
*/
4+
5+
export interface BundleChunk {
6+
id: string;
7+
name: string;
8+
size?: number;
9+
priority: 'high' | 'medium' | 'low';
10+
}
11+
12+
class BundleOptimizer {
13+
private chunks: Map<string, BundleChunk> = new Map();
14+
15+
/**
16+
* Registers a chunk for monitoring.
17+
*/
18+
registerChunk(chunk: BundleChunk) {
19+
this.chunks.set(chunk.id, chunk);
20+
}
21+
22+
/**
23+
* Recommends a loading strategy based on chunk priority and network conditions.
24+
*/
25+
getLoadingStrategy(chunkId: string, isSlow: boolean) {
26+
const chunk = this.chunks.get(chunkId);
27+
if (!chunk) return 'default';
28+
29+
if (isSlow) {
30+
return chunk.priority === 'high' ? 'eager' : 'lazy';
31+
}
32+
33+
return chunk.priority === 'low' ? 'lazy' : 'eager';
34+
}
35+
36+
/**
37+
* Identifies large chunks that might need further code splitting.
38+
*/
39+
analyzeChunkSizes(threshold: number = 200) { // threshold in KB
40+
const heavyChunks = Array.from(this.chunks.values()).filter(
41+
(chunk) => chunk.size && chunk.size > threshold
42+
);
43+
44+
if (heavyChunks.length > 0) {
45+
console.warn(`[Bundle Analysis] Found ${heavyChunks.length} heavy chunks (> ${threshold}KB). Consider further code splitting.`);
46+
heavyChunks.forEach((chunk) => {
47+
console.warn(` - Chunk: ${chunk.name} (${chunk.size}KB)`);
48+
});
49+
}
50+
51+
return heavyChunks;
52+
}
53+
54+
/**
55+
* Performs basic bundle size reporting.
56+
*/
57+
reportBundleHealth() {
58+
const totalChunks = this.chunks.size;
59+
const totalSize = Array.from(this.chunks.values()).reduce((acc, c) => acc + (c.size || 0), 0);
60+
61+
console.log(`[Bundle Optimizer] Monitoring ${totalChunks} logical chunks. Total estimated size: ${totalSize}KB.`);
62+
this.analyzeChunkSizes();
63+
}
64+
}
65+
66+
export const bundleOptimizer = new BundleOptimizer();

src/utils/i18nUtils.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
* i18n Utilities - Helper functions for formatting dates, numbers, currencies, etc.
33
*/
44

5-
import type { LanguageCode, CulturalPreferences, LocaleConfig } from '@/locales/types';
5+
import type { LanguageCode, CulturalPreferences } from '@/locales/types';
66
import { getLocaleConfig } from '@/locales/config';
7-
import { format, formatDistanceToNow, formatRelative, type Locale } from 'date-fns';
7+
import { format, formatDistanceToNow, type Locale } from 'date-fns';
88
import { enUS, es, fr, de, ar, he, ja, zhCN, ptBR, ru, it, ko } from 'date-fns/locale';
99

1010
// Date-fns locale mapping
@@ -181,7 +181,7 @@ export function parseNumber(
181181
const prefs = getCulturalPreferences(language);
182182

183183
// Replace localized separators with standard ones
184-
let normalized = value
184+
const normalized = value
185185
.replace(new RegExp(`\\${prefs.thousandsSeparator}`, 'g'), '')
186186
.replace(new RegExp(`\\${prefs.decimalSeparator}`, 'g'), '.');
187187

@@ -235,7 +235,7 @@ export function formatDuration(
235235
seconds: number,
236236
language: LanguageCode
237237
): string {
238-
const config = getLocaleConfig(language);
238+
getLocaleConfig(language);
239239

240240
const hours = Math.floor(seconds / 3600);
241241
const minutes = Math.floor((seconds % 3600) / 60);

0 commit comments

Comments
 (0)