Skip to content

feat: scaffold TanStack Module Federation plugin - #1

Merged
Nsttt merged 18 commits into
mainfrom
feat/tanstack-module-federation-scaffold
Oct 7, 2026
Merged

Nsttt merged 18 commits into
mainfrom
feat/tanstack-module-federation-scaffold

Conversation

@Nsttt

@Nsttt Nsttt commented Sep 17, 2026 •

Copy link
Copy Markdown
Member

Summary

Prepare this repository to publish @module-federation/tanstack for TanStack Start on Vite and Rsbuild/Rspack. The package now isolates each bundler adapter behind its own export and uses optional peer dependencies, so a project only installs and resolves the Module Federation plugin for its bundler.

Rslib builds conditional ESM and CommonJS outputs with matching declarations. Four runnable TanStack Start applications exercise Vite, Rsbuild, same-bundler federation, and reciprocal client-side manifest interoperability.

Changes

  • add the backward-compatible root and /vite adapter using @module-federation/vite
  • add /rsbuild using @module-federation/rsbuild-plugin, with browser script/JSONP compatibility and optional async-node CommonJS SSR output
  • default Rsbuild React and React DOM shares to eager singletons while preserving explicit overrides and caller immutability
  • replace tsdown with Rslib and publish .js/.d.ts ESM plus .cjs/.d.cts CommonJS artifacts
  • model Vite, Rsbuild, and both Module Federation adapters as isolated optional peers
  • name the Vite pair apps/vite-host and apps/vite-remote, alongside the new
    Rsbuild/Rspack host and remote apps
  • wire each host to both remote formats through manifests, with browser-only cross-bundler loading
  • verify manifests, entries, exposed CSS/types, package contents, compiler hooks, clean-consumer resolution, and live four-app HTTP startup
  • document the remaining same-bundler and cross-bundler SSR work in TODO.md
  • retain Changesets, preview publishing, semantic PR validation, and serialized trusted npm publishing

Testing

  • pnpm install --frozen-lockfile
  • pnpm format:check
  • pnpm exec tsc -b --force --pretty false
  • pnpm typecheck
  • pnpm test (22 tests, including live Vite/Rsbuild runtime shells)
  • pnpm pack:tanstack
  • actionlint
  • docs-list
  • git diff --check
  • Codex autoreview: clean final pass

Review notes

Accepted findings fixed during review: remote loader stale state, Vite hydration mismatch, and preserving ESM filenames when CommonJS forcing is disabled.

Two findings were rejected with direct build evidence: Rslib 1.0.1 accepts the --lib esm and --lib cjs selectors used here, and Rsbuild's environment-scoped Module Federation instances produce both browser and SSR manifests despite sharing the upstream plugin name.

Follow-up

Follow TODO.md for the Rsbuild SSR fixture, cross-bundler server-entry transport,
shared React identity, outage recovery, and version matrix. The current examples
and tests claim client manifest interoperability only, not cross-bundler SSR
remote rendering.

@pkg-pr-new

pkg-pr-new Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/module-federation/tanstack/@module-federation/tanstack@1

commit: 94601da

@Nsttt Nsttt self-assigned this Sep 18, 2026
- Make Rsbuild SSR federation opt-in and eager React host shares
- Add browser tests for hydration, remote interaction, and offline fallbacks
- Document support boundaries and update release workflows
The dev-only dynamic-remote-type-hints runtime plugin opens a WebSocket to the
federated type server. On CI runners the socket races host startup and logs
console errors, which fails the browser tests on every Node version. Type
generation is unchanged.
- pnpm 12.9.1: move settings from .npmrc to pnpm-workspace.yaml and let
  pnpm/action-setup read the version from packageManager
- TypeScript 7.0.2, @types/node 26, turbo 2.11, prettier 3.9.9, Rslib 1.0.3
- Module Federation 2.9.2 / @module-federation/vite 1.23.2, TanStack Start
  1.168.60, Router 1.170.41, Vite 8.3.2, Rsbuild 2.2.11
- Bump actions/checkout to v7.0.1 and pnpm/action-setup to v6.1.0
- Build the CJS library before ESM: under TypeScript 7 the CJS declaration
  pass renames every .d.ts in dist, including the ESM declarations
With server: true, a remote builds its Node container in a dedicated
environment, emitted to dist/client/ssr/ with only the exposed modules, and
advertises it in the browser manifest as ssrRemoteEntry. A host loads remotes
on the server from an async-node CommonJS build that keeps
dist/server/index.js as its entry.

