veza/apps/web/scripts/check-bundle-size.mjs

66 lines
2 KiB
JavaScript

#!/usr/bin/env node
/**
* Bundle size gate — TASK-DEBT-015
* Fails the build if initial JS bundle exceeds 200KB gzipped.
* Measures: index-*.js + vendor-react-*.js (critical path for first paint).
*/
import { readdirSync, readFileSync } from 'fs';
import { createGzip } from 'zlib';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const DIST = join(__dirname, '../dist_verification/assets');
const MAX_COMBINED_KB = 200;
function gzipSize(bytes) {
return new Promise((resolve, reject) => {
const chunks = [];
const gzip = createGzip();
gzip.on('data', (chunk) => chunks.push(chunk));
gzip.on('end', () => resolve(Buffer.concat(chunks).length));
gzip.on('error', reject);
gzip.end(bytes);
});
}
async function main() {
let dir;
try {
dir = readdirSync(DIST);
} catch (e) {
console.error('Run "npm run build" first. dist_verification/assets not found.');
process.exit(1);
}
const indexFile = dir.find((f) => f.startsWith('index-') && f.endsWith('.js'));
const vendorReactFile = dir.find((f) => f.startsWith('vendor-react-') && f.endsWith('.js'));
let totalGzip = 0;
const results = [];
for (const file of [indexFile, vendorReactFile]) {
if (!file) continue;
const path = join(DIST, file);
const bytes = readFileSync(path);
const sizeGzip = await gzipSize(bytes);
totalGzip += sizeGzip;
results.push({ file, kb: (sizeGzip / 1024).toFixed(1) });
}
const totalKb = totalGzip / 1024;
console.log('Bundle size (gzipped):');
results.forEach((r) => console.log(` ${r.file}: ${r.kb} kB`));
console.log(` Total (index + vendor-react): ${totalKb.toFixed(1)} kB`);
if (totalKb > MAX_COMBINED_KB) {
console.error(`\nFAIL: Initial bundle exceeds ${MAX_COMBINED_KB} kB (target: ORIGIN_PERFORMANCE_TARGETS §3.2)`);
process.exit(1);
}
console.log(`\nPASS: Bundle under ${MAX_COMBINED_KB} kB limit.`);
}
main().catch((e) => {
console.error(e);
process.exit(1);
});