90 lines
3.2 KiB
TypeScript
90 lines
3.2 KiB
TypeScript
import { existsSync, readFileSync } from "node:fs";
|
|
import { join, resolve } from "node:path";
|
|
|
|
interface BuildReport {
|
|
frameworkVersion: string;
|
|
generatedAt: string;
|
|
adapter: string;
|
|
routes: Array<{
|
|
path: string;
|
|
source: string;
|
|
sourceBytes: number;
|
|
dynamicParams: string[];
|
|
optimization?: {
|
|
staticNodes: number;
|
|
reactiveRegions: number;
|
|
eliminatedBranches: number;
|
|
unusedState: string[];
|
|
unusedHandlers: string[];
|
|
constantProps: string[];
|
|
unusedLocalCssClasses: string[];
|
|
batchableStateUpdates: number;
|
|
memoizableComponents: string[];
|
|
preloadDependencies: string[];
|
|
serverOnlyModules: string[];
|
|
};
|
|
}>;
|
|
assets: Array<{ file: string; bytes: number }>;
|
|
measurements: Record<string, number>;
|
|
budgetViolations: Array<{ metric: string; budget: number; actual: number; overBy: number }>;
|
|
}
|
|
|
|
function bytes(value: number): string {
|
|
if (value < 1024) return `${value} B`;
|
|
if (value < 1024 * 1024) return `${(value / 1024).toFixed(1)} KB`;
|
|
return `${(value / 1024 / 1024).toFixed(2)} MB`;
|
|
}
|
|
|
|
export function runAnalyze(appRoot: string, args: string[]): boolean {
|
|
const root = resolve(appRoot);
|
|
const path = join(root, "dist", "build-report.json");
|
|
if (!existsSync(path)) {
|
|
console.error("WRN-BUILD-REPORT-MISSING: run `wrnexus build` before `wrnexus analyze`.");
|
|
return false;
|
|
}
|
|
const report = JSON.parse(readFileSync(path, "utf8")) as BuildReport;
|
|
if (args.includes("--json")) {
|
|
console.log(JSON.stringify(report, null, 2));
|
|
return report.budgetViolations.length === 0;
|
|
}
|
|
|
|
console.log(`WRNexus build analysis (${report.frameworkVersion})`);
|
|
console.log(` Generated: ${report.generatedAt}`);
|
|
console.log(` Adapter: ${report.adapter}`);
|
|
console.log(` Routes: ${report.routes.length}`);
|
|
console.log("\nMeasurements:");
|
|
for (const [metric, value] of Object.entries(report.measurements)) {
|
|
console.log(` ${metric.padEnd(18)} ${bytes(value)}`);
|
|
}
|
|
console.log("\nLargest assets:");
|
|
for (const asset of report.assets.slice(0, 12)) {
|
|
console.log(` ${bytes(asset.bytes).padStart(10)} ${asset.file}`);
|
|
}
|
|
console.log("\nCompiler optimizations:");
|
|
for (const route of report.routes.filter((item) => item.optimization)) {
|
|
const optimization = route.optimization!;
|
|
console.log(
|
|
` ${route.path}: ${optimization.staticNodes} static nodes, ${optimization.reactiveRegions} reactive regions, ${optimization.eliminatedBranches} branches removed`,
|
|
);
|
|
if (
|
|
optimization.unusedState.length ||
|
|
optimization.unusedHandlers.length ||
|
|
optimization.unusedLocalCssClasses.length
|
|
)
|
|
console.log(
|
|
` candidates: ${[...optimization.unusedState, ...optimization.unusedHandlers, ...optimization.unusedLocalCssClasses].join(", ")}`,
|
|
);
|
|
}
|
|
if (report.budgetViolations.length) {
|
|
console.log("\nBudget violations:");
|
|
for (const violation of report.budgetViolations) {
|
|
console.log(
|
|
` ✗ ${violation.metric}: ${bytes(violation.actual)} > ${bytes(violation.budget)}`,
|
|
);
|
|
}
|
|
} else {
|
|
console.log("\n ✓ No configured performance budget violations.");
|
|
}
|
|
return report.budgetViolations.length === 0;
|
|
}
|