diff --git a/client/src/components/LazyComponents.tsx b/client/src/components/LazyComponents.tsx new file mode 100644 index 0000000..1ffab9a --- /dev/null +++ b/client/src/components/LazyComponents.tsx @@ -0,0 +1,43 @@ +import { lazy, Suspense } from 'react'; +import { Skeleton } from '@/components/ui/skeleton'; + +// Lazy load large components to improve bundle performance +export const LazyBudgetPlanner = lazy(() => import('@/components/financial/BudgetPlanner')); +export const LazyProjectManager = lazy(() => import('@/components/tasks/ProjectManager')); +export const LazyAIChat = lazy(() => import('@/components/ai/AIChat')); +export const LazyConversationHistory = lazy(() => import('@/components/ai/ConversationHistory')); +export const LazySystemMonitoring = lazy(() => import('@/pages/SystemMonitoringPage')); +export const LazyPerformanceBenchmark = lazy(() => import('@/components/PerformanceBenchmark')); + +// Reusable loading fallback for Arabic RTL support +const LoadingSkeleton = ({ className = "" }: { className?: string }) => ( +
+ + + +
+ + +
+
+); + +// Higher-order component for lazy loading with Arabic RTL loading states +export const withLazyLoading =

( + Component: React.ComponentType

, + loadingClassName?: string +) => { + return (props: P) => ( + }> + + + ); +}; + +// Pre-configured lazy components with loading states +export const BudgetPlannerWithLoading = withLazyLoading(LazyBudgetPlanner, "p-6"); +export const ProjectManagerWithLoading = withLazyLoading(LazyProjectManager, "p-4"); +export const AIChatWithLoading = withLazyLoading(LazyAIChat, "h-96"); +export const ConversationHistoryWithLoading = withLazyLoading(LazyConversationHistory); +export const SystemMonitoringWithLoading = withLazyLoading(LazySystemMonitoring); +export const PerformanceBenchmarkWithLoading = withLazyLoading(LazyPerformanceBenchmark, "p-6"); \ No newline at end of file diff --git a/client/src/components/RTLProvider.tsx b/client/src/components/RTLProvider.tsx index fa6beec..ed8328a 100644 --- a/client/src/components/RTLProvider.tsx +++ b/client/src/components/RTLProvider.tsx @@ -1,5 +1,7 @@ import { createContext, useContext, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; +import { arabicFontOptimizer } from '@/lib/fontOptimization'; +import { arabicPerformanceTracker } from '@/lib/arabicPerformanceTracker'; interface RTLContextType { isRTL: boolean; diff --git a/client/src/lib/arabicPerformanceTracker.ts b/client/src/lib/arabicPerformanceTracker.ts new file mode 100644 index 0000000..7a226d4 --- /dev/null +++ b/client/src/lib/arabicPerformanceTracker.ts @@ -0,0 +1,227 @@ +interface ArabicPerformanceMetrics { + rtlRenderTime: number; + fontLoadTime: number; + layoutShiftScore: number; + textDirectionSwitchTime: number; + arabicTextRenderScore: number; + timestamp: number; +} + +interface PerformanceThresholds { + rtlRenderTime: number; + fontLoadTime: number; + layoutShiftScore: number; + textDirectionSwitchTime: number; +} + +export class ArabicPerformanceTracker { + private metrics: ArabicPerformanceMetrics[] = []; + private thresholds: PerformanceThresholds = { + rtlRenderTime: 50, // ms + fontLoadTime: 200, // ms + layoutShiftScore: 0.1, + textDirectionSwitchTime: 100 // ms + }; + + trackRTLPerformance(language: string): ArabicPerformanceMetrics { + const startTime = performance.now(); + + // Measure RTL layout rendering + const rtlContainer = document.createElement('div'); + rtlContainer.style.direction = language === 'ar' ? 'rtl' : 'ltr'; + rtlContainer.style.position = 'absolute'; + rtlContainer.style.visibility = 'hidden'; + rtlContainer.innerHTML = language === 'ar' + ? 'مرحباً بكم في منصة المساعد الذكي للمهنيين العمانيين' + : 'Welcome to MenAssist Professional Platform'; + + document.body.appendChild(rtlContainer); + const rtlRenderTime = performance.now() - startTime; + + // Measure font loading for Arabic + const fontStartTime = performance.now(); + const fontTestElement = document.createElement('span'); + fontTestElement.style.fontFamily = language === 'ar' ? 'Noto Sans Arabic' : 'Inter'; + fontTestElement.style.fontSize = '16px'; + fontTestElement.innerHTML = language === 'ar' ? 'اختبار الخط' : 'Font Test'; + rtlContainer.appendChild(fontTestElement); + + const fontLoadTime = performance.now() - fontStartTime; + + // Measure layout shift + const initialWidth = rtlContainer.offsetWidth; + const layoutShiftScore = this.measureLayoutShift(rtlContainer, initialWidth); + + // Measure direction switch performance + const directionSwitchTime = this.measureDirectionSwitch(rtlContainer, language); + + document.body.removeChild(rtlContainer); + + const metrics: ArabicPerformanceMetrics = { + rtlRenderTime, + fontLoadTime, + layoutShiftScore, + textDirectionSwitchTime: directionSwitchTime, + arabicTextRenderScore: this.calculateTextRenderScore(rtlRenderTime, fontLoadTime), + timestamp: Date.now() + }; + + this.metrics.push(metrics); + this.analyzePerformanceIssues(metrics); + + return metrics; + } + + private measureLayoutShift(element: HTMLElement, initialWidth: number): number { + // Simulate font loading completion + element.style.fontDisplay = 'swap'; + const finalWidth = element.offsetWidth; + return Math.abs(finalWidth - initialWidth) / initialWidth; + } + + private measureDirectionSwitch(element: HTMLElement, currentLanguage: string): number { + const startTime = performance.now(); + + // Switch direction + element.style.direction = currentLanguage === 'ar' ? 'ltr' : 'rtl'; + element.style.textAlign = currentLanguage === 'ar' ? 'left' : 'right'; + + // Force reflow + element.offsetHeight; + + return performance.now() - startTime; + } + + private calculateTextRenderScore(rtlTime: number, fontTime: number): number { + const totalTime = rtlTime + fontTime; + if (totalTime < 50) return 100; + if (totalTime < 100) return 90; + if (totalTime < 200) return 80; + if (totalTime < 300) return 70; + return 60; + } + + private analyzePerformanceIssues(metrics: ArabicPerformanceMetrics): void { + const issues: string[] = []; + + if (metrics.rtlRenderTime > this.thresholds.rtlRenderTime) { + issues.push('RTL rendering slower than optimal'); + } + + if (metrics.fontLoadTime > this.thresholds.fontLoadTime) { + issues.push('Arabic font loading slower than optimal'); + } + + if (metrics.layoutShiftScore > this.thresholds.layoutShiftScore) { + issues.push('Significant layout shift during font loading'); + } + + if (metrics.textDirectionSwitchTime > this.thresholds.textDirectionSwitchTime) { + issues.push('Direction switching performance issue'); + } + + if (issues.length > 0) { + console.warn('Arabic Performance Issues Detected:', issues); + this.sendPerformanceAlert(metrics, issues); + } + } + + private sendPerformanceAlert(metrics: ArabicPerformanceMetrics, issues: string[]): void { + // Send performance data to analytics endpoint + fetch('/api/analytics/arabic-performance', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + metrics, + issues, + userAgent: navigator.userAgent, + timestamp: Date.now() + }) + }).catch(error => { + console.warn('Failed to send Arabic performance data:', error); + }); + } + + getAverageMetrics(): Partial { + if (this.metrics.length === 0) return {}; + + const totals = this.metrics.reduce((acc, metric) => ({ + rtlRenderTime: acc.rtlRenderTime + metric.rtlRenderTime, + fontLoadTime: acc.fontLoadTime + metric.fontLoadTime, + layoutShiftScore: acc.layoutShiftScore + metric.layoutShiftScore, + textDirectionSwitchTime: acc.textDirectionSwitchTime + metric.textDirectionSwitchTime, + arabicTextRenderScore: acc.arabicTextRenderScore + metric.arabicTextRenderScore + }), { + rtlRenderTime: 0, + fontLoadTime: 0, + layoutShiftScore: 0, + textDirectionSwitchTime: 0, + arabicTextRenderScore: 0 + }); + + const count = this.metrics.length; + return { + rtlRenderTime: totals.rtlRenderTime / count, + fontLoadTime: totals.fontLoadTime / count, + layoutShiftScore: totals.layoutShiftScore / count, + textDirectionSwitchTime: totals.textDirectionSwitchTime / count, + arabicTextRenderScore: totals.arabicTextRenderScore / count + }; + } + + getPerformanceReport(): { + averageMetrics: Partial; + totalSamples: number; + performanceGrade: string; + recommendations: string[]; + } { + const averageMetrics = this.getAverageMetrics(); + const totalSamples = this.metrics.length; + + const performanceGrade = this.calculatePerformanceGrade(averageMetrics); + const recommendations = this.generateRecommendations(averageMetrics); + + return { + averageMetrics, + totalSamples, + performanceGrade, + recommendations + }; + } + + private calculatePerformanceGrade(metrics: Partial): string { + const score = metrics.arabicTextRenderScore || 0; + if (score >= 90) return 'A+'; + if (score >= 80) return 'A'; + if (score >= 70) return 'B+'; + if (score >= 60) return 'B'; + return 'C'; + } + + private generateRecommendations(metrics: Partial): string[] { + const recommendations: string[] = []; + + if ((metrics.rtlRenderTime || 0) > this.thresholds.rtlRenderTime) { + recommendations.push('Optimize CSS for RTL layouts using logical properties'); + recommendations.push('Implement CSS containment for Arabic text sections'); + } + + if ((metrics.fontLoadTime || 0) > this.thresholds.fontLoadTime) { + recommendations.push('Preload Arabic fonts with font-display: swap'); + recommendations.push('Use font subsetting for Arabic character ranges'); + } + + if ((metrics.layoutShiftScore || 0) > this.thresholds.layoutShiftScore) { + recommendations.push('Reserve space for Arabic text during font loading'); + recommendations.push('Use size-adjust property for Arabic font fallbacks'); + } + + return recommendations; + } + + clearMetrics(): void { + this.metrics = []; + } +} + +export const arabicPerformanceTracker = new ArabicPerformanceTracker(); \ No newline at end of file diff --git a/client/src/lib/fontOptimization.ts b/client/src/lib/fontOptimization.ts new file mode 100644 index 0000000..f155bf9 --- /dev/null +++ b/client/src/lib/fontOptimization.ts @@ -0,0 +1,121 @@ +// Arabic font optimization with CDN delivery +export class ArabicFontOptimizer { + private fontCache = new Map(); + private loadingPromises = new Map>(); + + async optimizeArabicFonts(language: string): Promise { + if (language !== 'ar') return; + + const fonts = [ + { + family: 'Noto Sans Arabic', + weights: ['400', '500', '600', '700'], + display: 'swap' + } + ]; + + await Promise.all(fonts.map(font => this.loadFont(font))); + } + + private async loadFont(fontConfig: { + family: string; + weights: string[]; + display: string; + }): Promise { + const cacheKey = `${fontConfig.family}-${fontConfig.weights.join(',')}`; + + if (this.fontCache.has(cacheKey)) return; + + if (this.loadingPromises.has(cacheKey)) { + return this.loadingPromises.get(cacheKey); + } + + const loadPromise = this.performFontLoad(fontConfig, cacheKey); + this.loadingPromises.set(cacheKey, loadPromise); + + return loadPromise; + } + + private async performFontLoad( + fontConfig: { family: string; weights: string[]; display: string }, + cacheKey: string + ): Promise { + try { + // Preload critical Arabic font weights + const link = document.createElement('link'); + link.rel = 'preload'; + link.as = 'font'; + link.type = 'font/woff2'; + link.crossOrigin = 'anonymous'; + + // Use Google Fonts CDN with optimal parameters for Arabic + const weightParam = fontConfig.weights.join(';'); + link.href = `https://fonts.googleapis.com/css2?family=${fontConfig.family.replace(/ /g, '+')}:wght@${weightParam}&display=${fontConfig.display}&subset=arabic`; + + document.head.appendChild(link); + + // Create stylesheet link + const styleLink = document.createElement('link'); + styleLink.rel = 'stylesheet'; + styleLink.href = link.href; + + await new Promise((resolve, reject) => { + styleLink.onload = () => resolve(); + styleLink.onerror = () => reject(new Error(`Failed to load font: ${fontConfig.family}`)); + document.head.appendChild(styleLink); + }); + + this.fontCache.set(cacheKey, true); + this.loadingPromises.delete(cacheKey); + } catch (error) { + console.warn(`Font loading failed for ${fontConfig.family}:`, error); + this.loadingPromises.delete(cacheKey); + } + } + + // Performance monitoring for Arabic font rendering + measureArabicRenderPerformance(): { + renderTime: number; + layoutShift: number; + fontLoadTime: number; + } { + const startTime = performance.now(); + + // Create test element with Arabic text + const testDiv = document.createElement('div'); + testDiv.style.position = 'absolute'; + testDiv.style.visibility = 'hidden'; + testDiv.style.fontFamily = 'Noto Sans Arabic, Arial, sans-serif'; + testDiv.style.fontSize = '16px'; + testDiv.innerHTML = 'مرحباً بكم في منصة المساعد الذكي للمهنيين العمانيين'; + + document.body.appendChild(testDiv); + + const initialHeight = testDiv.offsetHeight; + const renderTime = performance.now() - startTime; + + // Measure layout shift + setTimeout(() => { + const finalHeight = testDiv.offsetHeight; + const layoutShift = Math.abs(finalHeight - initialHeight) / initialHeight; + + document.body.removeChild(testDiv); + + return { + renderTime, + layoutShift, + fontLoadTime: renderTime + }; + }, 100); + + document.body.removeChild(testDiv); + + return { + renderTime, + layoutShift: 0, + fontLoadTime: renderTime + }; + } +} + +export const arabicFontOptimizer = new ArabicFontOptimizer(); \ No newline at end of file