Loto / src /utils /BundleOptimizations.ts
Raí Santos
oi
4c1e4ec
/**
* Otimizações avançadas de bundle size e performance
*/
// Tree shaking optimizations
export const treeShakeOptimizations = {
// TODO: Importar ícones específicos quando suportado
// icons: { ... },
// Code splitting por rotas/seções
sections: {
DualGameViewer: () => import('../components/DualGameViewer'),
Statistics: () => import('../components/Statistics'),
ProbabilityCalculator: () => import('../components/ProbabilityCalculator'),
EnhancedLotomaniaGrid: () => import('../components/EnhancedLotomaniaGrid'),
LotomaniaFeatures: () => import('../components/LotomaniaFeatures'),
GameByGameResults: () => import('../components/GameByGameResults')
},
// Utilitários por demanda
utils: {
chartSetup: () => import('../utils/chartSetup'),
securityUtils: () => import('../utils/SecurityUtils'),
performanceAnalyzer: () => import('../utils/PerformanceAnalyzer')
}
};
// Preload estratégico baseado em user behavior
export class SmartPreloader {
private static instance: SmartPreloader;
private preloadedModules = new Set<string>();
private userInteractions = new Map<string, number>();
static getInstance(): SmartPreloader {
if (!SmartPreloader.instance) {
SmartPreloader.instance = new SmartPreloader();
}
return SmartPreloader.instance;
}
// Preload baseado em hover/focus
preloadOnHover(moduleName: string, importFn: () => Promise<any>): () => void {
let timeoutId: NodeJS.Timeout;
const handleMouseEnter = () => {
if (this.preloadedModules.has(moduleName)) return;
timeoutId = setTimeout(() => {
importFn().then(() => {
this.preloadedModules.add(moduleName);
console.log(`📦 Preloaded: ${moduleName}`);
}).catch(err => {
console.warn(`Failed to preload ${moduleName}:`, err);
});
}, 50); // 50ms delay para evitar preloads desnecessários
};
const handleMouseLeave = () => {
clearTimeout(timeoutId);
};
// Retornar cleanup function
return () => {
clearTimeout(timeoutId);
};
}
// Preload baseado em priority e network conditions
async preloadByPriority(modules: Array<{ name: string; import: () => Promise<any>; priority: number }>) {
// Verificar condições de rede
const connection = (navigator as any).connection;
const isSlowConnection = connection && (connection.effectiveType === 'slow-2g' || connection.effectiveType === '2g');
if (isSlowConnection) {
console.log('🐌 Slow connection detected, skipping preload');
return;
}
// Ordenar por priority
const sortedModules = modules.sort((a, b) => b.priority - a.priority);
for (const module of sortedModules) {
if (this.preloadedModules.has(module.name)) continue;
try {
await module.import();
this.preloadedModules.add(module.name);
console.log(`📦 Preloaded: ${module.name} (priority: ${module.priority})`);
// Pequeno delay para não bloquear a UI
await new Promise(resolve => setTimeout(resolve, 10));
} catch (error) {
console.warn(`Failed to preload ${module.name}:`, error);
}
}
}
// Track user interactions para preload inteligente
trackInteraction(section: string) {
const current = this.userInteractions.get(section) || 0;
this.userInteractions.set(section, current + 1);
// Se usuário interage muito com uma seção, preload componentes relacionados
if (current >= 3) {
this.preloadRelatedModules(section);
}
}
private preloadRelatedModules(section: string) {
const relatedModules: Record<string, string[]> = {
'dual-visualizer': ['Statistics', 'ProbabilityCalculator'],
'results-analysis': ['GameByGameResults', 'EnhancedLotomaniaGrid'],
'probability-calculator': ['Statistics', 'DualGameViewer']
};
const related = relatedModules[section] || [];
related.forEach(moduleName => {
const importFn = (treeShakeOptimizations.sections as any)[moduleName];
if (importFn && !this.preloadedModules.has(moduleName)) {
importFn().then(() => {
this.preloadedModules.add(moduleName);
console.log(`🧠 Smart preloaded: ${moduleName} (related to ${section})`);
});
}
});
}
getStats() {
return {
preloadedModules: Array.from(this.preloadedModules),
userInteractions: Object.fromEntries(this.userInteractions),
totalPreloaded: this.preloadedModules.size
};
}
}
// Resource hints para otimização
export const injectResourceHints = () => {
if (typeof document === 'undefined') return;
const head = document.head;
// DNS prefetch para APIs externas
const dnsPrefetchUrls = [
'servicebus2.caixa.gov.br',
'api.allorigins.win',
'brasilapi.com.br'
];
dnsPrefetchUrls.forEach(url => {
const link = document.createElement('link');
link.rel = 'dns-prefetch';
link.href = `//${url}`;
head.appendChild(link);
});
// Preconnect para recursos críticos
const preconnectUrls = [
'https://fonts.googleapis.com',
'https://fonts.gstatic.com'
];
preconnectUrls.forEach(url => {
const link = document.createElement('link');
link.rel = 'preconnect';
link.href = url;
link.crossOrigin = 'anonymous';
head.appendChild(link);
});
console.log('🔗 Resource hints injected');
};
// Bundle analyzer em runtime
export const analyzeBundleSize = () => {
if (typeof window === 'undefined') return;
window.addEventListener('load', () => {
setTimeout(() => {
const resources = performance.getEntriesByType('resource') as PerformanceResourceTiming[];
let totalJSSize = 0;
let totalCSSSize = 0;
let chunkCount = 0;
resources.forEach(resource => {
if (resource.name.includes('.js')) {
totalJSSize += resource.transferSize || 0;
chunkCount++;
} else if (resource.name.includes('.css')) {
totalCSSSize += resource.transferSize || 0;
}
});
const totalSize = totalJSSize + totalCSSSize;
const analysis = {
totalSize: formatBytes(totalSize),
jsSize: formatBytes(totalJSSize),
cssSize: formatBytes(totalCSSSize),
chunkCount,
score: getBundleScore(totalSize)
};
console.log('📊 Bundle Analysis:', analysis);
// Warnings baseados em size
if (totalSize > 500 * 1024) { // > 500KB
console.warn('⚠️ Bundle muito grande! Considere mais code splitting.');
}
if (chunkCount > 15) {
console.warn('⚠️ Muitos chunks! Considere bundling de módulos relacionados.');
}
return analysis;
}, 1000);
});
};
const 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];
};
const getBundleScore = (bytes: number): string => {
if (bytes < 100 * 1024) return 'Excelente'; // < 100KB
if (bytes < 250 * 1024) return 'Bom'; // < 250KB
if (bytes < 500 * 1024) return 'Aceitável'; // < 500KB
return 'Precisa melhorar'; // > 500KB
};
// Singleton instances
export const smartPreloader = SmartPreloader.getInstance();