Assist_Design/apps/portal/scripts/bundle-monitor.mjs
T. Narantuya a95ec60859 Refactor address management and update related services for improved clarity and functionality
- Updated address retrieval in user service to replace billing info with a dedicated address method.
- Adjusted API endpoints to use `PATCH /api/me/address` for address updates instead of billing updates.
- Enhanced documentation to reflect changes in address management processes and API usage.
- Removed deprecated types and services related to billing address handling, streamlining the codebase.
2025-09-17 18:43:43 +09:00

314 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 */
/* global console, process */
/**
* 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 "npm 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();