- default experiments.asyncStartup and role-aware React sharing (eager for
  hosts, lazy for remotes and containers)
- give remotes publicPath "auto": TanStack Start derived "/" from
  server.base, so production remotes loaded chunks from the host's origin
- write host server output to disk in development, where async-node chunks
  are read from disk while the entry runs from memory
- add @module-federation/tanstack/node-entry-loader, which loads CommonJS
  remote entries without import(), unavailable in Rsbuild's dev runner
- add rsbuild-ssr-remote and rsbuild-ssr-host fixtures
The six apps now run twice, as development servers and through vite preview
and rsbuild preview. Each run checks server-rendered remote markup for
concurrent first requests, reachable manifest assets, hydration, remote
interactivity, console errors, and fallback and recovery while remotes are
offline.

Host loaders ignore a failed remote import, so an offline remote renders the
boundary fallback instead of failing the route. The development run does not
stop the Vite remote: @module-federation/vite's dev SSR transport then exits
the Vite host with an unhandled rejection.
Mark Rsbuild-to-Rsbuild SSR as supported, describe the server: true layout,
list known upstream limitations (standalone Vite remotes, the dev SSR crash,
remote CSS in SSR responses), narrow the documented Vite support to the
tested Vite 8, and restructure TODO.md around what 0.1.0 proves.
Each host provides its name through a React context from the shared
example-host-context package, and every remote card renders it. The browser
and server scenarios assert the host's name in each card, including the Vite
remote inside the Rsbuild host and server-rendered HTML.

A Vite host renders before lazy shares resolve, so the Vite host provides the
package eagerly; the README now states that rule for Vite hosts.
Checked by hand with Vite 7.3.7 and @vitejs/plugin-react 5.2: browser
federation works, but production SSR hosts fail to load remotes because the
server bundle keeps a virtual: import that Node rejects.
Every Vite and Rsbuild host and remote pair now renders on the server, in
development and production:

- Rsbuild hosts hand Vite remotes' ES module entries to
  @module-federation/vite's SSR loader, loaded with Node's require so it also
  works in Rsbuild's dev runner. The host's shared modules are registered
  under their node_modules paths, so a Vite dev server's externalized React is
  the host's React.
- Vite hosts load Rsbuild server containers, which move to
  remoteEntry.ssr.cjs next to the manifest (chunks stay in ssr/). In
  development, saved remote entries are imported through Node instead of
  Vite's module runner, which evaluated CommonJS as ES modules.

Hosts now render a remote again after a failed first load, such as a host
started during an outage. Four caches kept the failure: the Vite server
wrapper's single load attempt, Vite's dev module runner caching the wrapper's
rejected then export, Rspack's module cache keeping a failed remote module's
empty exports, and React.lazy. The adapters patch the first three;
@module-federation/tanstack/runtime adds lazyRemote for the last.

The runtime subpath also exports getRemoteStylesheets, which reads an
expose's CSS from the remote manifest for the route head, so server-rendered
remote markup is styled before JavaScript loads. The node entry loader throws
RemoteEntryError with the failing phase and the original cause, and bounds
its fetches.

The Vite adapter requires Vite 8 and stops with an error on older versions,
and handles the unawaited __mf_remote_pending export that exited dev servers
when a remote went offline.

The example scenarios cover cross-bundler SSR, styling with JavaScript
disabled, and hosts started during an outage. The docs describe the server
rendering pattern, the React 19 requirement, and the first npm publish.
Nsttt added 2 commits October 7, 2026 00:58
The Vite adapter's workarounds and TODO.md now point at module-federation/vite#1421-#1424, so each workaround can go once its fix ships.
…of dist

@module-federation/vite 1.23.3 ships the fixes reported from this package
(module-federation/vite#1421, #1422, #1425, #1426), so the Vite adapter drops
its workarounds: the remote wrapper retry and the dev temp-file import. The
Rsbuild server container no longer needs static named-export re-assignments.
Every remote now also runs as a standalone app, so the example scenarios test
the Vite remote standalone too, instead of marking it todo.

A host's Rsbuild dev server wrote its server bundle to dist/server, replacing a
production build that `rsbuild preview` serves. The dev bundle now goes to
node_modules/.cache/tanstack-start-federation/, and the development scenarios
check that dist/server is left alone.
@Nsttt
Nsttt merged commit 797fc94 into main Oct 7, 2026
6 checks passed
@Nsttt
Nsttt deleted the feat/tanstack-module-federation-scaffold branch October 7, 2026 16:27
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