(feat) Federate most of the Module Federation runtime - #1853
Conversation
Effectively, this moves most of the Module Federation runtime into the app-shell, which means that apps built with a version of the rspack or webpack config after this PR is merged can no longer be run on _older_ versions of the app shell. RSpack bundles the Module Federation runtime into every app. This is desirable for some configurations, as it guarantees the app can always be loaded. However, it also adds some overhead to the initial chunk of every app (~85kB of which this PR reclaims ~50kB). As an unintended consequence, this now puts us fully on Module Federation 2.0. I've verified with testing that we, at least, don't duplicate the framework, even with older apps. Other federated libraries may accidentally be duplicated, but apart from SWR (where our framework already overwrites its internal cache), I'm not aware of any place where we are dependent on library-global state. Basically, this means this should be safe.
🦋 Changeset detectedLatest commit: a20fca3 The changes in this PR will be included in the next version bump. Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
This is now the "federation runtime". Note, though, that we're basically just creating global variables to hold it's state.
There was a problem hiding this comment.
Integration tests added (but not published)
| * globals are checked because `_FEDERATION_ERROR_CODES` is what describes the failure, and a runtime | ||
| * minor differing from the app shell's warns. `@openmrs/webpack-config` has a copy of this; keep them in step. | ||
| */ | ||
| function buildFederationRuntimeGuard(appName: string, expectedMinor: string | undefined) { |
There was a problem hiding this comment.
This is the bit of this PR I don't love, but it's the best way to get us a useful error is something goes wrong.
| 'This app was built with newer OpenMRS tooling than the app shell, so either upgrade @openmrs/esm-app-shell, ' + | ||
| "or rebuild this app with tooling matching the app shell.');}"; |
There was a problem hiding this comment.
This is the weird failure condition: deploying a newer app on an older app shell will not work since the older app shell will not provide the runtime the newer app expects.
| * globals are checked because `_FEDERATION_ERROR_CODES` is what describes the failure, and a runtime | ||
| * minor differing from the app shell's warns. `@openmrs/rspack-config` has a copy of this; keep them in step. | ||
| */ | ||
| function buildFederationRuntimeGuard(appName: string, expectedMinor: string | undefined) { |
There was a problem hiding this comment.
Same ugly code, but Webpack version
|
Size Change: -861 kB (-10.07%) 👏 Total Size: 7.69 MB 📦 View Changed
ℹ️ View Unchanged
|
There was a problem hiding this comment.
Pull request overview
This PR relocates most of the Module Federation runtime responsibility from individual remotes into the O3 app shell by switching remotes to consume the MF runtime from globals published by the shell. This reduces per-remote bundle overhead but introduces a compatibility requirement: remotes built with the updated tooling require a sufficiently new @openmrs/esm-app-shell that publishes the MF runtime globals.
Changes:
- Switch Rspack and Webpack shared configs to use
@module-federation/enhancedwithexternalRuntimeenabled and externals for MF helper packages. - Add app-shell runtime publishing (
publishFederationRuntime) and configure the shell’s Module Federation plugin toprovideExternalRuntime. - Add cross-package integration tests and fixtures to enforce consistent MF pins and verify that remote entries externalize the runtime as intended.
Reviewed changes
Copilot reviewed 21 out of 22 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| yarn.lock | Adds/updates Module Federation 2.8.1 and related dependencies used by the new runtime approach. |
| packages/tooling/webpack-config/src/index.ts | Uses @module-federation/enhanced for webpack remotes, enables externalRuntime, and injects runtime guard + externals. |
| packages/tooling/webpack-config/package.json | Pins @module-federation/enhanced and adds semver tooling used for runtime skew messaging. |
| packages/tooling/rspack-config/src/index.ts | Uses @module-federation/enhanced for rspack remotes, enables externalRuntime, and injects runtime guard + externals. |
| packages/tooling/rspack-config/package.json | Pins @module-federation/enhanced and adds semver tooling used for runtime skew messaging. |
| packages/tooling/integration-tests/vitest.config.ts | Adds a Vitest config suitable for fixture-based build tests (forks pool, no file parallelism). |
| packages/tooling/integration-tests/turbo.json | Defines Turborepo task inputs so config/fixture changes invalidate the integration-test cache correctly. |
| packages/tooling/integration-tests/tsconfig.json | Adds TypeScript config for the new integration-tests workspace package. |
| packages/tooling/integration-tests/src/module-federation-pins.test.ts | Ensures MF-related package pins match across app shell + tooling packages and remain exact versions. |
| packages/tooling/integration-tests/src/federation-runtime.test.ts | Builds real fixture remotes and verifies they externalize MF runtime globals and fail with a diagnosable error if missing. |
| packages/tooling/integration-tests/package.json | Introduces the private integration-tests workspace with the required bundler + MF dependencies. |
| packages/tooling/integration-tests/fixtures/remote-app/tsconfig.json | Adds TS config for the minimal remote-app fixture compiled by the integration tests. |
| packages/tooling/integration-tests/fixtures/remote-app/src/routes.json | Provides the minimal routes file expected by the shared build configs. |
| packages/tooling/integration-tests/fixtures/remote-app/src/index.ts | Provides a minimal ./start export to produce a valid federated remote entry. |
| packages/tooling/integration-tests/fixtures/remote-app/package.json | Defines the minimal fixture app package metadata used during test builds. |
| packages/shell/esm-app-shell/turbo.json | Adds Turborepo task inputs for app-shell test/coverage caching. |
| packages/shell/esm-app-shell/src/index.ts | Publishes the MF runtime globals before app initialization to ensure remotes can start. |
| packages/shell/esm-app-shell/src/federation-runtime.ts | Implements publishing of MF helper globals required by remotes. |
| packages/shell/esm-app-shell/src/federation-runtime.test.ts | Adds tests ensuring runtime publishing is configured and invoked early enough by the entrypoint. |
| packages/shell/esm-app-shell/rspack.config.js | Switches shell federation to @module-federation/enhanced and enables provideExternalRuntime + optimization. |
| packages/shell/esm-app-shell/package.json | Adds MF dependencies used by the shell runtime publisher. |
| package.json | Excludes the new private integration-tests workspace from CI publish scripts. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Greptile SummaryThis change moves most Module Federation runtime code into the app shell, configures Rspack and Webpack frontend modules to consume that shared runtime, and adds compatibility diagnostics for older app shells. Production builds of the remote fixture were exercised through both bundlers. With the app-shell runtime globals present, each remote started and exposed its container. With those globals absent, each remote stopped with the intended actionable error rather than failing silently or inside minified runtime code. No defects were found.
|
Co-authored-by: Dennis Kigen <kigen.work@gmail.com>
Co-authored-by: Dennis Kigen <kigen.work@gmail.com>
|
❌ The last analysis has failed. |
|



Requirements
Summary
Effectively, this moves most of the Module Federation runtime into the app-shell, which means that apps built with a version of the rspack or webpack config after this PR is merged can no longer be run on older versions of the app shell.
RSpack bundles the Module Federation runtime into every app. This is desirable for some configurations, as it guarantees the app can always be loaded. However, it also adds some overhead to the initial chunk of every app (~85kB of which this PR reclaims ~50kB).
As an unintended consequence, this now puts us fully on Module Federation 2.0.
I've verified with testing that we, at least, don't duplicate the framework, even with older apps. Other federated libraries may accidentally be duplicated, but apart from SWR (where our framework already overwrites its internal cache), I'm not aware of any place where we are dependent on library-global state. Basically, this means this should be safe.
Screenshots
Related Issue
Other