docs(public-docsite-v9): add native ESM examples (browser import map + Node) - #36682
Open
Martin Hochel (Hotell) wants to merge 6 commits into
Open
docs(public-docsite-v9): add native ESM examples (browser import map + Node)#36682Martin Hochel (Hotell) wants to merge 6 commits into
Martin Hochel (Hotell) wants to merge 6 commits into
Conversation
Adds a standalone, bundler-free HTML page that loads @fluentui/react-components from esm.sh via a browser import map, plus a Concepts > Developer docs page that explains it and embeds it live.
📊 Bundle size report✅ No changes found |
|
Pull request demo site: URL |
Complements the browser import map example with a runnable .mjs script that server-renders a Fluent UI Button via the package's import export condition, plus a resolve hook working around tabster missing an exports map.
Running the example in place inside the monorepo failed: it resolves to the workspace's older hoisted deps. Renames the hook to esm-compat-hook.mjs, adds a use-sync-external-store/shim redirect, and documents that a fresh install of published packages is required.
The repo resolves use-sync-external-store@1.6.0, which has a proper exports map covering ./shim; the earlier ERR_UNSUPPORTED_DIR_IMPORT was from a stale install, not a real packaging gap. Simplifies the hook back to the tabster redirect and documents react-icons as the reason the example cannot run in place inside the monorepo.
References microsoft/tabster#588 in the hook, the docs page, so the workaround can be removed once it ships. Icons bump is handled in a separate PR.
Martin Hochel (Hotell)
marked this pull request as ready for review
September 2, 2026 09:53
| return h( | ||
| FluentProvider, | ||
| { theme: webLightTheme }, | ||
| h('p', null, `Clicked ${count} times`), |
Contributor
There was a problem hiding this comment.
Would it be worth making the count a live region (for example, role="status" )?
Since focus remains on the button, I don’t think the updated count will be reliably announced by screen readers.
I realize this is a minimal demo, but it may also be copied as an example pattern.
| return h( | ||
| FluentProvider, | ||
| { theme: webLightTheme, style: { padding: 24 } }, | ||
| h('p', null, `Clicked ${count} ${count === 1 ? 'time' : 'times'}`), |
Contributor
There was a problem hiding this comment.
Same pattern as in the documentation snippet.
If we add live-region semantics there, could we update this runnable example to match?
Paul Mardling (PaulGMardling)
approved these changes
Sep 2, 2026
Paul Mardling (PaulGMardling)
left a comment
Contributor
There was a problem hiding this comment.
Looks good overall.
I left one non-blocking accessibility suggestion about announcing the updated count to screen readers. 🚀
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.
Previous Behavior
There was no example showing that Fluent UI v9 can run without a bundler. All docsite demos are React stories compiled by webpack, so the ESM-first packaging (
"type": "module"+ duallib//lib-commonjs/output) had no user-facing proof point.New Behavior
Adds two runnable, buildless examples — browser and Node — plus a docs page explaining both. Neither uses JSX (it can't be parsed without a compiler), so both build elements with
const h = React.createElement.Browser —
public/examples/native-esm.htmlA single static HTML file, served via
staticDirs, that renders a themed Fluent UI Button with no bundler, nonode_modules, and no build step:<script type="importmap">mapsreact,react-dom/clientand@fluentui/react-componentsto esm.sh, which rewrites the bare specifiers inside the ~70 transitive@fluentui/*/@griffel/*deps so the browser can resolve them.?external=react,react-domkeeps the CDN from inlining a second React copy (which would break hooks andFluentProvider's context).Node —
public/examples/native-esm-node.mjsA plain
.mjsscript that server-renders the same Button. Node needs no import map — it picks the ESM build through theimportexport condition — and the output is a full HTML document with Griffel styles extracted viarenderToStyleElements.Node ESM resolution gaps found
Node's resolution is stricter than any bundler's, and this example surfaced two issues. Both are already being addressed elsewhere — neither blocks this PR.
tabster(via@fluentui/react-tabster) declares"type": "module"and ships ESM indist/esm/, but has noexportsmap and a CommonJSmain, so Node loads the CJS entry:Reproduces on the latest release,
tabster@8.8.0. Fix in flight upstream: microsoft/tabster#588. Until it ships and the dependency is bumped, the example includespublic/examples/esm-compat-hook.mjs, a small resolve hook redirecting the specifier to tabster's ESM build; the hook and the--importflag can then be deleted. Bundlers are unaffected since they honor themodulefield Node ignores.@fluentui/react-iconsmust be reasonably current. Older builds — including2.0.311, the version this repo's lockfile currently pins — emit extensionless relative imports (export * from './icons/chunk-0') which Node rejects withERR_MODULE_NOT_FOUND. Fixed in current releases (2.0.339emits./icons/chunk-0.js); the bump is coming in a separate PR. Until then the Node example cannot be run in place inside this monorepo — the file header says so and gives copy-into-an-empty-folder instructions.Both are linked from the docs page so the workaround can be removed once the fixes land.
Docs
src/Concepts/NativeEsm.mdx(Concepts > Developer > Native ESM) covers both examples, with a live embed of the browser one, the duplicate-React pitfall, the gaps above, and caveats (request waterfall, no tree shaking, no build-time Griffel optimization, manual version pinning). Registered in the nav ordering in.storybook/preview.js.Verification
--colorBrandBackgroundresolves, state updates work, zero console errors. Network trace confirms exactly one React instance and per-package unbundled fetches for the whole v9 graph.npm i react react-dom @fluentui/react-components→@fluentui/react-icons@2.0.339,tabster@8.8.0,use-sync-external-store@1.6.0; Node 22.21.1) — emits a valid document containingfui-Buttonmarkup anddata-make-styles-bucketstyle tags. Without the hook it still fails ontabster, confirming the workaround is load-bearing..mjsfiles are reachable throughstaticDirs(200), so the docs links resolve in the deployed site.No change file —
@fluentui/public-docsite-v9is a private app.Related Issue(s)
nodeexport condition #36327tabster: fix: add exports map so ESM consumers resolve to the ESM build tabster#588