Assist_Design/apps/portal/scripts/bundle-monitor.mjs
barsa 3f7fa02b83 Refactor ESLint configuration and update TypeScript dependencies for improved type safety
- Enhanced ESLint configuration to better support TypeScript file patterns and added centralized dependency versions using pnpm catalogs.
- Updated TypeScript configurations across applications to utilize new file structure and improved type inference with Zod.
- Refactored domain modules to replace deprecated type inference methods, ensuring better type safety and consistency.
- Cleaned up package.json files to standardize dependency versions and improve overall project maintainability.
2025-12-12 14:50:12 +09:00

313 lines
8.4 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/* eslint-env node */
/**
* Bundle size monitoring script
* Analyzes bundle size and reports on performance metrics
*/
import { readFileSync, writeFileSync, existsSync } from "fs";
import { join, dirname } from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const BUNDLE_SIZE_LIMIT = {
// Size limits in KB
total: 1000, // 1MB total
individual: 250, // 250KB per chunk
vendor: 500, // 500KB for vendor chunks
};
// Note: Performance budgets can be added here when integrated
class BundleMonitor {
constructor() {
this.projectRoot = join(__dirname, "..");
this.buildDir = join(this.projectRoot, ".next");
this.reportFile = join(this.projectRoot, "bundle-report.json");
}
/**
* Analyze bundle size from Next.js build output
*/
analyzeBundleSize() {
const buildManifest = join(this.buildDir, "build-manifest.json");
if (!existsSync(buildManifest)) {
console.error('Build manifest not found. Run "pnpm run build" first.');
process.exit(1);
}
try {
const manifest = JSON.parse(readFileSync(buildManifest, "utf8"));
const chunks = [];
let totalSize = 0;
// Analyze JavaScript chunks
Object.entries(manifest.pages || {}).forEach(([page, files]) => {
files.forEach(file => {
if (file.endsWith(".js")) {
const filePath = join(this.buildDir, "static", file);
if (existsSync(filePath)) {
const stats = this.getFileStats(filePath);
chunks.push({
page,
file,
size: stats.size,
gzippedSize: stats.gzippedSize,
});
totalSize += stats.size;
}
}
});
});
return {
totalSize,
chunks,
timestamp: Date.now(),
};
} catch (error) {
console.error("Error analyzing bundle:", error.message);
process.exit(1);
}
}
/**
* Get file statistics including gzipped size
*/
getFileStats(filePath) {
try {
const content = readFileSync(filePath);
const size = content.length;
// Estimate gzipped size (rough approximation)
const gzippedSize = Math.round(size * 0.3); // Typical compression ratio
return { size, gzippedSize };
} catch {
return { size: 0, gzippedSize: 0 };
}
}
/**
* Check if bundle sizes are within limits
*/
checkBundleLimits(analysis) {
const issues = [];
// Check total size
const totalSizeKB = analysis.totalSize / 1024;
if (totalSizeKB > BUNDLE_SIZE_LIMIT.total) {
issues.push({
type: "total_size",
message: `Total bundle size (${totalSizeKB.toFixed(1)}KB) exceeds limit (${BUNDLE_SIZE_LIMIT.total}KB)`,
severity: "error",
});
}
// Check individual chunks
analysis.chunks.forEach(chunk => {
const sizeKB = chunk.size / 1024;
if (sizeKB > BUNDLE_SIZE_LIMIT.individual) {
const isVendor = chunk.file.includes("vendor") || chunk.file.includes("node_modules");
const limit = isVendor ? BUNDLE_SIZE_LIMIT.vendor : BUNDLE_SIZE_LIMIT.individual;
if (sizeKB > limit) {
issues.push({
type: "chunk_size",
message: `Chunk ${chunk.file} (${sizeKB.toFixed(1)}KB) exceeds limit (${limit}KB)`,
severity: "warning",
chunk: chunk.file,
});
}
}
});
return issues;
}
/**
* Generate recommendations for bundle optimization
*/
generateRecommendations(analysis) {
const recommendations = [];
// Large chunks recommendations
const largeChunks = analysis.chunks
.filter(chunk => chunk.size / 1024 > 100)
.sort((a, b) => b.size - a.size);
if (largeChunks.length > 0) {
recommendations.push({
type: "code_splitting",
message: "Consider implementing code splitting for large chunks",
chunks: largeChunks.slice(0, 5).map(c => c.file),
});
}
// Vendor chunk recommendations
const vendorChunks = analysis.chunks.filter(
chunk => chunk.file.includes("vendor") || chunk.file.includes("framework")
);
if (vendorChunks.some(chunk => chunk.size / 1024 > 300)) {
recommendations.push({
type: "vendor_optimization",
message: "Consider optimizing vendor chunks or using dynamic imports",
});
}
// Duplicate code detection (simplified)
const pageChunks = analysis.chunks.filter(chunk => chunk.page !== "_app");
if (pageChunks.length > 10) {
recommendations.push({
type: "common_chunks",
message: "Consider extracting common code into shared chunks",
});
}
return recommendations;
}
/**
* Load previous report for comparison
*/
loadPreviousReport() {
if (existsSync(this.reportFile)) {
try {
return JSON.parse(readFileSync(this.reportFile, "utf8"));
} catch (error) {
console.warn("Could not load previous report:", error.message);
}
}
return null;
}
/**
* Save current report
*/
saveReport(report) {
try {
writeFileSync(this.reportFile, JSON.stringify(report, null, 2));
console.log(`Report saved to ${this.reportFile}`);
} catch (error) {
console.error("Could not save report:", error.message);
}
}
/**
* Compare with previous report
*/
compareWithPrevious(current, previous) {
if (!previous) return null;
const currentTotal = current.analysis.totalSize;
const previousTotal = previous.analysis.totalSize;
const sizeDiff = currentTotal - previousTotal;
const percentChange = (sizeDiff / previousTotal) * 100;
return {
sizeDiff,
percentChange,
isRegression: sizeDiff > 10240, // 10KB threshold
};
}
/**
* Generate and display report
*/
run() {
console.log("🔍 Analyzing bundle size...\n");
const analysis = this.analyzeBundleSize();
const issues = this.checkBundleLimits(analysis);
const recommendations = this.generateRecommendations(analysis);
const previous = this.loadPreviousReport();
const comparison = this.compareWithPrevious({ analysis }, previous);
const report = {
timestamp: Date.now(),
analysis,
issues,
recommendations,
comparison,
};
// Display results
this.displayReport(report);
// Save report
this.saveReport(report);
// Exit with error code if there are critical issues
const hasErrors = issues.some(issue => issue.severity === "error");
if (hasErrors) {
console.log("\n❌ Bundle analysis failed due to critical issues.");
process.exit(1);
} else {
console.log("\n✅ Bundle analysis completed successfully.");
}
}
/**
* Display formatted report
*/
displayReport(report) {
const { analysis, issues, recommendations, comparison } = report;
// Bundle size summary
console.log("📊 Bundle Size Summary");
console.log("─".repeat(50));
console.log(`Total Size: ${(analysis.totalSize / 1024).toFixed(1)}KB`);
console.log(`Chunks: ${analysis.chunks.length}`);
if (comparison) {
const sign = comparison.sizeDiff > 0 ? "+" : "";
const color = comparison.isRegression ? "\x1b[31m" : "\x1b[32m";
console.log(
`Change: ${color}${sign}${(comparison.sizeDiff / 1024).toFixed(1)}KB (${comparison.percentChange.toFixed(1)}%)\x1b[0m`
);
}
// Top chunks
console.log("\n📦 Largest Chunks");
console.log("─".repeat(50));
analysis.chunks
.sort((a, b) => b.size - a.size)
.slice(0, 10)
.forEach(chunk => {
console.log(`${(chunk.size / 1024).toFixed(1)}KB - ${chunk.file}`);
});
// Issues
if (issues.length > 0) {
console.log("\n⚠ Issues Found");
console.log("─".repeat(50));
issues.forEach(issue => {
const icon = issue.severity === "error" ? "❌" : "⚠️ ";
console.log(`${icon} ${issue.message}`);
});
}
// Recommendations
if (recommendations.length > 0) {
console.log("\n💡 Recommendations");
console.log("─".repeat(50));
recommendations.forEach(rec => {
console.log(`${rec.message}`);
if (rec.chunks) {
rec.chunks.forEach(chunk => console.log(` - ${chunk}`));
}
});
}
}
}
// Run the monitor
const monitor = new BundleMonitor();
monitor.run();