React, from npm, as a single browser ES module — installed and bundled entirely in Rust. No Node, no CDN, no bundler at runtime.
cargo run --manifest-path examples/react-esm/Cargo.toml
# open http://127.0.0.1:8080/web-modules' normal path is buildless: it vendors a package's browser ES modules
into web_modules/ and lets the browser's import map resolve them (see the lit-element
and d3 examples). React can't be used that way — react and react-dom ship CommonJS
only: react's package entry is module.exports = … require("./cjs/react.production.js"),
which references module/require/process, none of which exist in a browser. (React 19
also dropped the old UMD builds.) So React has to be bundled into real ESM first.
That's what the opt-in bundle feature does, using rolldown — the embedded,
oxc-based Rust bundler. Still pure Rust, still no Node. The buildless react-umd example
next door shows the other answer: load React's UMD build as a global, no bundler at all.
- install —
web_modules::npm::install::node_modulesresolves and installsreact,react-domandzustand(the transitive tree, CommonJS and all) intoweb/node_modules/. This is a real "npm install", implemented in Rust. - bundle —
web_modules::bundle::bundleruns rolldown overweb/app.tsx+ thatnode_modules/tree, producing one self-contained browser ES module ($OUT_DIR/dist/app.js): CommonJS→ESM, JSX/TS transformed,process.env.NODE_ENVfolded to"production"(so React takes its production path and dev branches are dead-code-eliminated), React inlined exactly once, minified (~190 KB). - embed + serve —
main.rsembeds$OUT_DIR/distwithinclude_dir!and serves it viaFrontend::embedded(&DIST).router(). The shipped binary has no rolldown linked in — the app is already bundled.
The counter's state lives in a zustand store. zustand is a separate dependency that
itself imports React (it calls useSyncExternalStore); the component imports React too. For
the hooks in both to work, the bundle must contain exactly one React instance, shared by
the app and by zustand — a duplicate would throw "invalid hook call" the moment the two
hit different React dispatchers. rolldown deduplicates React to a single copy, so the app
just works. That diamond (app → react, app → zustand → react, resolved to one react) is
the point of the example, and the Playwright test is the live proof.
cd examples/react-esm
npm ci
npm run test:types # tsc --noEmit (oxc/rolldown strip types without checking them)
npm run test:e2e # Playwright: counter increments via the store, zero console errorsThe e2e watches for console errors and failed requests while it clicks the counter: no
errors ⇒ a single React instance. See tests/counter.spec.ts.