UNPKG

perf-audit-cli

Version:

CLI tool for continuous performance monitoring and analysis

157 lines 7.67 kB
import path from 'path'; import { HEAVY_SERVER_BUNDLE_THRESHOLD, LARGE_CLIENT_BUNDLE_THRESHOLD, LARGE_SERVER_BUNDLE_THRESHOLD, MIN_SMALL_CHUNKS_FOR_RECOMMENDATION, SMALL_CHUNK_THRESHOLD, } from "../constants/index.js"; import { BundleAnalyzer } from "../core/bundle-analyzer.js"; import { applyBudgetsToAllBundles, createAuditResult } from "../utils/bundle.js"; import { CIIntegration } from "../utils/ci-integration.js"; import { completeCommand, handleCommandError, initializeCommand, initializePluginManager, saveBuildData, } from "../utils/command-helpers.js"; import { Logger } from "../utils/logger.js"; import { ReportGenerator } from "../utils/report-generator.js"; import { ConsoleReporter } from "../utils/reporter.js"; export const analyzeCommand = async (options) => { const { config, spinner } = await initializeCommand(); try { const pluginManager = await initializePluginManager(config); await pluginManager.executeHook('beforeAnalysis', { config }); spinner.text = 'Analyzing bundles...'; const bundles = await analyzeBundles(config, pluginManager); if (bundles.length === 0) { spinner.fail('No bundles found for analysis'); Logger.warn('Make sure your project has been built and the output path is correct.'); return; } const bundlesWithBudgets = applyBudgetsToAllBundles(bundles, config); const recommendations = generateRecommendations(bundlesWithBudgets); const result = createAuditResult(bundlesWithBudgets, config, recommendations); await saveBuildData(result); await pluginManager.executeHook('afterAnalysis', { result }); completeCommand(spinner, 'Bundle analysis completed'); await generateReports(result, options, config, pluginManager); await outputCIResults(result); await pluginManager.unloadPlugins(); } catch (error) { await handleCommandError(spinner, error, 'Analysis failed', config); } }; const analyzeBundles = async (config, pluginManager) => { const allBundles = []; const analysisTarget = config.analysis.target; if (analysisTarget === 'client' || analysisTarget === 'both') { const clientBundles = await analyzeClientBundles(config, pluginManager); allBundles.push(...clientBundles); } if (analysisTarget === 'server' || analysisTarget === 'both') { const serverBundles = await analyzeServerBundles(config, pluginManager); allBundles.push(...serverBundles); } await pluginManager.executeHook('afterBundleAnalysis', { bundles: allBundles }); return allBundles; }; const analyzeClientBundles = async (config, pluginManager) => { const clientAnalyzer = new BundleAnalyzer({ outputPath: config.project.client.outputPath, gzip: config.analysis.gzip, ignorePaths: config.analysis.ignorePaths, }); await pluginManager.executeHook('beforeBundleAnalysis', { outputPath: config.project.client.outputPath, }); const clientBundles = await clientAnalyzer.analyzeBundles(); return clientBundles.map(bundle => ({ ...bundle, type: 'client' })); }; const analyzeServerBundles = async (config, pluginManager) => { const serverAnalyzer = new BundleAnalyzer({ outputPath: config.project.server.outputPath, gzip: config.analysis.gzip, ignorePaths: config.analysis.ignorePaths, }); await pluginManager.executeHook('beforeBundleAnalysis', { outputPath: config.project.server.outputPath, }); const serverBundles = await serverAnalyzer.analyzeBundles(); return serverBundles.map(bundle => ({ ...bundle, type: 'server' })); }; const generateReports = async (result, options, config, pluginManager) => { await pluginManager.executeHook('beforeReport', { result, format: options.format, }); switch (options.format) { case 'json': await generateJsonReport(result, config, pluginManager); break; case 'html': await generateHtmlReport(result, config, pluginManager); break; case 'console': default: await generateConsoleReport(result, options, config, pluginManager); break; } }; const generateJsonReport = async (result, config, pluginManager) => { const outputPath = path.join(config.reports.outputDir, `bundle-analysis-${Date.now()}.json`); ReportGenerator.generateJsonReport(result, outputPath); Logger.success(`JSON report saved to: ${outputPath}`); Logger.json(result); await pluginManager.executeHook('afterReport', { result, outputPath }); }; const generateHtmlReport = async (result, config, pluginManager) => { const outputPath = path.join(config.reports.outputDir, `bundle-analysis-${Date.now()}.html`); ReportGenerator.generateHtmlReport(result, outputPath); Logger.success(`HTML report saved to: ${outputPath}`); Logger.info('Open the HTML file in your browser to view the detailed report.'); await pluginManager.executeHook('afterReport', { result, outputPath }); }; const generateConsoleReport = async (result, options, config, pluginManager) => { const reporter = new ConsoleReporter(config); reporter.reportBundleAnalysis(result, options.details || false); await pluginManager.executeHook('afterReport', { result, outputPath: 'console', }); }; const outputCIResults = async (result) => { const ciContext = CIIntegration.detectCIEnvironment(); await CIIntegration.outputCIAnnotations(result, ciContext); }; const generateRecommendations = (bundles) => { const recommendations = []; const clientRecommendations = generateClientRecommendations(bundles); const serverRecommendations = generateServerRecommendations(bundles); recommendations.push(...clientRecommendations, ...serverRecommendations); return recommendations; }; const generateClientRecommendations = (bundles) => { const recommendations = []; const clientBundles = bundles.filter(b => b.type === 'client'); if (clientBundles.length === 0) { return recommendations; } const largeClientBundles = clientBundles.filter(b => b.size > LARGE_CLIENT_BUNDLE_THRESHOLD); if (largeClientBundles.length > 0) { recommendations.push(`[Client] Consider code splitting for large bundles: ${largeClientBundles.map(b => b.name).join(', ')}`); } const similarClientChunks = clientBundles.filter(b => b.name.includes('chunk') && b.size < SMALL_CHUNK_THRESHOLD); if (similarClientChunks.length > MIN_SMALL_CHUNKS_FOR_RECOMMENDATION) { recommendations.push(`[Client] Consider merging small chunks to reduce HTTP requests`); } return recommendations; }; const generateServerRecommendations = (bundles) => { const recommendations = []; const serverBundles = bundles.filter(b => b.type === 'server'); if (serverBundles.length === 0) { return recommendations; } const largeServerBundles = serverBundles.filter(b => b.size > LARGE_SERVER_BUNDLE_THRESHOLD); if (largeServerBundles.length > 0) { recommendations.push(`[Server] Consider optimizing large server bundles: ${largeServerBundles.map(b => b.name).join(', ')}`); } const heavyServerBundles = serverBundles.filter(b => b.size > HEAVY_SERVER_BUNDLE_THRESHOLD && b.name.includes('node_modules')); if (heavyServerBundles.length > 0) { recommendations.push(`[Server] Review server dependencies for optimization: ${heavyServerBundles.map(b => b.name).join(', ')}`); } return recommendations; }; //# sourceMappingURL=analyze.js.map