Module Federation

This chapter introduces how to build Module Federation output in Rslib.

Glossary

Module Federation is a JavaScript application divide-and-conquer architecture pattern (similar to server-side microservices) that allows you to share code and resources between multiple JavaScript applications (or Micro-Frontend).

Usage scenarios

Module federation has some typical usage scenarios, including:

  • Allows independent applications (called "Micro-Frontend" in the Micro-Frontend architecture) to share modules without having to recompile the entire application.
  • Different teams work on different parts of the same application without having to recompile the entire application.
  • Dynamic code loading and sharing between applications at runtime.

Module Federation can help you:

  • Reduce code duplication
  • Improve code maintainability
  • Reduce the overall size of the application
  • Improve application performance

Quick Start

First install the Module Federation Rsbuild Plugin (Rslib is based on Rsbuild).

npm
yarn
pnpm
bun
npm add @module-federation/rsbuild-plugin -D

Add the plugin to the rslib.config.ts file:

rslib.config.ts
import { pluginModuleFederation } from '@module-federation/rsbuild-plugin';
import { pluginReact } from '@rsbuild/plugin-react';
import { defineConfig } from '@rslib/core';

export default defineConfig({
  lib: [
    // ... other format
    {
      format: 'mf',
      output: {
        distPath: {
          root: './dist/mf',
        },
        // for production, add online assetPrefix here
        assetPrefix: 'http://localhost:3001/mf',
      },
      // for Storybook to dev
      dev: {
        assetPrefix: 'http://localhost:3001/mf',
      },
      // for Storybook to dev
      server: {
        port: 3001,
      },
      plugins: [
        pluginModuleFederation({
          name: 'rslib_provider',
          exposes: {
            // add expose here
          },
          // can not add 'remote' here, because you may build 'esm' or 'cjs' assets in one build.
          // if you want the Rslib package use remote module, please see below.
          shared: {
            react: {
              singleton: true,
            },
            'react-dom': {
              singleton: true,
            },
          },
        }),
      ],
    },
  ],
  output: {
    target: 'web',
  },
  plugins: [pluginReact()],
});

In this way, we have completed the integration of Rslib Module as a producer. After the construction is completed, we can see that the mf directory has been added to the product, and consumers can directly consume this package

In the above example we added a new format: 'mf' , which will help you add an additional Module Federation product, while also configuring the format of cjs and esm , which does not conflict.

However, if you want this Rslib Module to consume other producers at the same time, do not use the build configuration remote parameter, because in other formats, this may cause errors, please refer to the example below using the Module Federation runtime

Develop Module Federation remote module

Rslib support using Storybook to development Module Federation Rslib module.

1. Add Rslib mf dev command

package.json
{
  // ...
  "scripts": {
    "dev": "rslib mf dev"
  }
}
npm
yarn
pnpm
bun
npm run dev

Running the dev command can start the Module Federation development mode, enabling consumption by either your Host app or the Storybook app, along with Hot Module Replacement (HMR).

2. Install Storybook and Rsbuild Storybook addon

npm
yarn
pnpm
bun
npm add storybook storybook-addon-rslib @module-federation/storybook-addon -D

React App

npm
yarn
pnpm
bun
npm add storybook-react-rsbuild -D

Vue App

npm
yarn
pnpm
bun
npm add storybook-vue-rsbuild -D

This chapter will use React App as an example later.

3. Create .storybook/main.ts and add addons

.storybook/main.ts
1import { dirname, join } from 'node:path';
2import type { StorybookConfig } from 'storybook-react-rsbuild';
3
4function getAbsolutePath(value: string): any {
5  return dirname(require.resolve(join(value, 'package.json')));
6}
7
8const config: StorybookConfig = {
9  stories: [
10    '../stories/**/*.mdx',
11    '../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)',
12  ],
13  framework: {
14    name: getAbsolutePath('storybook-react-rsbuild'),
15    options: {},
16  },
17  addons: [
18    {
19      name: getAbsolutePath('storybook-addon-rslib'),
20      options: {
21        rslib: {
22          include: ['**/stories/**'],
23        },
24      },
25    },
26    {
27      name: '@module-federation/storybook-addon/preset',
28      options: {
29        // add your rslib module manifest here for storybook dev
30        // we have set dev.assetPrefix and server.port to 3001 in rslib.config.ts above
31        remotes: {
32          'rslib-module':
33            // you can also add shared here for storybook app
34            // shared: {}
35            'rslib-module@http://localhost:3001/mf/mf-manifest.json',
36        },
37      },
38    },
39  ],
40};
41
42export default config;

4. Start writing stories

stories/index.stories.tsx
1import React from 'react';
2// Load your remote module here, Storybook will act as the host app.
3import { Counter } from 'rslib-module';
4
5const Component = () => <Counter />;
6
7export default {
8  title: 'App Component',
9  component: Component,
10};
11
12export const Primary = {};

5. Add Module Federation types and stories into tsconfig.json.

tsconfig.json
{
  "compilerOptions": {
    // ...
    "paths": {
      "*": ["./@mf-types/*"]
    }
  },
  "include": ["src/**/*", ".storybook/**/*", "stories/**/*"]
}

6. Start Storybook app

There you go, start Storybook with npx storybook dev.

Consume other Module Federation modules

Because there are multiple formats in Rslib, if you configure the remote parameter to consume other modules during construction, it may not work properly in all formats. It is recommended to access through the Module Federation Runtime

First install the runtime dependencies

npm
yarn
pnpm
bun
npm add @module-federation/enhanced -D

Then consume other Module Federation modules at runtime, for example

import { init, loadRemote } from '@module-federation/enhanced/runtime';
import { Suspense, createElement, lazy } from 'react';

init({
  name: 'rslib_provider',
  remotes: [
    {
      name: 'mf_remote',
      entry: 'http://localhost:3002/mf-manifest.json',
    },
  ],
});

export const Counter: React.FC = () => {
  return (
    <div>
      <Suspense fallback={<div>loading</div>}>
        {createElement(
          lazy(
            () =>
              loadRemote('mf_remote') as Promise<{
                default: React.FC;
              }>,
          ),
        )}
      </Suspense>
    </div>
  );
};

This ensures that modules can be loaded as expected in multiple formats.

FAQs

If the Rslib producer is built with build, this means that the process.env.NODE_ENV of the producer is production . If the consumer is started in dev mode at this time, Due to the shared loading policy of Module Federation being version-first by default, there may be problems loading into different modes of react and react-dom (e.g. react in development mode, react-dom in production mode). You can set up shareStrategy at the consumer to solve this problem, but make sure you fully understand this configuration

pluginModuleFederation({
  // ...
  shareStrategy: 'loaded-first',
}),

Examples

Rslib Module Federation Example

  • mf-host: Rsbuild App Consumer
  • mf-react-component: Rslib Module, which is both a producer and a consumer, provides the module to the mf-host as a producer, and consumes the mf-remote
  • mf-remote: Rsbuild App Producer

Rslib Module Federation Storybook Example