Files
community-rule/scripts/bundle-analyzer.js
T
adilallo f2cdb6fec9
CI Pipeline / test (20) (pull_request) Successful in 6m27s
CI Pipeline / test (18) (pull_request) Successful in 8m15s
CI Pipeline / e2e (firefox) (pull_request) Successful in 3m22s
CI Pipeline / e2e (webkit) (pull_request) Successful in 3m39s
CI Pipeline / e2e (chromium) (pull_request) Successful in 11m31s
CI Pipeline / visual-regression (pull_request) Successful in 6m22s
CI Pipeline / storybook (pull_request) Successful in 1m26s
CI Pipeline / performance (pull_request) Successful in 6m44s
CI Pipeline / build (pull_request) Successful in 2m8s
Update tracked files
2026-01-26 15:58:08 -07:00

254 lines
7.0 KiB
JavaScript

#!/usr/bin/env node
/**
* Bundle Analysis Script
* Analyzes webpack bundles and provides detailed performance insights
*/
const { execSync } = require("child_process");
const fs = require("fs");
const path = require("path");
const BUNDLE_ANALYSIS_DIR = path.join(__dirname, "..", ".next", "analyze");
const PERFORMANCE_BUDGETS = require("../performance-budgets.json");
class BundleAnalyzer {
constructor() {
this.results = {
timestamp: new Date().toISOString(),
bundles: {},
recommendations: [],
budgetViolations: [],
};
}
/**
* Run bundle analysis using build output
*/
async analyzeBundles() {
console.log("🔍 Starting bundle analysis...");
try {
// Ensure analyze directory exists
if (!fs.existsSync(BUNDLE_ANALYSIS_DIR)) {
fs.mkdirSync(BUNDLE_ANALYSIS_DIR, { recursive: true });
}
// Build the project first
console.log("🏗️ Building project...");
execSync("npm run build", { stdio: "inherit" });
// Parse bundle stats from build output
await this.parseBundleStats();
// Check performance budgets
this.checkPerformanceBudgets();
// Generate recommendations
this.generateRecommendations();
// Save results
this.saveResults();
console.log("✅ Bundle analysis complete!");
console.log(`📁 Results saved to: ${BUNDLE_ANALYSIS_DIR}`);
} catch (error) {
console.error("❌ Bundle analysis failed:", error.message);
process.exit(1);
}
}
/**
* Parse bundle statistics from build output
*/
async parseBundleStats() {
const staticPath = path.join(__dirname, "..", ".next", "static");
const chunksPath = path.join(staticPath, "chunks");
// Analyze static assets
if (fs.existsSync(staticPath)) {
this.analyzeDirectory(staticPath, "static");
}
// Analyze chunks
if (fs.existsSync(chunksPath)) {
this.analyzeDirectory(chunksPath, "chunks");
}
// Analyze pages
const pagesPath = path.join(__dirname, "..", ".next", "server", "pages");
if (fs.existsSync(pagesPath)) {
this.analyzeDirectory(pagesPath, "pages");
}
}
/**
* Analyze directory for bundle sizes
*/
analyzeDirectory(dirPath, type) {
const files = fs.readdirSync(dirPath);
files.forEach((file) => {
const filePath = path.join(dirPath, file);
const stats = fs.statSync(filePath);
if (stats.isFile() && (file.endsWith(".js") || file.endsWith(".css"))) {
const key = `${type}/${file}`;
this.results.bundles[key] = {
size: stats.size,
sizeKB: Math.round(stats.size / 1024),
lastModified: stats.mtime,
type: file.endsWith(".css") ? "css" : "js",
};
} else if (stats.isDirectory()) {
this.analyzeDirectory(filePath, `${type}/${file}`);
}
});
}
/**
* Check against performance budgets
*/
checkPerformanceBudgets() {
const budgets = PERFORMANCE_BUDGETS.budgets || [];
Object.entries(this.results.bundles).forEach(([filename, bundle]) => {
const budget = budgets.find(
(b) => filename.includes(b.name) || b.name === "all",
);
if (budget) {
if (bundle.sizeKB > budget.maxSizeKB) {
this.results.budgetViolations.push({
file: filename,
currentSize: bundle.sizeKB,
maxSize: budget.maxSizeKB,
overage: bundle.sizeKB - budget.maxSizeKB,
severity:
bundle.sizeKB > budget.maxSizeKB * 1.2 ? "high" : "medium",
});
}
} else {
// Default budget check for large files
if (bundle.sizeKB > 500) {
this.results.budgetViolations.push({
file: filename,
currentSize: bundle.sizeKB,
maxSize: 500,
overage: bundle.sizeKB - 500,
severity: bundle.sizeKB > 600 ? "high" : "medium",
});
}
}
});
}
/**
* Generate optimization recommendations
*/
generateRecommendations() {
const recommendations = [];
// Check for large bundles
Object.entries(this.results.bundles).forEach(([filename, bundle]) => {
if (bundle.sizeKB > 500) {
recommendations.push({
type: "large-bundle",
file: filename,
size: bundle.sizeKB,
suggestion:
"Consider code splitting or dynamic imports for this bundle",
});
}
});
// Check for budget violations
if (this.results.budgetViolations.length > 0) {
recommendations.push({
type: "budget-violation",
count: this.results.budgetViolations.length,
suggestion:
"Review and optimize bundles that exceed performance budgets",
});
}
// General recommendations
const totalSize = Object.values(this.results.bundles).reduce(
(sum, bundle) => sum + bundle.sizeKB,
0,
);
if (totalSize > 2000) {
recommendations.push({
type: "total-size",
size: totalSize,
suggestion: "Consider implementing more aggressive code splitting",
});
}
this.results.recommendations = recommendations;
}
/**
* Save analysis results
*/
saveResults() {
// Ensure directory exists
if (!fs.existsSync(BUNDLE_ANALYSIS_DIR)) {
fs.mkdirSync(BUNDLE_ANALYSIS_DIR, { recursive: true });
}
const resultsPath = path.join(BUNDLE_ANALYSIS_DIR, "bundle-analysis.json");
fs.writeFileSync(resultsPath, JSON.stringify(this.results, null, 2));
// Generate markdown report
this.generateMarkdownReport();
}
/**
* Generate markdown report
*/
generateMarkdownReport() {
const reportPath = path.join(BUNDLE_ANALYSIS_DIR, "bundle-report.md");
let report = `# Bundle Analysis Report\n\n`;
report += `**Generated:** ${this.results.timestamp}\n\n`;
// Bundle sizes
report += `## Bundle Sizes\n\n`;
report += `| File | Size (KB) | Status |\n`;
report += `|------|-----------|--------|\n`;
Object.entries(this.results.bundles).forEach(([filename, bundle]) => {
const status = bundle.sizeKB > 500 ? "⚠️ Large" : "✅ Good";
report += `| ${filename} | ${bundle.sizeKB} | ${status} |\n`;
});
// Budget violations
if (this.results.budgetViolations.length > 0) {
report += `\n## Budget Violations\n\n`;
this.results.budgetViolations.forEach((violation) => {
report += `- **${violation.file}**: ${violation.currentSize}KB (exceeds ${violation.maxSize}KB by ${violation.overage}KB)\n`;
});
}
// Recommendations
if (this.results.recommendations.length > 0) {
report += `\n## Recommendations\n\n`;
this.results.recommendations.forEach((rec) => {
report += `- ${rec.suggestion}\n`;
});
}
fs.writeFileSync(reportPath, report);
}
}
// Run analysis if called directly
if (require.main === module) {
const analyzer = new BundleAnalyzer();
analyzer.analyzeBundles().catch(console.error);
}
module.exports = BundleAnalyzer;