Repository navigation
[docs-infra] Migrate demos to docs-infra - #48842
Conversation
fbf854d to
fd872bc
Compare
Deploy previewBundle size
Check out the code infra dashboard for more information about this PR. |
c560c40 to
2a567f5
Compare
200b696 to
c50e33f
Compare
mnajdova
left a comment
There was a problem hiding this comment.
Some issues I noticed, clicking on View Sources on GH on a preview link is broken:
- https://github.com/mui/material-ui/blob/docs-infra/migrate-demos-trimmed/docs/data/material/components/autocomplete/demos/combo-box/ComboBox.tsx vs other PR https://github.com/mui/material-ui/blob/v9.3.1/docs/data/material/components/autocomplete/ComboBox.tsx
- we are missing the JS/TS switcher
- I also noticed on master we are missing gap between the Edit in Chat and JS/TS switcher, we can fix it

- the demo on (perma|PR|master|next) anchor is wrong, e.g. it generates https://deploy-preview-48842--material-ui.netlify.app/material-ui/react-autocomplete/#ComboBox, while it should be https://deploy-preview-48842--material-ui.netlify.app/material-ui/react-autocomplete/#combo-box
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.
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
left a comment
There was a problem hiding this comment.
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() { | |||
There was a problem hiding this comment.
The syntax highlighting difference is expected since the new one is better and has more detailed highlighting.
78da6fe to
6ebb7c5
Compare
671c392 to
84e177f
Compare
1f6dee4 to
e394257
Compare
e394257 to
3759f1e
Compare
- 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).
|
Cherry-pick PRs will be created targeting branches: v9.x |


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-infraand rebuilds the demo UI inpackages-internal/core-docson top of it.docs/data/**/demos/<slug>/<Name>.tsx. The JavaScript variant is produced at build time, so the checked-in.jscopies,.tsx.previewfiles and theformattedTSDemosscript are removed (roughly 65k lines down to 20k).DemoContent/DemoContainer/DemoToolbarincore-docsreplacedocs/src/modules/components/Demo*, using docs-infra'sCodeHighlighter,useDemo,abstractCreateDemoand theloadPrecomputedCodeHighlighterloader.#Demo.tsx,#Demo.jsx) expand the skeleton before hydration and land on the toolbar, as onmaster. The overflow menu keeps the JS and TS "copy link" items.@focus-start/@focus-end) show up to 16 lines collapsed, matching the previous preview length.Performance benchmark
Mobile Lighthouse on five representative documentation pages,
mastervs this PR:Summary
Overall values are the mean of the five route medians.
By route
Master → this PR.
Where the wins come from
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 in0.13.1-canary.2). Fewer fonts are preloaded.Known deltas against
master#Demo.tsx) expand before hydration without a layout shift. The toolbar is ~250 B brotli per demo.master.Methodology
fcd2962a063c15f6911ac6669e511119c39c5efb(master--material-ui.netlify.app)9a0b66ff3cd5bc62ac37e3d0e93e280f151aebeb(deploy-preview-48842--material-ui.netlify.app)Accept-Encoding: identityandAccept-Encoding: brfrom the same deployments.