Skip to content

[docs-infra] Migrate demos to docs-infra - #48842

Merged
brijeshb42 merged 198 commits into
mui:masterfrom
brijeshb42:docs-infra/migrate-demos-trimmed
Oct 13, 2026
Merged

brijeshb42 merged 198 commits into
mui:masterfrom
brijeshb42:docs-infra/migrate-demos-trimmed

Conversation

@brijeshb42

@brijeshb42 brijeshb42 commented Jul 22, 2026 •

Copy link
Copy Markdown
Contributor

Extension of #47821
Comparison with original PR.

Do a review of here - https://diffshub.com/mui/material-ui/pull/48842

What this PR does

Migrates every Material UI docs demo to @mui/internal-docs-infra and rebuilds the demo UI in packages-internal/core-docs on top of it.

  • Demos move to docs/data/**/demos/<slug>/<Name>.tsx. The JavaScript variant is produced at build time, so the checked-in .js copies, .tsx.preview files and the formattedTSDemos script are removed (roughly 65k lines down to 20k).
  • DemoContent / DemoContainer / DemoToolbar in core-docs replace docs/src/modules/components/Demo*, using docs-infra's CodeHighlighter, useDemo, abstractCreateDemo and the loadPrecomputedCodeHighlighter loader.
  • The server HTML carries each demo's root source as plain text. The loading skeleton reserves the collapsed source height and renders the toolbar, so the swap to the live demo does not move the page.
  • Source deep links (#Demo.tsx, #Demo.jsx) expand the skeleton before hydration and land on the toolbar, as on master. The overflow menu keeps the JS and TS "copy link" items.
  • Focus regions (@focus-start / @focus-end) show up to 16 lines collapsed, matching the previous preview length.
  • Editing uses the textarea-based docs-infra editor and activates on first interaction with a demo; the highlighting engine and its wasm are not part of the page load.
  • Font preloads are reduced to IBM Plex Sans 400/600 and General Sans 600.
  • The regression suite globs follow the new layout (Number Field screenshots are renamed); e2e tests cover the toolbar, source links and editing.

Performance benchmark

Mobile Lighthouse on five representative documentation pages, master vs this PR:

  • Button
  • Autocomplete
  • Text Field
  • Dialog
  • Table

Summary

Metric Master This PR Change
Lighthouse performance score 67.6 77.4 +14.5%
FCP 1,547 ms 1,597 ms +3.2%
LCP 4,161 ms 2,494 ms -40.1%
TBT 592 ms 677 ms +14.4%
CLS 0.000 0.002 +0.002
Speed Index 4,485 ms 4,814 ms +7.3%
Requests 67 76 +13.7%
Total page transfer 1,577 KiB 1,507 KiB -4.4%
JS transfer 908 KiB 998 KiB +9.8%
HTML raw 575 KiB 655 KiB +13.9%
HTML brotli 55.9 KiB 61.4 KiB +9.9%

Overall values are the mean of the five route medians.

By route

Master → this PR.

Route Score LCP TBT Total transfer HTML brotli
Button 75 → 80 3.9 → 2.7 s 344 → 565 ms 1,546 → 1,503 KiB 52.7 → 55.5 KiB
Autocomplete 61 → 73 4.1 → 2.2 s 872 → 868 ms 1,939 → 1,912 KiB 62.1 → 80.1 KiB
Text Field 69 → 76 3.8 → 2.4 s 633 → 749 ms 1,400 → 1,359 KiB 70.2 → 70.7 KiB
Dialog 71 → 83 4.0 → 2.6 s 440 → 446 ms 1,391 → 1,250 KiB 42.7 → 46.1 KiB
Table 62 → 75 4.9 → 2.6 s 671 → 758 ms 1,609 → 1,512 KiB 51.8 → 54.7 KiB

Where the wins come from

  • LCP: the demo skeleton reserves the collapsed source height and renders the toolbar in the server HTML, so the intro text paints once and stays put. Only IBM Plex Sans 400/600 and General Sans 600 are preloaded; the other weights load on demand.
  • Total transfer: the code editor and its TextMate engine (onig.wasm, ~194 KiB) are no longer loaded on page load. Editing activates on first interaction with a demo. The plain-text source fallback no longer imports the highlighting engine ([docs-infra] Keep TextMate engine out of string fallback mui-public#1905, shipped in 0.13.1-canary.2). Fewer fonts are preloaded.

Known deltas against master

  • TBT +85 ms mean: the docs-infra runtime and per-demo hydration. Within run-to-run noise on Autocomplete and Dialog, measurable on Button.
  • JS transfer +10%: same cause.
  • HTML raw +14%, brotli +10%: every demo ships its toolbar and full source in the server HTML, so source deep links (#Demo.tsx) expand before hydration without a layout shift. The toolbar is ~250 B brotli per demo.
  • Requests +9: per-demo content chunks load lazily as demos scroll into view.
  • CLS 0.002 mean comes from one run where the site header's announcement text reflowed on a late font; the same shift occurs on master.
Methodology
  • Deployments (measured 2026-09-30):
    • Master: fcd2962a063c15f6911ac6669e511119c39c5efb (master--material-ui.netlify.app)
    • This PR: 9a0b66ff3cd5bc62ac37e3d0e93e280f151aebeb (deploy-preview-48842--material-ui.netlify.app)
  • Lighthouse 13.4.1, mobile defaults (Moto G Power emulation, 4x CPU slowdown, 1.6 Mbps / 150 ms RTT simulated), performance category only, headless Brave 153 (Chromium 153).
  • 3 runs per route and deployment, interleaved across deployments, all in one session. Medians per route, then means across routes.
  • Requests and transfer sizes from the Lighthouse network log of the same runs.
  • HTML sizes fetched with Accept-Encoding: identity and Accept-Encoding: br from the same deployments.

@brijeshb42 brijeshb42 added the scope: docs-infra Involves the docs-infra product (https://www.notion.so/mui-org/b9f676062eb94747b6768209f7751305). label Jul 22, 2026
@brijeshb42
brijeshb42 force-pushed the docs-infra/migrate-demos-trimmed branch from fbf854d to fd872bc Compare July 22, 2026 04:10
@code-infra-dashboard

code-infra-dashboard Bot commented Jul 22, 2026 •

Copy link
Copy Markdown

Deploy preview

Bundle size

Bundle Parsed size Gzip size
@mui/material 0B(0.00%) 0B(0.00%)
@mui/lab 0B(0.00%) 0B(0.00%)
@mui/private-theming 0B(0.00%) 0B(0.00%)
@mui/system 0B(0.00%) 0B(0.00%)
@mui/utils 0B(0.00%) 0B(0.00%)

Details of bundle changes


Check out the code infra dashboard for more information about this PR.

@github-actions github-actions Bot added PR: out-of-date The pull request has merge conflicts and can't be merged. and removed PR: out-of-date The pull request has merge conflicts and can't be merged. labels Jul 22, 2026
@brijeshb42
brijeshb42 force-pushed the docs-infra/migrate-demos-trimmed branch from c560c40 to 2a567f5 Compare July 24, 2026 14:44
@github-actions github-actions Bot added the PR: out-of-date The pull request has merge conflicts and can't be merged. label Jul 27, 2026
@github-actions github-actions Bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged. label Aug 7, 2026
@brijeshb42
brijeshb42 force-pushed the docs-infra/migrate-demos-trimmed branch from 200b696 to c50e33f Compare August 7, 2026 10:17

@mnajdova mnajdova left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some issues I noticed, clicking on View Sources on GH on a preview link is broken:

Not for the scope of this PR, I accidently noticed that for the Autocomplete, in some demos we have a dedicated file for the movies, while on other they are inlined. I guess we should have a dedicated file for files in all demos.

@brijeshb42

Copy link
Copy Markdown
Contributor Author

we are missing the JS/TS switcher

Note that for a demo, if js and ts contents remain same after transpiling from ts to js, the switcher won't show since it'll switch to the same content.

Checking others.

@silviuaavram silviuaavram left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will continue the review but what to mention a critical bug:

The Reset button functionality is broken. Even when you're in JS, and hit reset, it will bring back the TS version.

In the demo below I used quite a few clicks, but eventually it starts to bring the TS version.

Screen.Recording.2026-08-07.at.15.08.46.mov

@@ -4,6 +4,7 @@ import Autocomplete from '@mui/material/Autocomplete';
import Stack from '@mui/material/Stack';

export default function Playground() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a difference in text highlight colors, see options:

Before:

Image

After:

Image

@brijeshb42 brijeshb42 Aug 10, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The syntax highlighting difference is expected since the new one is better and has more detailed highlighting.

@github-actions github-actions Bot added PR: out-of-date The pull request has merge conflicts and can't be merged. and removed PR: out-of-date The pull request has merge conflicts and can't be merged. labels Aug 7, 2026
@brijeshb42
brijeshb42 force-pushed the docs-infra/migrate-demos-trimmed branch 2 times, most recently from 78da6fe to 6ebb7c5 Compare August 12, 2026 13:20
@github-actions github-actions Bot added the PR: out-of-date The pull request has merge conflicts and can't be merged. label Aug 12, 2026
@brijeshb42
brijeshb42 force-pushed the docs-infra/migrate-demos-trimmed branch from 671c392 to 84e177f Compare August 17, 2026 06:07
@github-actions github-actions Bot added PR: out-of-date The pull request has merge conflicts and can't be merged. and removed PR: out-of-date The pull request has merge conflicts and can't be merged. labels Aug 17, 2026
@brijeshb42
brijeshb42 force-pushed the docs-infra/migrate-demos-trimmed branch from 1f6dee4 to e394257 Compare August 17, 2026 08:20
@github-actions github-actions Bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged. label Aug 17, 2026
@brijeshb42
brijeshb42 force-pushed the docs-infra/migrate-demos-trimmed branch from e394257 to 3759f1e Compare August 17, 2026 08:32
@github-actions github-actions Bot added the PR: out-of-date The pull request has merge conflicts and can't be merged. label Aug 17, 2026
- Link "View the source on GitHub" to the authored `.tsx` file in the
  JavaScript view; no `.jsx` files exist after the migration. Fix the
  e2e test that asserted the `.jsx` URL.
- Point `updateIconSynonyms` at the relocated
  `material-icons/demos/search-icons/synonyms.js`.
- Enroll only `.js` templates in the regression fixtures and fail the
  bundle on duplicate `/<suite>/<name>` routes, so `.js`/`.tsx` twins
  cannot register the same route or screenshot path.
- Remove the redundant `joy` branch in `Dependencies.ts`.
- Migrate the menus and focus-visible demos that landed after the
  migration to the `demos/<slug>` layout, restoring their deploy
  preview links.
Add `initialExpanded` and `collapseToEmpty` to `DemoOptions` and the
AGENTS.md list; they are the options docs pages set. Mark
`defaultCodeOpen` as the legacy alias `createDemo` rewrites.
Stale duplicate of `SimpleDialogDemo.tsx.preview`, which the demo
migration folded into `@focus` markers.
Upstream mui#49086 updated the pre-migration `LimitTags.tsx`; the change
was lost when the demo moved to `demos/limit-tags/`. The sibling Chip
demos already carried it.
The docs vitest project also runs in Chromium, where `node:fs` and
`node:path` are unavailable; read the script via `import.meta.glob`
and import the synonyms module instead. Drop the stray blank line in
package.json that failed the prettier check.
Replace "Copy link to JavaScript/TypeScript source" with a single
"Copy link to source" that targets the `.tsx` anchor. There is only
one source file per demo now; the JS view is a transform of it. The
`.jsx` anchor ids stay rendered so existing links keep resolving.
The rebase onto master reverted 01e2a97 in Demo/DemoSandbox.tsx
and the DemoContent copy kept the pre-rebase code. Both import
utils/rtlBundle, which master deleted, so every test that touches
DemoContent fails to import and the docs build cannot resolve the
module. Mirror master: load @mui/stylis-plugin-rtl directly and key
the emotion cache on whether the plugin is active.
syntax.css reserved every source line for an off-screen collapsible
block whenever the demo root carried data-code-open. DemoContainer
sets that attribute from sourceVisible, which is also true for the
collapsed focus window, so every collapsed demo below the fold
reserved its full-source height (e.g. 478px instead of 302px on
react-button) and shrank once scrolled into view. On an anchored
load the content above the target collapsed after the scroll and
moved the heading off screen (theming/#theme-builder landed 522px
too low; react-button/#color reported CLS 0.03 vs 0 on prod).

Add data-code-expanded, driven by expanded, and key the rule on it.
The demo loader hid any focus region longer than twelve lines
(focusFramesMaxSize: 12 with oversizedFocus: 'hide'). The legacy docs
previewed JSX up to sixteen lines (babel-plugin-jsx-preview maxLines),
and the migrated focus markers were derived from those previews: 74
demos have a 13 to 16 line region, every one of them had a preview
before, and all rendered without a snippet (BasicSelect 152px instead
of 458px, StateTextFields 168px instead of 455px). Raise the limit to
match the legacy preview size.
The reservation rule sits behind an @supports check for typed attr()
(Chromium 133+). The legacy browser job runs an older engine where
the rule never applies, so computed contain-intrinsic-height is
"none" and the assertion fails. Skip the test where the feature is
missing, mirroring the CSS gate.
The live toolbar ships in the code-split DemoContent chunk, behind
the highlighter runtime and the demo's precompute data: ~32kB over
four chunks, requested after hydration and idle. Under a 4Mbps/100ms
link the buttons mount ~630ms after DOMContentLoaded, against ~190ms
on master, whose 8kB lazy toolbar is requested during hydration. On a
cold connection the gap reached 1.8s, so the toolbar visibly pops in.

Render the same DemoToolbar in DemoContentLoading with no-op
handlers, so the buttons are in the server HTML and the swap to the
live toolbar changes nothing. The toolbar root is inert and aria-busy
until then: clicks and focus wait for the live toolbar, assistive
tech does not see placeholder controls, and Playwright's click
actionability holds for the inert window. The reset e2e test gated
on the button's visibility to detect hydration; it now waits for
aria-busy to clear.
This reverts commit d4e806e.

The two items link to the demo file itself in either language: the
`.jsx` anchor lands on the JS transform and the `.tsx` anchor on the
TypeScript source. Master offers both, and the single ".tsx only"
item dropped the JavaScript permalink.

The skeleton toolbar added in baf8c0f passes the TS anchor only;
it has no transform data and its menu is inert until the live
toolbar mounts.
The `#<File>.tsx` / `.jsx` deep-link anchors were rendered at the
demo root, so landing on a copied source link scrolled the demo's
preview to the top of the viewport and left the source further down.
Master renders these anchors just before the toolbar, so the toolbar
and the expanded source sit at the top (react-button/#BasicButtons.js
lands with the toolbar at y=63 on prod vs the preview area on the
branch). The plain `#<Demo>` anchor stays at the demo top.
Landing on `#<File>.jsx` / `.tsx` opened the source only once the
live DemoContent mounted, about 2s after the anchor scroll on the
preview, so the demo grew 224px to 452px under the reader (CLS 0.05).
Master expands on hydration, ~230ms after the scroll.

Read the hash in DemoContentLoading and render the skeleton expanded
when it matches one of the demo's source anchors. The hash is null on
the server and during hydration, so this is a post-hydration
re-render rather than a mismatch. The `.js` language swap needs the
transform and still waits for the live demo.
The Carbon ad under the source was keyed on the viewer being
expanded, so landing on a source deep link added a 76px ad block
under the code (react-button/#BasicButtons.jsx: demo 476px on the
preview vs 400px on prod). Master sets showAd only from the toggle
click. Set adShown in handleToggleFrames instead of deriving it from
demo.expanded, so deep links and initialExpanded no longer show it.

The e2e covers the landing position, expansion and JS selection; the
ad assertion is exercised on deployed builds only, as ads are off in
dev and the flag is not exposed to the client.
9ac5a7a cut font preloads to IBM Plex 400 and General Sans 600.
Both 500 (app bar, nav, badges) and 600 (nav sections, the chip row
under every intro) render in the first viewport, so they swap in
after first paint and reflow it. Lighthouse mobile reports CLS
0.018–0.021 attributed to "web font loaded" on Button, Text Field
and Table; master, which preloads every weight, reports 0. Preload
the two weights (2 x ~55kB) and keep 700 deferred: above the fold
it is only the "⌘K" badge.
Every demo page shipped the Starry Night engine (vscode-textmate,
oniguruma, grammar JSON: chunks 59481 + 68628, 68kB gzipped) as
deferred scripts in the initial HTML, then fetched onig.wasm (194kB)
on the main thread before the highlighter worker fetched it again.
Master ships no WASM. Lighthouse mobile: 56 JS files / 758kB vs
30 / 590kB, TBT +20%.

Static chain: abstractCreateDemo > CodeHighlighter > prepareInitialSource
> buildStringFallback > parseSource/index.mjs (barrel) > parseSource.mjs
> @wooorm/starry-night. buildStringFallback only needs parsePlainText,
which does not touch the engine. Move createPlainTextRoot to its own
module and import it from there.

Temporary pnpm patch to measure the gain on the deploy preview; the
change belongs in mui-public.
Preloading 500 and 600 removed the CLS (0.021 to 0 on Button and
Text Field) but cost LCP: +0.3s on Button and +0.9s on Text Field in
every Lighthouse mobile run, as 110kB of fonts queued ahead of the
critical resources. The shift came from the 600 chip row; 500 is app
bar and desktop nav text, which is off screen on mobile. Keep only
600 preloaded to retain the CLS win at half the cost.
editActivation: 'eager' made every demo on a page warm the textarea
editor, the emphasis enhancer, the main-thread TextMate engine (with
its 194kB WASM) and the highlighter worker on mount, whether or not
the reader ever touched the code. On Lighthouse mobile that engine
boot lands on the main thread ~4s into every page load; master runs
no highlighter engine at all.

'interaction' defers all of it to the reader's first pointer-down or
keyboard entry on a code block. The highlighted code itself is
unchanged. The e2e spec now activates the editor before reading the
textarea, and asserts the code block rather than the editor where it
only needed proof that a preview is shown.
The two toolbar keyboard tests call focus() on a toolbar button as
soon as the page loads. Since baf8c0f the toolbar starts as an
inert skeleton, and an inert button cannot take focus, so on a slow
runner the arrow keys had nothing to move: test_e2e_website failed
on both while they pass locally against the same deploy. Gate them
on aria-busy clearing, as the reset test already does.
The roving-tabindex hook resolved the visible buttons on mount and on
every subtree mutation, calling `checkVisibility` per button. That
forces a style flush, and a page like Text Field hydrates 30 skeleton
toolbars plus 30 live ones. A CPU profile of the deploy preview put
~80ms of self time in that filter alone, the largest single delta
against master.

Render `tabIndex={-1}` on every control but the first, as master did
through `getControlProps`, and drop the mount effect and the
MutationObserver. Visibility is now only queried once focus enters a
toolbar, which is when it matters.
The canary ships mui/mui-public#1905, which moves the plain-text
source builder out of `pipeline/parseSource` so the string fallback no
longer pulls the TextMate engine into every page's initial JS. That
change was carried here as a pnpm patch on 0.12.1-canary.50; the
upstream release makes the patch redundant.

Nothing else between the two canaries touches modules this repo uses
(only `loadServerTypesMeta`, `syncTypes` and the CLI changed).
@github-actions

Copy link
Copy Markdown
Contributor

Cherry-pick PRs will be created targeting branches: v9.x

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs cherry-pick The PR should be cherry-picked to master after merge. scope: docs-infra Involves the docs-infra product (https://www.notion.so/mui-org/b9f676062eb94747b6768209f7751305). v9.x

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants