Skip to content

rwe-core vs whisker-host

An implementation-grounded comparison of rwe-core and whisker-host, scoped to what actually exists in both today: module federation, iframe embedding, local development, deployments, and host architecture. whisker-service-kit, the planned whisker-bff, AI tooling, and platform-wide CI orchestration are out of scope here.

Each system makes trade-offs appropriate for its operating model; neither is uniformly “better.” Where one is genuinely cleaner for a given concern, this doc says so.

rwe-core whisker-host
What is it? A 4-package monorepo: rwe-host, rwe-shell, rwe-demo-app (example only), rwe-internal-shared. Real widgets/apps live in other repos. A single host application (this repo also houses kits, WCL, and the manifest-publisher Lambda — but the host is one app).
Federation modules today One demo app, one auth/shell module, three widgets. Most user-facing apps are iframes. Federation modules and iframe consumers coexist by manifest field.
Iframe consumers Hand-listed in apps.json, hand-routed in App.tsx Discovered from the dynamic manifest registry; rendered uniformly by IframeRenderer
Module registry source of truth Three hand-edited files in rwe-host src/moduleManifest.ts → S3 → Lambda → DynamoDB, fetched at runtime
Operating model Host team owns the registry; module teams PR against rwe-host Module teams own their own repo/manifest/CI — no host PR to register
rwe-core whisker-host
Plugin @originjs/vite-plugin-federation v1.4 @module-federation/vite (Zack Jackson’s maintained Vite-native plugin)
Vite / React 6.4.x / React 18.3 7.x / React 19.2
Shared packages 2 (react, react-dom), array form — no singleton guarantee ~17, explicit singleton: true + eager: true for the framework + full MUI/Emotion stack + WCL
Host-to-module API retailUXShell/core exposes ~13 named exports + two export * re-exports remoteSafeSelectors.ts exposes exactly 4 named exports
Remote wiring vite.config.ts reads hand-edited registry files at build time Generated by scripts/generate-remote-loader.js from the live DynamoDB registry

Trade-off: rwe-core’s small share list means fewer compatibility constraints for small widgets, but any provider-shaped pattern (Theme, Auth, Snackbar) gets duplicated by default. Whisker’s wider, singleton share list is what makes shared WCL providers, Emotion caches, and MUI’s styled-engine actually work across modules — the cost is a larger surface for the host to keep in lockstep.

Trade-off: rwe-core’s ~13-export surface is powerful (a module can render <Shell>, use raw auth functions) but every export is a backwards-compat liability. Whisker’s 4-export surface is tighter to evolve but forces modules through WCL for shared components.

Federation as routing primitive vs registry

Section titled “Federation as routing primitive vs registry”
rwe-core whisker-host
Pages Hand-listed <Route> in App.tsx from app-registry.ts Computed from manifest pages[]; componentPath tells the host which export to load
Widgets WidgetHostProvider + explicit notifyReady/notifyHide SDK; per-operator dismiss persisted in localStorage Rendered from manifest widgets[]; no equivalent imperative SDK yet

