Add bundle-size gate script for CI
This commit is contained in:
@@ -40,6 +40,9 @@ jobs:
|
||||
- name: Build both targets
|
||||
run: pnpm build:both
|
||||
|
||||
- name: Bundle size gate
|
||||
run: pnpm bundle-size
|
||||
|
||||
- name: Validate MF manifest
|
||||
run: |
|
||||
MANIFEST=$(find dist/remote -name "mf-manifest.json" | head -1)
|
||||
|
||||
+2
-1
@@ -18,7 +18,8 @@
|
||||
"test": "vitest run",
|
||||
"test:coverage": "vitest run --coverage",
|
||||
"lint": "eslint \"src/**/*.{ts,tsx}\" --max-warnings 0",
|
||||
"typecheck": "tsc --noEmit"
|
||||
"typecheck": "tsc --noEmit",
|
||||
"bundle-size": "node scripts/ci/bundle-size-gate.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/signalr": "^10.0.0",
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Bundle-size gate for CI.
|
||||
*
|
||||
* Finds all .js files in dist/remote/static/js/, measures their gzip size,
|
||||
* reports per-file and total, and fails if total exceeds the budget.
|
||||
*
|
||||
* Budgets:
|
||||
* - Total remote bundle: <= 2000 kB gzip
|
||||
*/
|
||||
|
||||
import { readFileSync, readdirSync, statSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { gzipSync } from "node:zlib";
|
||||
|
||||
const BUDGET_TOTAL_KB = 2000;
|
||||
const JS_DIR = "dist/remote/static/js";
|
||||
|
||||
function collectJsFiles(dir) {
|
||||
const results = [];
|
||||
let entries;
|
||||
try {
|
||||
entries = readdirSync(dir);
|
||||
} catch {
|
||||
return results;
|
||||
}
|
||||
for (const entry of entries) {
|
||||
const full = join(dir, entry);
|
||||
const stat = statSync(full, { throwIfNoEntry: false });
|
||||
if (!stat) continue;
|
||||
if (stat.isDirectory()) {
|
||||
results.push(...collectJsFiles(full));
|
||||
} else if (entry.endsWith(".js")) {
|
||||
results.push(full);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
const files = collectJsFiles(JS_DIR);
|
||||
|
||||
if (files.length === 0) {
|
||||
console.error(`No .js files found in ${JS_DIR}. Did the build run?`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let totalGzip = 0;
|
||||
const rows = [];
|
||||
|
||||
for (const file of files) {
|
||||
const raw = readFileSync(file);
|
||||
const gzipped = gzipSync(raw);
|
||||
const gzipKB = gzipped.length / 1024;
|
||||
totalGzip += gzipKB;
|
||||
rows.push({ file: file.replace("dist/remote/", ""), rawKB: (raw.length / 1024).toFixed(1), gzipKB: gzipKB.toFixed(1) });
|
||||
}
|
||||
|
||||
rows.sort((a, b) => parseFloat(b.gzipKB) - parseFloat(a.gzipKB));
|
||||
|
||||
console.log("\n--- Bundle Size Report ---\n");
|
||||
console.log(
|
||||
"File".padEnd(60) +
|
||||
"Raw (kB)".padStart(12) +
|
||||
"Gzip (kB)".padStart(12)
|
||||
);
|
||||
console.log("-".repeat(84));
|
||||
|
||||
for (const r of rows) {
|
||||
console.log(
|
||||
r.file.padEnd(60) +
|
||||
r.rawKB.padStart(12) +
|
||||
r.gzipKB.padStart(12)
|
||||
);
|
||||
}
|
||||
|
||||
console.log("-".repeat(84));
|
||||
console.log(
|
||||
"TOTAL".padEnd(60) +
|
||||
"".padStart(12) +
|
||||
totalGzip.toFixed(1).padStart(12)
|
||||
);
|
||||
console.log(`\nBudget: ${BUDGET_TOTAL_KB} kB gzip`);
|
||||
|
||||
if (totalGzip > BUDGET_TOTAL_KB) {
|
||||
console.error(`\nFAILED: Total gzip size ${totalGzip.toFixed(1)} kB exceeds budget of ${BUDGET_TOTAL_KB} kB`);
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log(`\nPASSED: ${totalGzip.toFixed(1)} kB / ${BUDGET_TOTAL_KB} kB budget`);
|
||||
}
|
||||
Reference in New Issue
Block a user