// 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); }