Beta · 0.1.x

Module Federation for React shells

A composable toolkit for React shells that load trusted, curated micro-frontends at runtime — dynamic routing, namespaced contribution registries, a typed host↔plugin store contract, version gating, supply-chain integrity and standalone plugin dev, all on top of the official Module Federation runtime.

Install

pnpm add @z0devs/module-federation

Published to the private registry at https://npm.zonezero.dev. Point the @z0devs scope at it in .npmrc — see Installation.

React 18/19RspackWebpack 5ViteReact Router 6/7TypeScript

What it handles for you

The layer above Module Federation that every plugin host ends up writing by hand — the manifest, the version gate, the contribution registries, the store contract, the telemetry.

Runtime plugin loading

PluginRouteLoader reads a host-controlled manifest, validates every entry, gates on version + inter-plugin dependencies, then mounts each plugin's init once and holds the loader until all report ready.

Dynamic routing

Remotes register and unregister React Router routes at runtime through a namespaced registry. The router rebuilds itself; ids are auto-prefixed per remote.

Typed host↔plugin store

defineSharedStore on the host, createPluginStoreBridge on the plugin. One federation singleton, one contract, full inference on both sides — plus a narrowed slice type so a plugin only sees what it declared.

Capability-scoped registries

Nav, settings, toolbar, component, routes and onboarding registries. Hosts keep the full API; remotes receive an append-only, id-scoped view, so one plugin can't wipe another's contributions.

Version + integrity gating

Every plugin declares a shellApiVersion range checked against the shell's advertised API version, with block/warn/off policies. Optional remoteEntryHash detects a tampered bundle before it executes.

Standalone plugin dev

Run a plugin without booting the host. A fail-loud stub context throws on any shell value you read but never stubbed, so standalone drift surfaces in dev instead of production.

Observability built in

Per-plugin lifecycle records, load-duration metrics, a typed error union with a central sink, and a drop-in debug panel — all SDK-agnostic, ready to push into OpenTelemetry or Datadog.

Testing harness

Mock shell stores, fake auth adapters, manifest-entry builders and a render-with-shell helper, so plugin tests never need a live host.

Both sides of the contract

The host declares what it offers once. A plugin binds to it once. From then on the host’s hooks are just hooks — called inside the plugin with no provider, no Suspense, and no prop drilling. Follow useAuth across the three files below.

1Host declares it

shell/src/store/sharedStore.tsx
import { defineSharedStore } from "@z0devs/mf-shell-kit";
import { NavRegistry } from "@z0devs/mf-registry";
import { useAuth } from "./auth";

// Everything the host offers its plugins. Exposed over Module
// Federation as "shell/sharedStore".
export const sharedStore = defineSharedStore({
  values: { useAuth },
  registries: { nav: NavRegistry.api },
  features: { router: true },
});

2Plugin binds it

plugin/src/core/useShared.tsx
import { createPluginStoreBridge } from "@z0devs/mf-plugin-kit";
import LocalContext from "./LocalContext";

const bridge = createPluginStoreBridge({
  loadShellModule: () => import("shell/sharedStore"),
  LocalContextFallback: LocalContext, // stands in when running standalone
});

// Never rejects. The module graph waits here, so every consumer
// below reads the store synchronously — no provider, no Suspense.
await bridge.ready;
export const { useShellSharedStore } = bridge;

3Plugin reads it

plugin/src/HomePage.tsx
import { useShellSharedStore } from "./core/useShared";

// The host's hook, called as if it were local.
export default function HomePage() {
  const { values } = useShellSharedStore();
  const { user } = values.useAuth();

  return <p>Signed in as {user.email}</p>;
}

10 packages, composable

The primitives have zero dependencies on each other — take the dynamic router alone, or install the meta package and get everything.

PackagePurposeStandalone
@z0devs/mf-contextReact context factory + typed event bus that survive across federated bundles.yes
@z0devs/mf-routerDynamic React Router host — register and unregister routes at runtime.yes
@z0devs/mf-registryNamespaced contribution registries with capability scoping and flag gating.yes
@z0devs/mf-coreMF runtime plugin, remote loaders, manifest schema, integrity, telemetry.yes
@z0devs/mf-shell-kitHost orchestrator: defineSharedStore, PluginRouteLoader, OnboardingHost.needs siblings
@z0devs/mf-plugin-kitPlugin-side store bridge, standalone stubs, slice types, proxy client.yes
@z0devs/mf-configBuild-time bundler config: z0Shared / z0Federation emit the share map and plugin options as plain data.yes
@z0devs/mf-testingMock shell store, auth adapter, manifest builder, render harness.needs siblings
@z0devs/module-federationMeta package re-exporting the whole library — install one, get everything.yes
create-z0-pluginCLI scaffolder: a runnable federated plugin in one command.yes

Documentation

Guides, concepts and API reference — searchable with Ctrl K.

Start reading

From the blog

Module Federation is solved. Plugin hosts are not.

Why runtime micro-frontends still take months to get right, and what the ZoneZero Module Federation toolkit does about it.

Read the article →