Files
2026-07-13 12:12:04 +08:00

262 lines
6.8 KiB
JavaScript

#!/usr/bin/env node
/* eslint-disable no-console */
import {execSync} from "child_process";
import fs from "fs";
import path from "path";
import {fileURLToPath} from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const rootDir = path.join(__dirname, "..");
const distDir = path.join(rootDir, "dist");
const componentsDir = path.join(rootDir, "components");
const outputFile = path.join(rootDir, "bundle-size.json");
/**
* Get file size in bytes
*/
function getFileSize(filePath) {
try {
const stats = fs.statSync(filePath);
return stats.size;
} catch {
return null;
}
}
/**
* Format bytes to human readable string
*/
function formatBytes(bytes) {
if (bytes === null) return "N/A";
const kb = bytes / 1024;
return {
bytes,
formatted: kb >= 1 ? `${kb.toFixed(2)} KB` : `${bytes} B`,
kb: Math.round(kb * 1000) / 1000,
};
}
/**
* Get sizes for all component CSS files
*/
function getComponentSizes() {
const components = {};
if (!fs.existsSync(componentsDir)) {
return components;
}
const files = fs
.readdirSync(componentsDir)
.filter((f) => f.endsWith(".css") && f !== "index.css");
for (const file of files) {
const filePath = path.join(componentsDir, file);
const size = getFileSize(filePath);
if (size !== null) {
components[file] = {
bytes: size,
kb: Math.round((size / 1024) * 1000) / 1000,
};
}
}
return components;
}
/**
* Build the CSS bundle
*/
function buildBundle() {
console.log("Building CSS bundle...");
try {
execSync("npm run build", {
cwd: rootDir,
stdio: "inherit",
});
console.log("Build completed.\n");
return true;
} catch (error) {
console.error("Build failed:", error.message);
return false;
}
}
/**
* Load previous measurement for comparison
*/
function loadPreviousMeasurement() {
try {
if (fs.existsSync(outputFile)) {
const content = fs.readFileSync(outputFile, "utf-8");
return JSON.parse(content);
}
} catch {
// Ignore errors
}
return null;
}
/**
* Calculate and display comparison
*/
function showComparison(current, previous, _) {
if (!previous) return "";
const diff = current - previous;
const percent = ((diff / previous) * 100).toFixed(2);
const arrow = diff > 0 ? "↑" : diff < 0 ? "↓" : "→";
const sign = diff > 0 ? "+" : "";
return ` (${arrow} ${sign}${formatBytes(diff).formatted}, ${sign}${percent}%)`;
}
/**
* Main function
*/
async function main() {
const args = process.argv.slice(2);
const skipBuild = args.includes("--skip-build");
const compareOnly = args.includes("--compare");
console.log("=".repeat(60));
console.log("HeroUI CSS Bundle Size Measurement");
console.log("=".repeat(60));
console.log();
// Load previous measurement
const previous = loadPreviousMeasurement();
if (compareOnly && previous) {
console.log("Previous measurement from:", previous.timestamp);
console.log();
console.log("Uncompressed (index.css):", formatBytes(previous.uncompressed.bytes).formatted);
console.log("Minified (heroui.min.css):", formatBytes(previous.minified.bytes).formatted);
console.log();
console.log("Component breakdown:");
const sortedComponents = Object.entries(previous.components).sort(
(a, b) => b[1].bytes - a[1].bytes,
);
for (const [name, size] of sortedComponents.slice(0, 10)) {
console.log(` ${name.padEnd(30)} ${formatBytes(size.bytes).formatted}`);
}
if (sortedComponents.length > 10) {
console.log(` ... and ${sortedComponents.length - 10} more`);
}
return;
}
// Build unless skipped
if (!skipBuild) {
const buildSuccess = buildBundle();
if (!buildSuccess) {
process.exit(1);
}
}
// Measure sizes
const indexPath = path.join(distDir, "index.css");
const minifiedPath = path.join(distDir, "heroui.min.css");
const uncompressedSize = getFileSize(indexPath);
const minifiedSize = getFileSize(minifiedPath);
if (uncompressedSize === null) {
console.error("Error: dist/index.css not found. Run build first.");
process.exit(1);
}
const components = getComponentSizes();
// Calculate totals
const totalComponentBytes = Object.values(components).reduce((sum, c) => sum + c.bytes, 0);
// Create result object
const result = {
components,
componentsTotal: formatBytes(totalComponentBytes),
minified: formatBytes(minifiedSize),
timestamp: new Date().toISOString(),
uncompressed: formatBytes(uncompressedSize),
};
// Display results
console.log("Bundle Sizes:");
console.log("-".repeat(60));
console.log(
`Uncompressed (index.css): ${result.uncompressed.formatted}${previous ? showComparison(uncompressedSize, previous.uncompressed.bytes, "uncompressed") : ""}`,
);
console.log(
`Minified (heroui.min.css): ${result.minified.formatted}${previous ? showComparison(minifiedSize, previous.minified.bytes, "minified") : ""}`,
);
console.log(
`Components total: ${result.componentsTotal.formatted}${previous ? showComparison(totalComponentBytes, previous.componentsTotal?.bytes, "components") : ""}`,
);
console.log();
// Component breakdown (top 10 largest)
console.log("Top 10 Largest Components:");
console.log("-".repeat(60));
const sortedComponents = Object.entries(components).sort((a, b) => b[1].bytes - a[1].bytes);
for (const [name, size] of sortedComponents.slice(0, 10)) {
const prevSize = previous?.components?.[name]?.bytes;
const comparison = prevSize ? showComparison(size.bytes, prevSize, name) : "";
console.log(
` ${name.padEnd(30)} ${formatBytes(size.bytes).formatted.padStart(12)}${comparison}`,
);
}
console.log();
// Save results
fs.writeFileSync(outputFile, JSON.stringify(result, null, 2));
console.log(`Results saved to: bundle-size.json`);
// Summary comparison
if (previous) {
console.log();
console.log("=".repeat(60));
console.log("Summary vs Previous Measurement");
console.log("=".repeat(60));
const uncompressedDiff = uncompressedSize - previous.uncompressed.bytes;
const minifiedDiff = minifiedSize - previous.minified.bytes;
console.log(`Previous timestamp: ${previous.timestamp}`);
console.log(`Current timestamp: ${result.timestamp}`);
console.log();
console.log(
`Uncompressed change: ${uncompressedDiff > 0 ? "+" : ""}${formatBytes(uncompressedDiff).formatted}`,
);
console.log(
`Minified change: ${minifiedDiff > 0 ? "+" : ""}${formatBytes(minifiedDiff).formatted}`,
);
if (minifiedDiff < 0) {
const savedPercent = ((-minifiedDiff / previous.minified.bytes) * 100).toFixed(2);
console.log();
console.log(`🎉 Bundle size reduced by ${savedPercent}%!`);
}
}
}
main().catch((error) => {
console.error("Error:", error);
process.exit(1);
});