262 lines
6.8 KiB
JavaScript
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);
|
|
});
|