feat: enforce web vitals performance budgets

This commit is contained in:
donghyeon-ka
2026-07-25 21:26:51 +09:00
parent 9d40724ab2
commit b1625252d5
11 changed files with 624 additions and 1 deletions
+93
View File
@@ -0,0 +1,93 @@
import { readFile, writeFile } from "node:fs/promises";
import { evaluateBundleBudget } from "../src/application/policies/performance-budgets.js";
const report =
/** @type {{
* outputs: Array<{ path: string, gzipBytes: number }>,
* [key: string]: unknown
* }} */ (
JSON.parse(await readFile("artifacts/performance/bundle.json", "utf8"))
);
const viteManifest =
/** @type {Record<string, { file: string, isEntry?: boolean }>} */ (
JSON.parse(await readFile("dist/.vite/manifest.json", "utf8"))
);
const budgets =
/** @type {{ initialJsGzipBytes: number, lazyChunkGzipBytes: number }} */ (
JSON.parse(await readFile("config/performance/budgets.json", "utf8")).bundle
);
const outputByPath = new Map(
report.outputs.map((output) => [output.path.replace(/^dist\//, ""), output]),
);
const initialFiles = new Set(
Object.values(viteManifest)
.filter((entry) => entry.isEntry)
.map((entry) => entry.file),
);
const lazyFiles = new Set(
Object.values(viteManifest)
.filter((entry) => !entry.isEntry && entry.file.endsWith(".js"))
.map((entry) => entry.file),
);
const initialJsGzipBytes = [...initialFiles].reduce(
(total, file) => total + (outputByPath.get(file)?.gzipBytes ?? 0),
0,
);
const lazyChunks = [...lazyFiles].map((file) => ({
path: file,
gzipBytes: outputByPath.get(file)?.gzipBytes ?? 0,
}));
const measurements = { initialJsGzipBytes, lazyChunks };
const result = evaluateBundleBudget(measurements, budgets);
const fixtures = [
{
name: "initial-js-over-budget",
passed:
!evaluateBundleBudget(
{
initialJsGzipBytes: budgets.initialJsGzipBytes + 1,
lazyChunks: [],
},
budgets,
).passed,
},
{
name: "lazy-chunk-over-budget",
passed:
!evaluateBundleBudget(
{
initialJsGzipBytes: 0,
lazyChunks: [
{
path: "fixture.js",
gzipBytes: budgets.lazyChunkGzipBytes + 1,
},
],
},
budgets,
).passed,
},
];
const passed = result.passed && fixtures.every((fixture) => fixture.passed);
const completedReport = {
...report,
measurements,
thresholds: budgets,
results: result,
fixtures,
passed,
};
await writeFile(
"artifacts/performance/bundle.json",
`${JSON.stringify(completedReport, null, 2)}\n`,
);
if (!passed) {
process.stderr.write("Bundle budget exceeded.\n");
process.exit(1);
}
process.stdout.write(
`Bundle budget: PASS (initial JS ${initialJsGzipBytes} / ${budgets.initialJsGzipBytes} gzip bytes)\n`,
);