Skip to content

perf(studio): defer the source editor and markdown preview off first paint - #2943

Draft
miguel-heygen wants to merge 1 commit into
perf/studio-load-u7from
perf/studio-load-u5
Draft

perf(studio): defer the source editor and markdown preview off first paint#2943
miguel-heygen wants to merge 1 commit into
perf/studio-load-u7from
perf/studio-load-u5

Conversation

@miguel-heygen

Copy link
Copy Markdown
Collaborator

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 manualChunks and no React.lazy anywhere. This cuts 265,507 B — −23.6% — and does not reach the target. That is stated plainly rather than papered over.

eager gzip files
before 1,126,473 B 1
after 860,966 B 3 (entry 444,432 + react-vendor 61,378 + vendor 355,156)

What was deferred

LazyPanel — one Suspense + 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 renders role="alert" + Retry on a rejected chunk rather than a blank panel. Behind it: the whole @codemirror/* + @lezer/* family via LazySourceEditor, and StoryboardSourceEditor (which is what statically pulled marked + dompurify). Plus manualChunks so app-only deploys stop invalidating vendor.

bpm-detective needed no work — already behind a dynamic import() 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-editor and markdown chunks are not fetched.

The metric decision that matters

The gate measures the entry module plus its modulepreloaded chunks, not the entry file alone. With manualChunks in 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's openComposition: @babel/parser 527 KB, esprima 296 KB, acorn 233 KB, recast 214 KB, ast-types 202 KB, source-map 107 KB, linkedom 132 KB, plus cssom/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 linkedom by 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-preloaded vendor chunk carrying 123 linkedom modules, 31 ast-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.

…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant