From 86bb4321a9e00941a940183607dfdcddc739582c Mon Sep 17 00:00:00 2001 From: David Festal Date: Thu, 6 Nov 2025 15:44:22 +0100 Subject: [PATCH] Add and update documentation Signed-off-by: David Festal Assisted-by: Cursor --- .../building-apps/07-module-federation.md | 173 ++++++++++++++++++ docs/tooling/cli/02-build-system.md | 6 + docs/tooling/cli/03-commands.md | 1 + microsite/sidebars.ts | 1 + 4 files changed, 181 insertions(+) create mode 100644 docs/frontend-system/building-apps/07-module-federation.md diff --git a/docs/frontend-system/building-apps/07-module-federation.md b/docs/frontend-system/building-apps/07-module-federation.md new file mode 100644 index 0000000000..2a75163790 --- /dev/null +++ b/docs/frontend-system/building-apps/07-module-federation.md @@ -0,0 +1,173 @@ +--- +id: module-federation +title: Module Federation +sidebar_label: Module Federation +description: Using Module Federation in Backstage +--- + +## Introduction + +Module Federation is a feature that enables sharing code and dependencies between separately built JavaScript applications at runtime. In Backstage, module federation support allows you to: + +- Build your frontend application as a **module federation host** that can load remote modules at runtime +- Package individual plugins or bundles of several plugins as **module federation remotes** that can be loaded dynamically +- Share dependencies efficiently between the host and remotes to avoid code duplication + +This guide explains how to configure and build both module federation hosts and remotes in Backstage, and how to initialize module federation at runtime using the standard Module Federation Runtime API. + +## Overview + +### Module Federation Host vs Remotes + +In module federation terminology: + +- **Host**: The main frontend application that loads and consumes remote modules. In Backstage, this is your app package (typically `packages/app-next`). +- **Remote**: A separately built module that can be loaded by the host at runtime. In Backstage, these are typically plugin packages built as module federation remotes. + +### Shared Dependencies + +A critical aspect of module federation is **shared dependencies**. When a host loads remote modules, both need to share common dependencies (like React, React Router, Material-UI) to: + +- Avoid loading the same dependency multiple times +- Ensure singleton dependencies (like React) only have one instance +- Enable context sharing between host and remotes + +Backstage provides a list of default shared dependencies for common packages like React, React Router, and Material-UI. At build-time the `version` field is automatically resolved from your `package.json` files. + +## Building the Module Federation Host + +The module federation host is your main frontend application. By default, Backstage frontend applications include a default list of module federation shared dependencies. + +When building and bundling the frontend application, the CLI automatically: + +1. Resolves versions of the shared dependencies based on the monorepo dependencies +2. Adds an additional entrypoint to the frontend application bundle with the list of resolved runtime shared dependencies + +## Building Module Federation Remotes + +Plugin packages can be built as module federation remotes, allowing them to be loaded dynamically by a host application. + +### Using the CLI + +To build a plugin as a module federation remote, use the `--module-federation` option with the `package build` command: + +```bash +cd plugins/my-plugin +yarn build --module-federation +``` + +### Build Output + +When building a plugin as a module federation remote, the CLI: + +1. Resolves versions of the shared dependencies based on the monorepo dependencies (done automatically by the Rspack/Webpack module federation plugin) +2. Produces the bundle assets in the `dist` folder, including: + - a `mf-manifest.json` file which contains the module federation manifest + - a `remoteEntry.js` file which is the main entrypoint for the remote module + +## Runtime Usage + +To use module federation in your Backstage app, you need to initialize the Module Federation Runtime with the shared dependencies configuration. + +### Basic Usage + +Here's how to initialize module federation in your app, and load remote modules: + +```typescript title="packages/app/src/moduleFederation.ts" +import { + createInstance, + ModuleFederation, +} from '@module-federation/enhanced/runtime'; +import { buildRuntimeSharedUserOption } from '@backstage/module-federation-common'; + +export async function initializeModuleFederation(): Promise { + // Build the shared dependencies configuration + const { shared, errors } = await buildRuntimeSharedUserOption(); + + // Log any errors loading shared dependencies + if (errors.length > 0) { + for (const err of errors) { + console.error(err.message, err.cause); + } + } + + // Initialize Module Federation Runtime + return createInstance({ + name: 'app-next', + remotes: [ + { + name: 'my_plugin', + entry: 'http://localhost:3001/mf-manifest.json', + }, + ], + shared, + }); +} + +export async function loadRemote( + instance: ModuleFederation, + name: string, +): Promise { + return await instance.loadRemote(name); +} +``` + +### Integration with Feature Loaders + +Integration with frontend feature loaders is straightforward: + +```typescript title="packages/app/src/App.tsx" +import { createInstance } from '@module-federation/enhanced/runtime'; +import { buildRuntimeSharedUserOption } from '@backstage/module-federation-common'; +import { createFrontendFeatureLoader } from '@backstage/frontend-plugin-api'; + +const moduleFederationInstance = createInstance({ + name: 'app-next', + remotes: [], +}); + +const moduleFederationLoader = createFrontendFeatureLoader({ + async loader() { + moduleFederationInstance.registerShared( + (await buildRuntimeSharedUserOption()).shared, + ); + moduleFederationInstance.registerRemotes([ + { + name: 'myFirstRemoteWith2ExposedModules', + entry: + 'https://someCDN.org/myFirstRemoteWith2ExposedModules/mf-manifest.json', + }, + { + name: 'mySecondRemote', + entry: 'https://someCDN.org/mySecondRemote/mf-manifest.json', + }, + ]); + const myFirstRemoteModule1 = await moduleFederationInstance.loadRemote( + 'myFirstRemoteWith2Exposes/module1', + ); + const myFirstRemoteModule2 = await moduleFederationInstance.loadRemote( + 'myFirstRemoteWith2Exposes/module2', + ); + const mySecondRemoteModule = await moduleFederationInstance.loadRemote( + 'mySecondRemote', + ); + return [ + myFirstRemoteModule1.default, + myFirstRemoteModule2.default, + mySecondRemoteModule.default, + ]; + }, +}); + +const app = createApp({ + features: [moduleFederationLoader], +}); + +export default app.createRoot(); +``` + +The [`dynamicFrontendFeaturesLoader`](https://github.com/backstage/backstage/blob/master/packages/frontend-dynamic-feature-loader/src/loader.ts) provided in the [`@backstage/frontend-dynamic-feature-loader`](https://github.com/backstage/backstage/blob/master/packages/frontend-dynamic-feature-loader/README.md) package, which provides an integrated solution to load module federation remotes as dynamic frontend plugins, is a more complete example of a feature loader based on the module federation support. + +## Default Shared Dependencies + +Default shared dependencies are the same for both the host and remotes, and the list can be found in the [`@backstage/module-federation-common`](https://github.com/backstage/backstage/blob/master/packages/module-federation-common/src/defaults.ts) package. diff --git a/docs/tooling/cli/02-build-system.md b/docs/tooling/cli/02-build-system.md index c896aa1399..2c7826a149 100644 --- a/docs/tooling/cli/02-build-system.md +++ b/docs/tooling/cli/02-build-system.md @@ -270,6 +270,12 @@ that are exported from the package, leaving a much cleaner type definition file and making sure that the type definitions are in sync with the generated JavaScript. +### Building Module Federation Remotes + +Frontend plugin packages can be built as module federation remotes, which allows them to be loaded dynamically at runtime by a module federation host (typically your main frontend app). To build a package as a module federation remote, use the `--module-federation` option with the `package build` command. + +More details are given in the [Module Federation](../../frontend-system/building-apps/07-module-federation.md#building-module-federation-remotes) documentation. + ## Bundling The goal of the bundling process is to combine multiple packages together into a diff --git a/docs/tooling/cli/03-commands.md b/docs/tooling/cli/03-commands.md index 07f6dee879..b2d84ed09c 100644 --- a/docs/tooling/cli/03-commands.md +++ b/docs/tooling/cli/03-commands.md @@ -200,6 +200,7 @@ Options: --skip-build-dependencies Skip the automatic building of local dependencies. Applies to backend packages only. --stats If bundle stats are available, write them to the output directory. Applies to app packages only. --config Config files to load instead of app-config.yaml. Applies to app packages only. (default: []) + --module-federation Build a package as a module federation remote. Applies to frontend plugin packages only. ``` ## package lint diff --git a/microsite/sidebars.ts b/microsite/sidebars.ts index 5432edd6db..495b6fa469 100644 --- a/microsite/sidebars.ts +++ b/microsite/sidebars.ts @@ -583,6 +583,7 @@ export default { 'frontend-system/building-apps/configuring-extensions', 'frontend-system/building-apps/built-in-extensions', 'frontend-system/building-apps/plugin-conversion', + 'frontend-system/building-apps/module-federation', 'frontend-system/building-apps/migrating', ], ),