perf-audit-cli
Version:
CLI tool for continuous performance monitoring and analysis
157 lines • 7.67 kB
JavaScript
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