/** * Performance budget presets and classification utilities. * * ESLint model: global defaults, zero-config, auto-measurement. * Users configure 0-3 numbers. Per-component overrides are the `eslint-disable` equivalent. */ // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- export interface PerformanceBudgets { /** Maximum gzipped bundle size in bytes */ bundleSize: number; } export interface PerformanceConfig { preset: string; budgets: PerformanceBudgets; } export type ComplexityTier = 'lightweight' | 'moderate' | 'heavy'; export interface PerformanceData { /** Gzipped bundle size in bytes */ bundleSize: number; /** Raw (minified, not gzipped) bundle size in bytes */ rawSize: number; /** Complexity classification */ complexity: ComplexityTier; /** Percentage of budget used (0-100+) */ budgetPercent: number; /** Whether the component exceeds its budget */ overBudget: boolean; /** ISO timestamp when measured */ measuredAt: string; } export interface PerformanceSummary { /** Preset name used */ preset: string; /** Budget applied in bytes */ budget: number; /** Total components measured */ total: number; /** Number of components over budget */ overBudget: number; /** Distribution by tier */ tiers: Record; } // --------------------------------------------------------------------------- // Presets // --------------------------------------------------------------------------- const PRESETS: Record = { strict: { bundleSize: 8 * 1024 }, // 8KB gzipped standard: { bundleSize: 15 * 1024 }, // 15KB gzipped relaxed: { bundleSize: 30 * 1024 }, // 30KB gzipped }; export const PRESET_NAMES = Object.keys(PRESETS) as readonly string[]; // --------------------------------------------------------------------------- // Resolution // --------------------------------------------------------------------------- /** * Resolve a performance config from user input. * Accepts a preset name string or a custom config object. */ export function resolvePerformanceConfig( input: string | { preset?: string; budgets?: Partial } | undefined ): PerformanceConfig { if (!input) { return { preset: 'standard', budgets: PRESETS.standard }; } if (typeof input === 'string') { const budgets = PRESETS[input]; if (!budgets) { throw new Error( `Unknown performance preset "${input}". Available: ${PRESET_NAMES.join(', ')}` ); } return { preset: input, budgets }; } const presetName = input.preset ?? 'standard'; const baseBudgets = PRESETS[presetName]; if (!baseBudgets) { throw new Error( `Unknown performance preset "${presetName}". Available: ${PRESET_NAMES.join(', ')}` ); } return { preset: presetName, budgets: { bundleSize: input.budgets?.bundleSize ?? baseBudgets.bundleSize, }, }; } // --------------------------------------------------------------------------- // Classification // --------------------------------------------------------------------------- /** * Classify a component's complexity based on gzipped bundle size. * * - lightweight: < 5KB — simple, leaf components * - moderate: < 15KB — typical composed components * - heavy: >= 15KB — complex widgets with dependencies */ export function classifyComplexity(gzipBytes: number): ComplexityTier { if (gzipBytes < 5 * 1024) return 'lightweight'; if (gzipBytes < 15 * 1024) return 'moderate'; return 'heavy'; } // --------------------------------------------------------------------------- // Formatting helpers // --------------------------------------------------------------------------- /** * Format bytes to a human-readable string (e.g. "2.1KB", "15.3KB"). */ export function formatBytes(bytes: number): string { if (bytes < 1024) return `${bytes}B`; const kb = bytes / 1024; return kb < 10 ? `${kb.toFixed(1)}KB` : `${Math.round(kb)}KB`; } /** * Create a visual budget bar for terminal output. */ export function budgetBar(percent: number, width = 20): string { const filled = Math.min(Math.round((percent / 100) * width), width); const bar = '█'.repeat(filled) + '░'.repeat(width - filled); return percent > 100 ? `\x1b[31m${bar}\x1b[0m` : `\x1b[32m${bar}\x1b[0m`; }