Private beta — the @z0devs packages are available to design partners while we harden for general availability.Try the live demoRequest access

Cross-bundler (Vite remote, Rspack host)

The four non-default settings that make a Vite-built remote load under an Rspack host, and the one that breaks it.

A host and its remotes do not have to share a bundler. A Vite-built remote loads under an Rspack host, and it is verified end to end in examples/vite-plugin.

It needs four non-default settings, plus one plausible-looking setting you must avoid. Skipping any of them produces a blank page rather than an error, so it is worth doing all four before debugging.

Prerequisite

Everything here assumes the subpath entrypoints are already shared on every participant, as in Installation. That is the requirement most likely to be the actual cause: an unshared react/jsx-runtime sends the Vite remote to its own React, and two Reacts fail in ways that look nothing like a config problem.

The Vite remote sets `import: false` — but only when federated

vite.config.ts
import { z0Federation } from "@z0devs/mf-config";
 
const IS_STANDALONE = process.env["IS_STANDALONE"] === "true";
 
federation(
  z0Federation({
    target: "vite",
    name: "vite_plugin",
    exposes: { "./init": "./src/init.tsx" },
    shellEntry: SHELL_ENTRY,
    router: true,
    standalone: IS_STANDALONE,   // gates `import: false`
  }),
);

target: "vite" emits all four interdependent settings — the swapped filename/varFilename container pair, manifest: true, the { type: "var" } shell remote, and import: false on the shares unless standalone. Equivalent to writing this by hand:

const sharedLib = { singleton: true, ...(IS_STANDALONE ? {} : { import: false as const }) };
const shared = { react: sharedLib, "react-dom": sharedLib, "react-router-dom": sharedLib };

Note import: false is applied only to the React set and react-router-dom — never to arbitrary extras, because a consume-only share the host does not provide hard-fails at load with Shared module '<pkg>' must be provided by host.

"No local fallback — the host must provide these." This is the one place Vite genuinely differs from Rspack: an Rspack remote that ships a fallback still resolves the host's copy at runtime, while @module-federation/vite binds its own fallback even when the host's is in the share scope and unified. Removing the fallback is what forces it onto the host's React — and it drops the dead weight too (the react-router-dom share chunk went 89 kB → 2.5 kB).

It must be conditional: standalone has no host to provide the shares, so leaving import: false on parks an unresolved entry in pendingShareLoads, which the entry bootstrap awaits before importing your app. Blank page, no error.

Emit a `var` container and register that one

vite.config.ts
federation({ filename: "esmRemoteEntry.js", varFilename: "remoteEntry.js", manifest: true });
the host's manifest entry
{ "service": { "id": "vite_plugin", "remoteEntry": ".../remoteEntry.js", "type": "global" } }

An ESM container evaluates its own module body on import(), so it self-initializes its share scope before a foreign host can hand over its own. A Webpack/Rspack container is inert until the host calls init(shareScope) — which is why those remotes never hit this. The var entry is a ~600-byte shim that dynamically imports the real ESM container, so you still ship exactly one container.

type: "global" on the manifest entry is what tells the host to treat it as a var container. Point the URL at remoteEntry.js, not esmRemoteEntry.js or mf-manifest.json.

Serve the built output, never the Vite dev server

The dev server injects @module-federation/dts-plugin's type-hints module into remoteEntry.js. That's Node code — it references module — so a foreign host's container init dies with RUNTIME-015: module is not defined. Use vite preview (or any static host) for the federated entry, and a separate port for dev:standalone.

Keep the share sets symmetric

Whatever you share on the remote, share on the host, and vice versa. A subpath shared on only one side is worse than sharing it nowhere: the two sides disagree about who provides React, and which one wins depends on load order.

Don't set shareStrategy: 'loaded-first'

Combined with import: false it lets the Vite remote win provider status for react-dom — which it has just declared it cannot provide — breaking the other remotes instead.

Why a federated build looks blank on its own port#

Opening the Vite remote's own URL shows nothing, and that is correct. A federated build exposes modules for a host to consume; it has no app to mount. Use dev:standalone on its own port when you want to look at the plugin's UI directly — see Standalone development.