Loto / src /utils /BundleOptimizer.ts
Raí Santos
oi
4c1e4ec
/**
* 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<string>();
private unusedImports = new Set<string>();
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<string, string[]> = new Map();
private components: Map<string, number> = 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<string, string[]> {
const suggestions: Record<string, string[]> = {
'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<any>> = [];
private preloadCache = new Set<string>();
private isPreloading = false;
// Registrar componente para preload
register(key: string, loader: () => Promise<any>): 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<void> {
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<void> {
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<void> {
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<any>): () => 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<string>();
// 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<string, number> } {
const types: Record<string, number> = {};
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<any>) => intelligentPreloader.register(key, loader),
getPreloadStatus: () => intelligentPreloader.getPreloadStatus(),
getSplitSuggestions: () => codeSplitter.generateSplitSuggestions(),
getHintsStatus: () => resourceHints.getHintsStatus()
};
}