UNPKG

perf-audit-cli

Version:

CLI tool for continuous performance monitoring and analysis

207 lines • 10.5 kB
import chalk from 'chalk'; import { BundleAnalyzer } from "../core/bundle-analyzer.js"; import { formatDelta, formatSize } from "./size.js"; export class ConsoleReporter { config; constructor(config) { this.config = config; } reportBundleAnalysis(result, showDetails = false) { console.log(chalk.blue('\nšŸŽÆ Performance Audit Report')); console.log(chalk.blue('═══════════════════════════════════════════')); const typeIndicator = result.analysisType === 'both' ? 'šŸ“¦šŸ–„ļø Client & Server Analysis' : result.analysisType === 'client' ? 'šŸ“¦ Client-side Analysis' : 'šŸ–„ļø Server-side Analysis'; console.log(chalk.blue(`\n${typeIndicator}`)); if (result.clientBundles.length > 0) { console.log(chalk.yellow('\nšŸ“¦ Client Bundles:')); result.clientBundles.forEach(bundle => { const status = this.getStatusIcon(bundle.status); const sizeText = formatSize(bundle.size); const gzipText = bundle.gzipSize ? ` (gzip: ${formatSize(bundle.gzipSize)})` : ''; const deltaText = bundle.delta ? ` ${this.formatDelta(bundle.delta)}` : ''; console.log(`ā”œā”€ ${bundle.name}: ${sizeText}${gzipText} ${status}${deltaText}`); if (showDetails && bundle.status !== 'ok') { this.showBundleDetails(bundle); } }); const clientTotalSize = result.clientBundles.reduce((sum, b) => sum + b.size, 0); const clientTotalGzipSize = result.clientBundles.reduce((sum, b) => sum + (b.gzipSize || 0), 0); const clientSizeText = formatSize(clientTotalSize); const clientGzipText = clientTotalGzipSize > 0 ? ` (gzip: ${formatSize(clientTotalGzipSize)})` : ''; console.log(`└─ Client Total: ${clientSizeText}${clientGzipText}`); } if (result.serverBundles.length > 0) { console.log(chalk.green('\nšŸ–„ļø Server Bundles:')); result.serverBundles.forEach(bundle => { const status = this.getStatusIcon(bundle.status); const sizeText = formatSize(bundle.size); const gzipText = bundle.gzipSize ? ` (gzip: ${formatSize(bundle.gzipSize)})` : ''; const deltaText = bundle.delta ? ` ${this.formatDelta(bundle.delta)}` : ''; console.log(`ā”œā”€ ${bundle.name}: ${sizeText}${gzipText} ${status}${deltaText}`); if (showDetails && bundle.status !== 'ok') { this.showBundleDetails(bundle); } }); const serverTotalSize = result.serverBundles.reduce((sum, b) => sum + b.size, 0); const serverTotalGzipSize = result.serverBundles.reduce((sum, b) => sum + (b.gzipSize || 0), 0); const serverSizeText = formatSize(serverTotalSize); const serverGzipText = serverTotalGzipSize > 0 ? ` (gzip: ${formatSize(serverTotalGzipSize)})` : ''; console.log(`└─ Server Total: ${serverSizeText}${serverGzipText}`); } if (result.analysisType === 'both' && (result.clientBundles.length > 0 || result.serverBundles.length > 0)) { console.log(chalk.blue('\nšŸ“Š Overall Total:')); const totalSizes = BundleAnalyzer.calculateTotalSize([...result.serverBundles, ...result.clientBundles]); const totalSizeText = formatSize(totalSizes.size); const totalGzipText = totalSizes.gzipSize ? ` (gzip: ${formatSize(totalSizes.gzipSize)})` : ''; console.log(`└─ Combined Total: ${totalSizeText}${totalGzipText}`); } if (result.lighthouse) { this.reportPerformanceMetrics(result.lighthouse); } if (result.recommendations.length > 0) { console.log(chalk.blue('\nšŸ’” Recommendations:')); result.recommendations.forEach(rec => { console.log(`- ${rec}`); }); } console.log(this.getOverallStatus(result.budgetStatus)); } reportBudgetCheck(result) { console.log(chalk.blue('\nšŸ’° Budget Check Report')); console.log(chalk.blue('═══════════════════════════════════════════')); let hasViolations = false; [...result.serverBundles, ...result.clientBundles].forEach(bundle => { if (bundle.status !== 'ok') { hasViolations = true; const status = this.getStatusIcon(bundle.status); const sizeText = formatSize(bundle.size); console.log(`${status} ${bundle.name}: ${sizeText}`); const budgetKey = this.getBudgetKey(bundle.name); const bundleType = bundle.type ?? 'client'; const budgetConfig = bundleType === 'server' ? this.config.budgets.server : this.config.budgets.client; const budget = budgetConfig.bundles[budgetKey]; if (budget) { console.log(` Budget: ${budget.warning} (warning) / ${budget.max} (max)`); } } }); if (!hasViolations) { console.log(chalk.green('āœ… All bundles are within budget limits')); } console.log(this.getOverallStatus(result.budgetStatus)); } reportLighthouseResults(result) { if (!result.lighthouse) return; const metrics = result.lighthouse; console.log(chalk.blue('\nšŸ“Š Lighthouse Scores')); console.log(`ā”œā”€ Performance: ${this.formatScore(metrics.performance)}/100 ${this.getScoreIcon(metrics.performance)}`); console.log(`ā”œā”€ Accessibility: ${this.formatScore(metrics.accessibility || 0)}/100 ${this.getScoreIcon(metrics.accessibility || 0)}`); console.log(`ā”œā”€ Best Practices: ${this.formatScore(metrics.bestPractices || 0)}/100 ${this.getScoreIcon(metrics.bestPractices || 0)}`); console.log(`└─ SEO: ${this.formatScore(metrics.seo || 0)}/100 ${this.getScoreIcon(metrics.seo || 0)}`); console.log(chalk.blue('\nšŸš€ Core Web Vitals')); if (metrics.metrics) { const { fcp, lcp, cls, tti } = metrics.metrics; console.log(`ā”œā”€ FCP: ${fcp}ms ${this.getMetricStatus(fcp, this.config.budgets.metrics.fcp)}`); console.log(`ā”œā”€ LCP: ${lcp}ms ${this.getMetricStatus(lcp, this.config.budgets.metrics.lcp)}`); console.log(`ā”œā”€ CLS: ${cls} ${this.getMetricStatus(cls, this.config.budgets.metrics.cls)}`); console.log(`└─ TTI: ${tti}ms ${this.getMetricStatus(tti, this.config.budgets.metrics.tti)}`); } if (result.recommendations.length > 0) { console.log(chalk.blue('\nšŸ’” Performance Recommendations:')); result.recommendations.forEach(rec => { console.log(`- ${rec}`); }); } console.log(this.getOverallStatus(result.budgetStatus)); } formatScore(score) { return score.toString().padStart(2); } getScoreIcon(score) { if (score >= 90) return chalk.green('āœ…'); if (score >= 75) return chalk.yellow('āš ļø'); return chalk.red('āŒ'); } reportPerformanceMetrics(metrics) { console.log(chalk.blue('\nšŸ“Š Performance Metrics')); if (metrics.performance !== undefined) { const status = metrics.performance >= 90 ? 'āœ…' : metrics.performance >= 75 ? 'āš ļø' : 'āŒ'; console.log(`ā”œā”€ Performance Score: ${metrics.performance}/100 ${status}`); } if (metrics.metrics) { const { fcp, lcp, cls, tti } = metrics.metrics; console.log(`ā”œā”€ FCP: ${fcp}ms ${this.getMetricStatus(fcp, this.config.budgets.metrics.fcp)}`); console.log(`ā”œā”€ LCP: ${lcp}ms ${this.getMetricStatus(lcp, this.config.budgets.metrics.lcp)}`); console.log(`ā”œā”€ CLS: ${cls} ${this.getMetricStatus(cls, this.config.budgets.metrics.cls)}`); console.log(`└─ TTI: ${tti}ms ${this.getMetricStatus(tti, this.config.budgets.metrics.tti)}`); } } getStatusIcon(status) { switch (status) { case 'ok': return chalk.green('āœ…'); case 'warning': return chalk.yellow('āš ļø'); case 'error': return chalk.red('āŒ'); default: return ''; } } formatDelta(delta) { const formatted = formatDelta(delta); if (delta > 0) { return chalk.red(formatted); } else if (delta < 0) { return chalk.green(formatted); } return formatted; } showBundleDetails(bundle) { const indent = ' '; console.log(chalk.dim(`${indent}Size: ${formatSize(bundle.size)}`)); if (bundle.gzipSize) { console.log(chalk.dim(`${indent}Gzipped: ${formatSize(bundle.gzipSize)}`)); } } getMetricStatus(value, budget) { if (value >= budget.max) { return chalk.red(`āŒ (budget: ${budget.max})`); } else if (value >= budget.warning) { return chalk.yellow(`āš ļø (budget: ${budget.warning})`); } return chalk.green('āœ…'); } getOverallStatus(status) { const timestamp = new Date().toLocaleString(); switch (status) { case 'ok': return chalk.green(`\nāœ… All checks passed! (${timestamp})`); case 'warning': return chalk.yellow(`\nāš ļø Some warnings detected (${timestamp})`); case 'error': return chalk.red(`\nāŒ Budget violations detected! (${timestamp})`); default: return ''; } } getBudgetKey(bundleName) { const name = bundleName.toLowerCase(); if (name.includes('main') || name.includes('index')) return 'main'; if (name.includes('vendor') || name.includes('chunk')) return 'vendor'; if (name.includes('runtime')) return 'runtime'; return 'main'; } } //# sourceMappingURL=reporter.js.map