rwe-core’s widget SDK (window.rweWidgetHost + notifyReady/notifyHide + per-operator dismiss) is the strongest single piece of prior art in either system — worth studying if whisker-host grows its own widget lifecycle convention (see Risks & improvements #16).

rwe-core has two distinct scenarios that are easy to conflate:

  • Demo (pnpm install && pnpm dev in rwe-core): host + shell + rwe-demo-app, a reference implementation, not a real module. One repo, minutes to running.
  • A real module: needs rwe-core and the module’s own repo, with no scaffolder — copy rwe-demo-app, hand-wire federation and CI yourself, then get a host-team PR merged to register it in app-registry.ts / widget-registry.ts / apps.json.

whisker-host assumes host and module live in separate repos from the start:

Terminal window
cd my-new-module
pnpm dev
# → generate + validate manifest → locate/auto-clone whisker-host
# → whisker:prep on host → build module (VITE_IS_LOCAL=true)
# → rebuild host's remote loader → build + start host on 3001
# → vite --watch + browser-sync proxying the host

whisker dev (test BFF) and whisker dev:prod (prod BFF) both run the host + your module locally while loading the rest of the platform from a BFF; VITE_BFF_URL overrides either to point at a local BFF instead. The manifest endpoint needs no auth either way.

Honest call-out: rwe-core is simpler only for “run the built-in demo.” For “run the host against a real module,” both systems need two repos — the difference is that whisker scaffolds and orchestrates the module side, while rwe-core gives you a demo app to copy and a registry file to edit by hand (plus a host-team PR).

rwe-core: one Jenkinsfile per package (host/shell/demo-app), all deploying to a single shared S3 bucket with a Lambda@Edge rewrite per prefix. External federated modules and iframe apps deploy independently but still need cross-team IAM coordination to add a new bucket prefix. Adding a new module/widget to production is 5–8 PRs against rwe-host, depending on type.

whisker-host: two automated tracks — see Manifest pipeline & deployment for the full mechanism. Federation bundles land in per-module S3 prefixes under one CloudFront; adding a new module to production is zero PRs against whisker-host, ever.

Concern rwe-core whisker-host
PRs to host to add a module 5–7 (federation page) / 1–3 (widget) / 4–7 (iframe) 0
Per-env admin enable/disable Manual code edit + redeploy enabled field flips via the (planned) whisker-config-panel; admin merge already preserves it
Origin allow-list Static union of apps.json Derived dynamically from the live registry
Schema validation at ingest None — TypeScript at host build time only ModuleManifestSchema.parse() in the Lambda, same schema the kit uses

Honest call-out: for a small team with ≤5 modules on the same cadence, rwe-core’s “edit a file and ship” model is genuinely faster — no Lambda to debug, no DLQ to triage. For many teams on independent cadences, the trade-off flips: every whisker module addition is a git push in the module’s own repo, at the cost of a Lambda + DynamoDB + DLQ the platform team has to run.

Boot order: rwe-core’s Shell (auth + nav) is itself a federated import — the host’s first paint depends on retailUXShell/remoteEntry.js being reachable. whisker-host’s auth lives in its own bundle; boot doesn’t wait on any federated remote, and manifests fetch in parallel.

Auth: both speak the same useAuth() contract shape (accessToken, loggedIn, login, logout, userInfo) and the same T.R.U.E-compatible iframe protocol — rwe-core’s auth lives inside the federated rwe-shell; whisker-host’s lives directly in the host.

Iframe handling: rwe-core wraps each app in a hand-written component and assigns window.onmessage = globally — which means only one iframe app can be active at a time; switching apps swaps handlers. whisker-host’s IframeRenderer is one manifest-driven component with a per-iframe message handler (useIframeMessageHandler), so multiple iframes can coexist safely. Targeting in Playwright uses the iframe’s title attribute (set to the module name), e.g. page.frameLocator('iframe[title="sim-web-ui"]').

Shared components: rwe-core has none — each team picks its own UI library, so federated modules can carry duplicate MUI/EDS copies with no shared theme. whisker-host has WCL, a single published, versioned component + provider library every module shares.

Observability: rwe-core calls NewRelic directly with hand-defined event names; whisker-host’s useModuleAnalytics(name) abstracts that away and auto-tags every event with its source module (see Analytics architecture).

What each system has that the other doesn’t

Section titled “What each system has that the other doesn’t”

rwe-core: a genuine widget SDK with per-operator dismiss persistence; a federated, in-principle-replaceable Shell; standalone auth helpers (getStoredUserInfo(), getAccessToken()) outside useAuth(); a lower operational footprint (no Lambda/DynamoDB/DLQ); a working in-repo demo.

whisker-host: a dynamic registry (no host PRs to add a module); a versioned, queryable host contract artifact; end-to-end schema validation (scaffold → commit → CI → ingest, one Zod schema); admin-aware enable/disable without a redeploy; a wider federation share list with explicit singleton/eager control; unified iframe handling with per-iframe handlers and Playwright-targetable markup; whisker doctor pre-flight; an AI-context surface (thundercats init/audit); a first-class shared component library.

  • Single team, ≤5 modules, same release cadence, comfortable owning your own federation/CI setup → rwe-core’s model is fine, arguably simpler operationally, and the widget SDK is the most mature piece of architecture in either system.
  • Many teams, many modules, independent cadences → whisker’s model is what the scale forces; the Lambda/DynamoDB/contract pipeline pays for itself once host-team PR review becomes the bottleneck.
  • You want a federated Shell other apps could swap in → rwe-core supports this; whisker doesn’t.
  • You want a host-as-platform with a shared component library, shared analytics, and admin-gated rollout → whisker.

T.R.U.E-compatible iframe protocol (interoperability between hosts), per-route error boundaries with NewRelic context, React Router 7, and federated modules consuming auth via a single named useAuth import.

An imperative widget SDK (notifyReady/notifyHide + per-operator dismiss) — already on the roadmap, see Risks & improvements #16.