|
|
|
|
|
|
|
|
|
|
|
import { memoize } from './PerformanceOptimizer'; |
|
|
|
interface ModuleInfo { |
|
name: string; |
|
size: number; |
|
dependencies: string[]; |
|
lastUsed: number; |
|
priority: 'high' | 'medium' | 'low'; |
|
} |
|
|
|
interface OptimizationResult { |
|
originalSize: number; |
|
optimizedSize: number; |
|
savings: number; |
|
recommendations: string[]; |
|
} |
|
|
|
export class BundleOptimizerAdvanced { |
|
private static instance: BundleOptimizerAdvanced; |
|
private preloadedModules = new Set<string>(); |
|
private moduleRegistry = new Map<string, ModuleInfo>(); |
|
private criticalModules = new Set<string>(); |
|
|
|
static getInstance(): BundleOptimizerAdvanced { |
|
if (!BundleOptimizerAdvanced.instance) { |
|
BundleOptimizerAdvanced.instance = new BundleOptimizerAdvanced(); |
|
} |
|
return BundleOptimizerAdvanced.instance; |
|
} |
|
|
|
|
|
|
|
|
|
initialize(): void { |
|
console.log('🚀 Inicializando BundleOptimizerAdvanced...'); |
|
|
|
this.setupResourceHints(); |
|
this.setupModuleTracking(); |
|
this.setupTreeShaking(); |
|
this.setupChunkOptimization(); |
|
this.registerCriticalModules(); |
|
} |
|
|
|
private setupResourceHints(): void { |
|
|
|
const head = document.head; |
|
|
|
|
|
this.addResourceHint('dns-prefetch', 'https://servicebus2.caixa.gov.br'); |
|
this.addResourceHint('dns-prefetch', 'https://brasilapi.com.br'); |
|
|
|
|
|
this.addResourceHint('preconnect', 'https://fonts.googleapis.com'); |
|
this.addResourceHint('preconnect', 'https://fonts.gstatic.com', true); |
|
|
|
|
|
this.addResourceHint('prefetch', '/static/js/chunks/'); |
|
} |
|
|
|
private addResourceHint(rel: string, href: string, crossorigin = false): void { |
|
const link = document.createElement('link'); |
|
link.rel = rel; |
|
link.href = href; |
|
if (crossorigin) link.crossOrigin = 'anonymous'; |
|
document.head.appendChild(link); |
|
} |
|
|
|
private setupModuleTracking(): void { |
|
|
|
if (typeof window !== 'undefined') { |
|
(window as any).__bundleOptimizer = { |
|
trackModuleUsage: (moduleName: string, size: number) => { |
|
this.trackModuleUsage(moduleName, size); |
|
}, |
|
getOptimizationReport: () => this.getOptimizationReport() |
|
}; |
|
} |
|
} |
|
|
|
private setupTreeShaking(): void { |
|
|
|
console.log('🌳 Tree shaking monitor ativo'); |
|
|
|
|
|
if (process.env.NODE_ENV === 'development') { |
|
this.detectUnusedImports(); |
|
} |
|
} |
|
|
|
private setupChunkOptimization(): void { |
|
|
|
const chunkStrategy = { |
|
vendor: ['react', 'react-dom', 'axios'], |
|
charts: ['chart.js', 'react-chartjs-2'], |
|
utils: ['lodash', 'date-fns'], |
|
ui: ['lucide-react', 'tailwindcss'] |
|
}; |
|
|
|
console.log('📦 Estratégia de chunks configurada:', chunkStrategy); |
|
} |
|
|
|
private registerCriticalModules(): void { |
|
|
|
const critical = [ |
|
'react', |
|
'react-dom', |
|
'src/App', |
|
'src/components/Sidebar', |
|
'src/components/DualGameViewer', |
|
'src/hooks/useLotomaniaAPI', |
|
'src/utils/lotomaniaAlgorithm' |
|
]; |
|
|
|
critical.forEach(module => this.criticalModules.add(module)); |
|
console.log('⭐ Módulos críticos registrados:', critical); |
|
} |
|
|
|
|
|
|
|
|
|
preloadStrategic = memoize((route: string) => { |
|
const preloadMap: Record<string, string[]> = { |
|
'dual-visualizer': [ |
|
'src/components/EnhancedLotomaniaGrid', |
|
'src/components/GameViewer' |
|
], |
|
'statistics': [ |
|
'src/components/Statistics', |
|
'src/utils/chartSetup' |
|
], |
|
'results-analysis': [ |
|
'src/components/ResultsAnalysis', |
|
'src/hooks/useLotomaniaAPI' |
|
], |
|
'probability-calculator': [ |
|
'src/components/ProbabilityCalculator' |
|
] |
|
}; |
|
|
|
const modules = preloadMap[route] || []; |
|
modules.forEach(module => this.preloadModule(module)); |
|
}); |
|
|
|
private preloadModule(moduleName: string): void { |
|
if (this.preloadedModules.has(moduleName)) return; |
|
|
|
try { |
|
|
|
import( `${moduleName}`) |
|
.then(() => { |
|
console.log(`✅ Módulo precarregado: ${moduleName}`); |
|
this.preloadedModules.add(moduleName); |
|
}) |
|
.catch(() => { |
|
|
|
}); |
|
} catch (error) { |
|
|
|
} |
|
} |
|
|
|
|
|
|
|
|
|
optimizeImages(): void { |
|
const images = document.querySelectorAll('img'); |
|
|
|
images.forEach(img => { |
|
|
|
if (!img.hasAttribute('loading')) { |
|
img.loading = 'lazy'; |
|
} |
|
|
|
|
|
if (!img.hasAttribute('decoding')) { |
|
img.decoding = 'async'; |
|
} |
|
|
|
|
|
if (!img.hasAttribute('sizes') && img.hasAttribute('srcset')) { |
|
img.sizes = '(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw'; |
|
} |
|
}); |
|
|
|
console.log(`🖼️ ${images.length} imagens otimizadas`); |
|
} |
|
|
|
|
|
|
|
|
|
optimizeFonts(): void { |
|
|
|
const criticalFonts = [ |
|
'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap' |
|
]; |
|
|
|
criticalFonts.forEach(fontUrl => { |
|
const link = document.createElement('link'); |
|
link.rel = 'preload'; |
|
link.href = fontUrl; |
|
link.as = 'style'; |
|
link.onload = () => { |
|
link.rel = 'stylesheet'; |
|
}; |
|
document.head.appendChild(link); |
|
}); |
|
|
|
console.log(`🔤 ${criticalFonts.length} fontes críticas otimizadas`); |
|
} |
|
|
|
|
|
|
|
|
|
monitorBundleSize(): void { |
|
if (typeof window === 'undefined') return; |
|
|
|
window.addEventListener('load', () => { |
|
setTimeout(() => { |
|
const resources = performance.getEntriesByType('resource') as PerformanceResourceTiming[]; |
|
const jsResources = resources.filter(r => r.name.includes('.js')); |
|
const cssResources = resources.filter(r => r.name.includes('.css')); |
|
|
|
const jsSize = jsResources.reduce((total, r) => total + (r.transferSize || 0), 0); |
|
const cssSize = cssResources.reduce((total, r) => total + (r.transferSize || 0), 0); |
|
|
|
console.log('📊 Bundle Size Analysis:'); |
|
console.log(`📄 JavaScript: ${this.formatBytes(jsSize)}`); |
|
console.log(`🎨 CSS: ${this.formatBytes(cssSize)}`); |
|
console.log(`📦 Total: ${this.formatBytes(jsSize + cssSize)}`); |
|
|
|
|
|
if (jsSize > 1024 * 1024) { |
|
console.warn('⚠️ Bundle JS muito grande. Considere code splitting.'); |
|
} |
|
|
|
if (jsResources.length > 10) { |
|
console.warn('⚠️ Muitos arquivos JS. Considere bundling.'); |
|
} |
|
|
|
this.trackModuleUsage('total-bundle', jsSize + cssSize); |
|
}, 1000); |
|
}); |
|
} |
|
|
|
private trackModuleUsage(moduleName: string, size: number): void { |
|
const existing = this.moduleRegistry.get(moduleName); |
|
|
|
if (existing) { |
|
existing.lastUsed = Date.now(); |
|
} else { |
|
this.moduleRegistry.set(moduleName, { |
|
name: moduleName, |
|
size, |
|
dependencies: [], |
|
lastUsed: Date.now(), |
|
priority: this.calculatePriority(moduleName) |
|
}); |
|
} |
|
} |
|
|
|
private calculatePriority(moduleName: string): 'high' | 'medium' | 'low' { |
|
if (this.criticalModules.has(moduleName)) return 'high'; |
|
if (moduleName.includes('component') || moduleName.includes('hook')) return 'medium'; |
|
return 'low'; |
|
} |
|
|
|
private detectUnusedImports(): void { |
|
|
|
console.log('🔍 Monitorando imports não utilizados (desenvolvimento)'); |
|
|
|
|
|
|
|
setTimeout(() => { |
|
const moduleCount = this.moduleRegistry.size; |
|
console.log(`📊 ${moduleCount} módulos rastreados para otimização`); |
|
}, 5000); |
|
} |
|
|
|
|
|
|
|
|
|
getOptimizationReport(): OptimizationResult { |
|
const modules = Array.from(this.moduleRegistry.values()); |
|
const totalSize = modules.reduce((sum, m) => sum + m.size, 0); |
|
|
|
|
|
const savings = totalSize * 0.15; |
|
|
|
const recommendations = this.generateOptimizationRecommendations(modules); |
|
|
|
return { |
|
originalSize: totalSize, |
|
optimizedSize: totalSize - savings, |
|
savings, |
|
recommendations |
|
}; |
|
} |
|
|
|
private generateOptimizationRecommendations(modules: ModuleInfo[]): string[] { |
|
const recommendations: string[] = []; |
|
|
|
|
|
const largeModules = modules.filter(m => m.size > 100 * 1024); |
|
if (largeModules.length > 0) { |
|
recommendations.push(`📦 Módulos grandes detectados: ${largeModules.map(m => m.name).join(', ')}`); |
|
} |
|
|
|
|
|
const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000); |
|
const unusedModules = modules.filter(m => m.lastUsed < thirtyDaysAgo); |
|
if (unusedModules.length > 0) { |
|
recommendations.push(`🗑️ Módulos potencialmente não utilizados: ${unusedModules.length} encontrados`); |
|
} |
|
|
|
|
|
const lowPriorityModules = modules.filter(m => m.priority === 'low'); |
|
if (lowPriorityModules.length > 0) { |
|
recommendations.push(`⬇️ Considere lazy loading para ${lowPriorityModules.length} módulos de baixa prioridade`); |
|
} |
|
|
|
|
|
recommendations.push('🌳 Use tree shaking para remover código não utilizado'); |
|
recommendations.push('🔄 Implemente code splitting baseado em rotas'); |
|
recommendations.push('💾 Configure caching agressivo para módulos vendor'); |
|
|
|
return recommendations; |
|
} |
|
|
|
private formatBytes(bytes: number): string { |
|
if (bytes === 0) return '0 Bytes'; |
|
const k = 1024; |
|
const sizes = ['Bytes', 'KB', 'MB', 'GB']; |
|
const i = Math.floor(Math.log(bytes) / Math.log(k)); |
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; |
|
} |
|
|
|
|
|
|
|
|
|
applyAllOptimizations(): void { |
|
console.log('🚀 Aplicando todas as otimizações...'); |
|
|
|
this.optimizeImages(); |
|
this.optimizeFonts(); |
|
this.monitorBundleSize(); |
|
|
|
|
|
setTimeout(() => { |
|
this.preloadStrategic('dual-visualizer'); |
|
}, 2000); |
|
|
|
console.log('✅ Todas as otimizações aplicadas'); |
|
} |
|
|
|
|
|
|
|
|
|
cleanup(): void { |
|
this.preloadedModules.clear(); |
|
this.moduleRegistry.clear(); |
|
console.log('🧹 BundleOptimizerAdvanced limpo'); |
|
} |
|
} |
|
|
|
|
|
export const bundleOptimizer = BundleOptimizerAdvanced.getInstance(); |
|
|