Skip to content

Commit ff99a27

Browse files
committed
Add bundle size budget, CodeQL and audit to CI
1 parent d7c0fe2 commit ff99a27

4 files changed

Lines changed: 87 additions & 1 deletion

File tree

.github/workflows/ci.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ name: CI
33
on:
44
pull_request:
55
push:
6-
branches: [main, dev]
6+
7+
concurrency:
8+
group: ${{ github.workflow }}-${{ github.ref }}
9+
cancel-in-progress: true
710

811
jobs:
912
check:
@@ -24,6 +27,12 @@ jobs:
2427
- name: Test
2528
run: npm test
2629

30+
- name: Bundle size
31+
run: npm run size
32+
33+
- name: Audit
34+
run: npm audit --audit-level=high
35+
2736
- name: No new dependencies
2837
run: |
2938
if [ -s package.json ] && [ "$(node -p "Object.keys(require('./package.json').dependencies ?? {}).length")" != "0" ]; then

.github/workflows/codeql.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: CodeQL
2+
3+
on:
4+
pull_request:
5+
push:
6+
schedule:
7+
- cron: "0 6 * * 1"
8+
9+
concurrency:
10+
group: ${{ github.workflow }}-${{ github.ref }}
11+
cancel-in-progress: true
12+
13+
jobs:
14+
analyze:
15+
runs-on: ubuntu-latest
16+
permissions:
17+
security-events: write
18+
actions: read
19+
contents: read
20+
steps:
21+
- uses: actions/checkout@v4
22+
23+
- uses: github/codeql-action/init@v3
24+
with:
25+
languages: javascript-typescript
26+
queries: security-and-quality
27+
28+
- uses: github/codeql-action/analyze@v3

build/check-bundle-size.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { readdirSync, statSync } from "node:fs";
2+
import { join } from "node:path";
3+
4+
const BUDGET_BYTES: Record<string, number> = {
5+
".css": 45_000,
6+
".js": 280_000,
7+
};
8+
9+
const ASSETS = join("dist", "assets");
10+
11+
function bundles(): { name: string; ext: string; bytes: number }[] {
12+
return readdirSync(ASSETS)
13+
.filter((name) => name.endsWith(".css") || name.endsWith(".js"))
14+
.map((name) => ({
15+
name,
16+
ext: name.slice(name.lastIndexOf(".")),
17+
bytes: statSync(join(ASSETS, name)).size,
18+
}));
19+
}
20+
21+
const found = bundles();
22+
if (found.length === 0) {
23+
console.error(`No bundles in ${ASSETS}. Run "npm run build" first.`);
24+
process.exit(1);
25+
}
26+
27+
const totals = new Map<string, number>();
28+
for (const { ext, bytes } of found) totals.set(ext, (totals.get(ext) ?? 0) + bytes);
29+
30+
let failed = false;
31+
for (const [ext, budget] of Object.entries(BUDGET_BYTES)) {
32+
const bytes = totals.get(ext) ?? 0;
33+
const percent = Math.round((bytes / budget) * 100);
34+
const label = `${ext.slice(1).toUpperCase().padEnd(3)} ${String(bytes).padStart(7)} / ${budget} bytes (${percent}%)`;
35+
if (bytes > budget) {
36+
failed = true;
37+
console.error(`over budget ${label}`);
38+
} else {
39+
console.log(`ok ${label}`);
40+
}
41+
}
42+
43+
if (failed) {
44+
console.error("");
45+
console.error("A bundle grew past its budget. Justify the growth and raise BUDGET_BYTES,");
46+
console.error("or find what was added. Adding a CSS framework once cost 19 kB unnoticed.");
47+
process.exit(1);
48+
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"dev": "vite",
88
"build": "tsc --noEmit && vite build",
99
"test": "node --test \"src/**/*.test.ts\"",
10+
"size": "node build/check-bundle-size.ts",
1011
"check": "npm run build && npm test",
1112
"preview": "vite preview"
1213
},

0 commit comments

Comments
 (0)