Skip to content

Commit 5a6343b

Browse files
aleksigrongithub-actions[bot]
authored andcommitted
Add file-level bundle size diffs to CI
GitOrigin-RevId: b41274fd1a2e33834357fa13a8a3df534654d845
1 parent 9b03c98 commit 5a6343b

2 files changed

Lines changed: 148 additions & 0 deletions

File tree

rollup.config.esm.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,14 @@ function esmConfig(dir: string, workerSuffix: string, emitVisualizer = false): R
6868
sourcemap: false,
6969
title: 'GL JS ESM bundle',
7070
}),
71+
emitVisualizer && visualizer({
72+
filename: `${dir}bundle-stats.json`,
73+
template: 'raw-data',
74+
gzipSize: true,
75+
brotliSize: false,
76+
sourcemap: false,
77+
projectRoot: process.cwd(),
78+
}),
7179
],
7280
};
7381
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import {describe, expect, test} from 'vitest';
2+
import {
3+
diffBundleStats,
4+
diffBundleStatsData,
5+
formatBundleDiffMarkdown,
6+
parseBundleStats,
7+
type VisualizerData,
8+
} from '../../../internal/scripts/compare-bundle-stats.js';
9+
10+
const baseStats: VisualizerData = {
11+
version: 2,
12+
options: {gzip: true, brotli: false},
13+
nodeMetas: {
14+
'meta-a': {
15+
id: 'src/foo.ts',
16+
moduleParts: {'mapbox-gl': 'part-a1', core: 'part-a2'},
17+
},
18+
'meta-b': {
19+
id: 'src/bar.ts',
20+
moduleParts: {'mapbox-gl': 'part-b1'},
21+
},
22+
'meta-c': {
23+
id: 'src/removed.ts',
24+
moduleParts: {shared: 'part-c1'},
25+
},
26+
},
27+
nodeParts: {
28+
'part-a1': {renderedLength: 500, gzipLength: 200, metaUid: 'meta-a'},
29+
'part-a2': {renderedLength: 300, gzipLength: 150, metaUid: 'meta-a'},
30+
'part-b1': {renderedLength: 1000, gzipLength: 400, metaUid: 'meta-b'},
31+
'part-c1': {renderedLength: 800, gzipLength: 300, metaUid: 'meta-c'},
32+
},
33+
};
34+
35+
const headStats: VisualizerData = {
36+
version: 2,
37+
options: {gzip: true, brotli: false},
38+
nodeMetas: {
39+
'meta-a': {
40+
id: 'src/foo.ts',
41+
moduleParts: {'mapbox-gl': 'part-a1', core: 'part-a2'},
42+
},
43+
'meta-b': {
44+
id: 'src/bar.ts',
45+
moduleParts: {'mapbox-gl': 'part-b1'},
46+
},
47+
'meta-d': {
48+
id: 'src/new.ts',
49+
moduleParts: {shared: 'part-d1'},
50+
},
51+
},
52+
nodeParts: {
53+
'part-a1': {renderedLength: 500, gzipLength: 310, metaUid: 'meta-a'},
54+
'part-a2': {renderedLength: 300, gzipLength: 150, metaUid: 'meta-a'},
55+
'part-b1': {renderedLength: 1000, gzipLength: 300, metaUid: 'meta-b'},
56+
'part-d1': {renderedLength: 600, gzipLength: 250, metaUid: 'meta-d'},
57+
},
58+
};
59+
60+
describe('compare-bundle-stats', () => {
61+
test('parseBundleStats aggregates gzip per file across bundles', () => {
62+
const files = parseBundleStats(baseStats);
63+
expect(files.get('src/foo.ts')).toEqual({file: 'src/foo.ts', gzip: 350, rendered: 800});
64+
expect(files.get('src/bar.ts')).toEqual({file: 'src/bar.ts', gzip: 400, rendered: 1000});
65+
});
66+
67+
test('diffBundleStats reports added, removed, and changed files', () => {
68+
const head = parseBundleStats(headStats);
69+
const base = parseBundleStats(baseStats);
70+
const result = diffBundleStats(head, base);
71+
72+
const byFile = new Map(result.entries.map((entry) => [entry.file, entry]));
73+
74+
expect(byFile.get('src/foo.ts')).toMatchObject({
75+
baseGzip: 350,
76+
headGzip: 460,
77+
delta: 110,
78+
status: 'changed',
79+
});
80+
expect(byFile.get('src/bar.ts')).toMatchObject({
81+
baseGzip: 400,
82+
headGzip: 300,
83+
delta: -100,
84+
status: 'changed',
85+
});
86+
expect(byFile.get('src/new.ts')).toMatchObject({
87+
baseGzip: 0,
88+
headGzip: 250,
89+
delta: 250,
90+
status: 'added',
91+
});
92+
expect(byFile.get('src/removed.ts')).toMatchObject({
93+
baseGzip: 300,
94+
headGzip: 0,
95+
delta: -300,
96+
status: 'removed',
97+
});
98+
});
99+
100+
test('diffBundleStats filters changes below 20 bytes', () => {
101+
const head = parseBundleStats(headStats);
102+
const base = parseBundleStats(baseStats);
103+
head.set('src/tiny.ts', {file: 'src/tiny.ts', gzip: 115, rendered: 115});
104+
base.set('src/tiny.ts', {file: 'src/tiny.ts', gzip: 100, rendered: 100});
105+
const result = diffBundleStats(head, base);
106+
107+
expect(result.entries.find((entry) => entry.file === 'src/tiny.ts')).toBeUndefined();
108+
});
109+
110+
test('diffBundleStats sorts by absolute delta', () => {
111+
const result = diffBundleStatsData(headStats, baseStats);
112+
113+
expect(result.entries[0].file).toBe('src/removed.ts');
114+
expect(result.entries[1].file).toBe('src/new.ts');
115+
expect(result.entries[2].file).toBe('src/foo.ts');
116+
expect(result.entries[3].file).toBe('src/bar.ts');
117+
});
118+
119+
test('formatBundleDiffMarkdown includes table headers and status labels', () => {
120+
const result = diffBundleStatsData(headStats, baseStats);
121+
const markdown = formatBundleDiffMarkdown(result);
122+
123+
expect(markdown).toContain('### File-level changes (gzip, top 20)');
124+
expect(markdown).toContain('| File | Base | Head | Δ | Δ% |');
125+
expect(markdown).toContain('`src/new.ts` (new)');
126+
expect(markdown).toContain('`src/removed.ts` (removed)');
127+
expect(markdown).toContain('-0.30 kB');
128+
expect(markdown).toContain('+0.25 kB');
129+
});
130+
131+
test('rejects unsupported visualizer data versions', () => {
132+
const invalid: VisualizerData = {
133+
version: 99,
134+
options: baseStats.options,
135+
nodeMetas: baseStats.nodeMetas,
136+
nodeParts: baseStats.nodeParts,
137+
};
138+
expect(() => parseBundleStats(invalid)).toThrow(/Unsupported bundle-stats version/);
139+
});
140+
});

0 commit comments

Comments
 (0)