/** * Utilitários para otimização de bundle e carregamento */ // Análise de dependências e sugestões de otimização export interface BundleAnalysis { totalSize: number; chunks: Array<{ name: string; size: number; modules: string[]; }>; duplicatedDependencies: string[]; suggestions: string[]; } // Tree shaking detector export class TreeShakingDetector { private unusedExports = new Set(); private unusedImports = new Set(); analyzeModule(moduleCode: string, imports: string[], exports: string[]): void { // Simular análise de tree shaking const usedImports = this.findUsedImports(moduleCode, imports); const usedExports = this.findUsedExports(moduleCode, exports); imports.forEach(imp => { if (!usedImports.includes(imp)) { this.unusedImports.add(imp); } }); exports.forEach(exp => { if (!usedExports.includes(exp)) { this.unusedExports.add(exp); } }); } private findUsedImports(code: string, imports: string[]): string[] { return imports.filter(imp => code.includes(imp)); } private findUsedExports(code: string, exports: string[]): string[] { return exports.filter(exp => code.includes(exp)); } getUnusedImports(): string[] { return Array.from(this.unusedImports); } getUnusedExports(): string[] { return Array.from(this.unusedExports); } getSuggestions(): string[] { const suggestions: string[] = []; if (this.unusedImports.size > 0) { suggestions.push(`Remover ${this.unusedImports.size} imports não utilizados`); } if (this.unusedExports.size > 0) { suggestions.push(`Remover ${this.unusedExports.size} exports não utilizados`); } return suggestions; } } // Code splitting automático export class CodeSplitter { private routes: Map = new Map(); private components: Map = new Map(); registerRoute(path: string, components: string[]): void { this.routes.set(path, components); // Contar uso de componentes components.forEach(comp => { this.components.set(comp, (this.components.get(comp) || 0) + 1); }); } getCommonComponents(): string[] { return Array.from(this.components.entries()) .filter(([_, count]) => count > 1) .map(([comp]) => comp); } generateSplitSuggestions(): Record { const suggestions: Record = { 'vendor': ['react', 'react-dom', 'axios'], 'common': this.getCommonComponents(), 'routes': [] }; // Sugerir split por rota this.routes.forEach((components, route) => { if (components.length > 3) { suggestions.routes.push(`Split route ${route}: ${components.join(', ')}`); } }); return suggestions; } } // Preloading inteligente export class IntelligentPreloader { private preloadQueue: Array<() => Promise> = []; private preloadCache = new Set(); private isPreloading = false; // Registrar componente para preload register(key: string, loader: () => Promise): void { if (!this.preloadCache.has(key)) { this.preloadQueue.push(async () => { try { await loader(); this.preloadCache.add(key); } catch (error) { console.warn(`Preload failed for ${key}:`, error); } }); } } // Executar preload com base na prioridade async preloadByPriority(priorities: string[]): Promise { if (this.isPreloading) return; this.isPreloading = true; try { // Preload com requestIdleCallback se disponível if ('requestIdleCallback' in window) { await this.preloadWithIdleCallback(priorities); } else { await this.preloadWithTimeout(priorities); } } finally { this.isPreloading = false; } } private async preloadWithIdleCallback(priorities: string[]): Promise { return new Promise((resolve) => { const processQueue = async () => { if (this.preloadQueue.length === 0) { resolve(); return; } const loader = this.preloadQueue.shift()!; await loader(); if (this.preloadQueue.length > 0) { (window as any).requestIdleCallback(processQueue, { timeout: 5000 }); } else { resolve(); } }; (window as any).requestIdleCallback(processQueue, { timeout: 5000 }); }); } private async preloadWithTimeout(priorities: string[]): Promise { for (const loader of this.preloadQueue) { await new Promise(resolve => setTimeout(resolve, 10)); await loader(); } this.preloadQueue = []; } // Preload baseado em interação do usuário preloadOnHover(element: HTMLElement, key: string, loader: () => Promise): () => void { let timeoutId: NodeJS.Timeout; const handleMouseEnter = () => { timeoutId = setTimeout(() => { this.register(key, loader); this.preloadByPriority([key]); }, 150); // Pequeno delay para evitar preloads desnecessários }; const handleMouseLeave = () => { if (timeoutId) clearTimeout(timeoutId); }; element.addEventListener('mouseenter', handleMouseEnter); element.addEventListener('mouseleave', handleMouseLeave); // Cleanup return () => { element.removeEventListener('mouseenter', handleMouseEnter); element.removeEventListener('mouseleave', handleMouseLeave); if (timeoutId) clearTimeout(timeoutId); }; } getPreloadStatus(): { cached: number; pending: number } { return { cached: this.preloadCache.size, pending: this.preloadQueue.length }; } } // Resource hints para otimização export class ResourceHintsManager { private addedHints = new Set(); // Adicionar preload para recursos críticos preloadResource(href: string, as: string, crossorigin?: boolean): void { const key = `preload-${href}`; if (this.addedHints.has(key)) return; const link = document.createElement('link'); link.rel = 'preload'; link.href = href; link.as = as; if (crossorigin) link.crossOrigin = 'anonymous'; document.head.appendChild(link); this.addedHints.add(key); } // Adicionar prefetch para recursos futuros prefetchResource(href: string): void { const key = `prefetch-${href}`; if (this.addedHints.has(key)) return; const link = document.createElement('link'); link.rel = 'prefetch'; link.href = href; document.head.appendChild(link); this.addedHints.add(key); } // Preconnect para domínios externos preconnectDomain(domain: string): void { const key = `preconnect-${domain}`; if (this.addedHints.has(key)) return; const link = document.createElement('link'); link.rel = 'preconnect'; link.href = domain; document.head.appendChild(link); this.addedHints.add(key); } // DNS prefetch para domínios dnsPrefetch(domain: string): void { const key = `dns-prefetch-${domain}`; if (this.addedHints.has(key)) return; const link = document.createElement('link'); link.rel = 'dns-prefetch'; link.href = domain; document.head.appendChild(link); this.addedHints.add(key); } // Configurar hints automáticos para APIs externas setupApiHints(): void { // DNS prefetch para API da Caixa this.dnsPrefetch('//servicebus2.caixa.gov.br'); // Preconnect se não houver problemas de CORS this.preconnectDomain('https://servicebus2.caixa.gov.br'); } getHintsStatus(): { total: number; types: Record } { const types: Record = {}; this.addedHints.forEach(hint => { const type = hint.split('-')[0]; types[type] = (types[type] || 0) + 1; }); return { total: this.addedHints.size, types }; } } // Instâncias globais export const treeshakingDetector = new TreeShakingDetector(); export const codeSplitter = new CodeSplitter(); export const intelligentPreloader = new IntelligentPreloader(); export const resourceHints = new ResourceHintsManager(); // Configuração automática export function setupBundleOptimizations(): void { // Configurar resource hints resourceHints.setupApiHints(); // Preload componentes críticos intelligentPreloader.register('DualGameViewer', () => import('../components/DualGameViewer')); intelligentPreloader.register('EnhancedLotomaniaGrid', () => import('../components/EnhancedLotomaniaGrid')); // Registrar rotas para code splitting codeSplitter.registerRoute('/', ['DualGameViewer', 'EnhancedLotomaniaGrid']); codeSplitter.registerRoute('/statistics', ['Statistics', 'Charts']); codeSplitter.registerRoute('/analysis', ['ResultsAnalysis', 'ProbabilityCalculator']); } // Hook para uso em React export function useBundleOptimizations() { return { preload: (key: string, loader: () => Promise) => intelligentPreloader.register(key, loader), getPreloadStatus: () => intelligentPreloader.getPreloadStatus(), getSplitSuggestions: () => codeSplitter.generateSplitSuggestions(), getHintsStatus: () => resourceHints.getHintsStatus() }; }