perf(studio): defer the source editor and markdown preview off first paint - #2943
Draft
miguel-heygen wants to merge 1 commit into
Draft
perf(studio): defer the source editor and markdown preview off first paint#2943miguel-heygen wants to merge 1 commit into
miguel-heygen wants to merge 1 commit into
Conversation
…paint The studio shipped one 3.7 MB eager entry chunk: 1,126,473 B gzip against the 600 KiB eagerEntryChunkGzipBytes budget. CodeMirror and the markdown renderer were the two largest blocks on that path and neither is needed to paint the shell. - React.lazy the source editor (@codemirror/*, @lezer/*) and the storyboard source view (marked, dompurify), copying the deferred-dependency shape of the mediabunny dynamic import in player/lib/mediaProbe.ts. - Add LazyPanel: one Suspense + error boundary whose fallback fills the panel's slot (h-full w-full flex-1) so opening the editor cannot shift the layout, and whose rejected-chunk state renders a visible retry instead of a blank panel. - manualChunks splits the React runtime and the remaining vendor code out of the app chunk so app-only deploys stop invalidating them for repeat visitors. - tests/e2e/studio-bundle-budget.mjs gates the built artifact against eagerEntryChunkGzipBytes with no browser. It measures the entry module plus its modulepreloaded chunks, not the entry file alone: the entry file alone now gzips to 444,432 B and would report a false pass while the browser still downloads 860,966 B before first paint. Eager gzip drops 1,126,473 B -> 860,966 B (-23.6%). Still 40% over the 600 KiB budget, so the gate fails by design; the remainder is a Node-only AST stack (@babel/parser, esprima, acorn, recast, ast-types) pulled into the browser bundle through @hyperframes/sdk, which is outside this unit.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR 4 of 5 · base
perf/studio-load-u7(#2942)The studio shipped as one eagerly-loaded 1,126,473 B gzip chunk with no
manualChunksand noReact.lazyanywhere. This cuts 265,507 B — −23.6% — and does not reach the target. That is stated plainly rather than papered over.What was deferred
LazyPanel— oneSuspense+ error boundary whose fallback fills its slot (h-full min-h-0 w-full flex-1,role="status") so opening a panel cannot shift layout, and which rendersrole="alert"+ Retry on a rejected chunk rather than a blank panel. Behind it: the whole@codemirror/*+@lezer/*family viaLazySourceEditor, andStoryboardSourceEditor(which is what statically pulledmarked+dompurify). PlusmanualChunksso app-only deploys stop invalidating vendor.bpm-detectiveneeded no work — already behind a dynamicimport()in core and absent from the entry chunk.Verified in a real browser against the production build: a cold load requests exactly three JS files; the
source-editorandmarkdownchunks are not fetched.The metric decision that matters
The gate measures the entry module plus its modulepreloaded chunks, not the entry file alone. With
manualChunksin place the entry file alone gzips to 444,432 B and would report a PASS while the browser still downloads 860,966 B before first paint. An entry-only metric would have turned this PR into a rename. There is a test asserting a split cannot game it.Why it is still 40% over
The remainder is not UI code. A Node-only AST/DOM stack is in the browser bundle, reachable through
@hyperframes/sdk'sopenComposition:@babel/parser527 KB,esprima296 KB,acorn233 KB,recast214 KB,ast-types202 KB,source-map107 KB,linkedom132 KB, pluscssom/htmlparser2/css-select/domutils~135 KB. Roughly 1.85 MB of an 8.07 MB module graph.Worth flagging how this was nearly missed: I checked for
linkedomby grepping the minified bundle for the string and concluded it was tree-shaken out. That is a false negative — minification strips package names. Sourcemaps show the eagerly-preloadedvendorchunk carrying 123linkedommodules, 31ast-types, 41 DOM-lib modules. Cutting that stack is worth roughly as much as everything in this PR and is its own unit.The budget is not revised down to meet what we ship today. PR 5 enforces a ratchet just above the current measurement so the 23.6% cannot be given back, and reports the remaining gap to the 600 KiB target on every run.