Skip to main content
Version: Next

Module Federation

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).
  • 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) in order to ensure singleton dependencies only have one instance.

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:

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:

packages/app/src/moduleFederation.ts
import {
createInstance,
ModuleFederation,
} from '@module-federation/enhanced/runtime';
import { loadModuleFederationHostShared } from '@backstage/module-federation-common';

export async function initializeModuleFederation(): Promise<ModuleFederation> {
return createInstance({
name: 'app',
remotes: [
{
name: 'my_plugin',
entry: 'http://localhost:3001/mf-manifest.json',
},
],
shared: await loadModuleFederationHostShared(),
});
}

export async function loadRemote(
instance: ModuleFederation,
name: string,
): Promise<any> {
return await instance.loadRemote<any>(name);
}

The loadModuleFederationHostShared function loads all shared dependencies in parallel and returns them in the format expected by the Module Federation Runtime. By default it will throw if any shared dependency fails to load. You can pass an onError callback to handle errors gracefully instead:

const shared = await loadModuleFederationHostShared({
onError: error => console.error(error.message, error.cause),
});

Integration with Feature Loaders

Standard Module Federation runtime API integrates very well with frontend feature loaders, as shown in the example below:

packages/app/src/loader.tsx
import { createInstance } from '@module-federation/enhanced/runtime';
import { loadModuleFederationHostShared } from '@backstage/module-federation-common';
import { createFrontendFeatureLoader } from '@backstage/frontend-plugin-api';

export const moduleFederationLoader = createFrontendFeatureLoader({
async loader() {
const moduleFederationInstance = createInstance({
name: 'app',
remotes: [],
shared: await loadModuleFederationHostShared(),
});
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<any>(
'myFirstRemoteWith2ExposedModules/module1',
);
const myFirstRemoteModule2 = await moduleFederationInstance.loadRemote<any>(
'myFirstRemoteWith2ExposedModules/module2',
);
const mySecondRemoteModule = await moduleFederationInstance.loadRemote<any>(
'mySecondRemote',
);
return [
myFirstRemoteModule1.default,
myFirstRemoteModule2.default,
mySecondRemoteModule.default,
];
},
});

const app = createApp({
features: [moduleFederationLoader],
});

export default app.createRoot();

Note that, on top of the standard API, we plan to provide a more simplified way to configure module federation remotes.

Additionally, the dynamicFrontendFeaturesLoader provided in the @backstage/frontend-dynamic-feature-loader 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 package.