Found while measuring #5266 (PR #5323). Filing rather than fixing — out of that card's scope.
.github/workflows/performance-budget.yml is named Bundle Analysis and its step is Check console performance budget, but what it actually measures is one file:
MAX_ENTRY_GZIP_KB=350
ENTRY_FILE=$(find "$DIST_DIR" -name 'index-*.js' -not -name '*.gz' -not -name '*.br'| head -1)
GZIP_BYTES=$(gzip -c "$ENTRY_FILE"| wc -c)
The entry chunk is not what a page load costs. index-*.js statically imports a closure of other chunks, and index.html cannot render without them — the browser fetches and parses all of it before the app runs.
Measured on 3fbbea1f3, walking static imports only from index.html:
| gzipped |
|---|
index-*.js alone — what the budget weighs | ~110 KB, comfortably under the 350 KB line |
| the full eager closure — 58 chunks | 3,957,301 bytes |
So the budget passes on ~2.8% of the payload it claims to govern.
This is not theoretical: #5266 was 89.0 KiB gzipped added to every console page load, and it landed in vendor-objectstack-*.js. The budget could not have caught it, and did not. Any regression that lands outside index-*.js — which is most of them, since advancedChunks deliberately routes vendor and workspace code into named chunks — is invisible to this gate.
Worth deciding rather than assuming: a budget over the whole eager closure would need a threshold set from the current 3.96 MB, which is a different conversation from the 350 KB entry number. Options include budgeting the closure as a whole, budgeting per named chunk, or ratcheting (fail only on growth vs. main). The ratchet form is probably the most useful and the least likely to need re-tuning, but that is a call for whoever picks this up.
PR #5323 adds a targeted build-time assertion for the specific @objectstack/lint case, which is why that one is now guarded. This issue is about the general gate.
Reproduction of the closure figure is in PR #5323 — eager closure = BFS from the <script type="module"> in dist/index.html over static imports only (es-module-lexer, d === -1), gzip read from the build's own emitted .gz siblings.
Found while measuring #5266 (PR #5323). Filing rather than fixing — out of that card's scope.
.github/workflows/performance-budget.ymlis named Bundle Analysis and its step isCheck console performance budget, but what it actually measures is one file:The entry chunk is not what a page load costs.
index-*.jsstatically imports a closure of other chunks, andindex.htmlcannot render without them — the browser fetches and parses all of it before the app runs.Measured on
3fbbea1f3, walking static imports only fromindex.html:index-*.jsalone — what the budget weighsSo the budget passes on ~2.8% of the payload it claims to govern.
This is not theoretical: #5266 was 89.0 KiB gzipped added to every console page load, and it landed in
vendor-objectstack-*.js. The budget could not have caught it, and did not. Any regression that lands outsideindex-*.js— which is most of them, sinceadvancedChunksdeliberately routes vendor and workspace code into named chunks — is invisible to this gate.Worth deciding rather than assuming: a budget over the whole eager closure would need a threshold set from the current 3.96 MB, which is a different conversation from the 350 KB entry number. Options include budgeting the closure as a whole, budgeting per named chunk, or ratcheting (fail only on growth vs.
main). The ratchet form is probably the most useful and the least likely to need re-tuning, but that is a call for whoever picks this up.PR #5323 adds a targeted build-time assertion for the specific
@objectstack/lintcase, which is why that one is now guarded. This issue is about the general gate.Reproduction of the closure figure is in PR #5323 — eager closure = BFS from the
<script type="module">indist/index.htmlover static imports only (es-module-lexer,d === -1), gzip read from the build's own emitted.gzsiblings.