ci: Größenbudget des 3D-Chunks im Frontend-Build prüfen

scripts/check-3d-budget.mjs misst den three.js-Chunk (Marker
WebGLRenderer und __THREE__) mit gzip -9 und bricht den Image-Build ab,
wenn er 320 kB übersteigt oder nicht gefunden wird. Heute 299 kB.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-10-16 10:18:58 +02:00
1 parent 92c00b4453
commit 2207bd2e67
4 files changed
+118 -1

No files matched your search

@@ -0,0 +1,63 @@
// Bundle-Budget fuer den lazy geladenen Beute-3D-Chunk.
// Spec: docs/superpowers/specs/2026-10-15-beute-3d-design.md (§9.9 / L3).
// Laeuft nach `next build`; Exit 1, wenn der three.js-Chunk das Budget
// ueberschreitet oder gar kein three.js-Chunk gefunden wird.
import { readdirSync, readFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { gzipSync } from "node:zlib";
// Spec §9.9: hoechstens 320 kB gzip fuer den 3D-Chunk (1 kB = 1000 Byte).
export const BUDGET_BYTES = 320_000;
// Stabile Marker, die three.js auch minifiziert im Chunk behaelt (String-
// Literale): der Renderer-Name und die globale Mehrfach-Import-Markierung.
const THREE_MARKERS = ["WebGLRenderer", "__THREE__"];
export function isThreeChunk(source) {
return THREE_MARKERS.every((marker) => source.includes(marker));
}
/**
* Misst den groessten three.js-Chunk (gzip level 9) unter chunksDir.
* @returns {{ ok: boolean, file: string | null, gzipBytes: number, budget: number, message: string }}
*/
export function checkBudget(chunksDir, budget = BUDGET_BYTES) {
let largest = null;
for (const name of readdirSync(chunksDir)) {
if (!name.endsWith(".js")) continue;
const source = readFileSync(path.join(chunksDir, name));
if (!isThreeChunk(source.toString("latin1"))) continue;
const gzipBytes = gzipSync(source, { level: 9 }).length;
if (!largest || gzipBytes > largest.gzipBytes) largest = { file: name, gzipBytes };
}
if (!largest) {
return {
ok: false,
file: null,
gzipBytes: 0,
budget,
message: `Kein three.js-Chunk in ${chunksDir} gefunden (Marker: ${THREE_MARKERS.join(", ")}). Check kaputt oder Bundle umgebaut?`,
};
}
const ok = largest.gzipBytes <= budget;
return {
ok,
...largest,
budget,
message: `3D-Chunk ${largest.file}: ${largest.gzipBytes} Byte gzip, Budget ${budget} Byte${ok ? "" : " UEBERSCHRITTEN"}`,
};
}
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
const chunksDir = path.resolve(process.cwd(), ".next/static/chunks");
let result;
try {
result = checkBudget(chunksDir);
} catch (error) {
console.error(`check-3d-budget: ${chunksDir} nicht lesbar (zuerst next build?): ${error.message}`);
process.exit(1);
}
(result.ok ? console.log : console.error)(result.message);
process.exit(result.ok ? 0 : 1);
}