React Bridge

@module-federation/bridge-react provides bridge utility functions for React applications:

  • createBridgeComponent: Used for exporting application-level modules, suitable for producers to wrap modules exported as application types
  • createRemoteComponent: Used for loading application-level modules, suitable for consumers to load modules as application types

View Demo

Installation

npm
yarn
pnpm
npm install @module-federation/bridge-react@latest

Examples

Exporting Application Type Modules

DANGER

Note: After using @module-federation/bridge-react, you cannot set react-router-dom as shared, otherwise the build tool will throw an exception. This is because @module-federation/bridge-react implements route control by proxying react-router-dom to ensure that inter-application routing works correctly.

In the producer project, assuming we need to export the application as an application type module using @module-federation/bridge-react, with App.tsx as the application entry point.

  • Step 1: First, create a new file export-app.tsx, which will be the file exported as an application type module. We need to use createBridgeComponent to wrap the root component of the application.
// ./src/export-app.tsx
import App from './src/App.tsx';
import { createBridgeComponent } from '@module-federation/bridge-react';

export default createBridgeComponent({
  rootComponent: App
});
  • Step 2: In the rsbuild.config.ts configuration file, we need to export export-app.tsx as an application type module
// rsbuild.config.ts
import { pluginModuleFederation } from '@module-federation/rsbuild-plugin';

export default defineConfig({
  plugins: [
    pluginReact(),
    pluginModuleFederation({
      name: 'remote1',
      exposes: {
        './export-app': './src/export-app.tsx',
      },
      shared: ['react', 'react-dom'],
    }),
  ],
});

At this point, we have completed the export of the application type module.

INFO

Why do application type modules need to be wrapped with createBridgeComponent? There are three main reasons:

  1. Support for cross-framework rendering. Components wrapped with createBridgeComponent will conform to the loading protocol of the application type consumer, making cross-framework rendering possible.
  2. Automatic injection of basename. Components wrapped with createBridgeComponent will automatically inject basename, ensuring that the producer application works correctly under the consumer project.
  3. Wrapping ErrorBoundary. Components wrapped with createBridgeComponent will be wrapped with ErrorBoundary to ensure that fallback logic is automatically entered when remote loading fails or rendering errors occur.

Loading Application Type Modules

Host

  • Step 1: In the rsbuild.config.ts configuration, we need to register remote modules, which is no different from other Module Federation configurations.
// rsbuild.config.ts
export default defineConfig({
  plugins: [
    pluginReact(),
    pluginModuleFederation({
      name: 'host',
      remotes: {
        remote1: 'remote1@http://localhost:2001/mf-manifest.json',
      },
    }),
  ],
});
  • Step 2: In the consumer project, we need to load the application type module. We use createRemoteComponent to load the application type module
// ./src/App.tsx
import React from 'react';
import { createRemoteComponent } from '@module-federation/bridge-react';
import styles from './index.module.less';

// Define FallbackErrorComp component
const FallbackErrorComp = ({ error, resetErrorBoundary }) => {
  return (
    <div>
      <h2>This is ErrorBoundary Component</h2>
      <p>Something went wrong:</p>
      <pre style={{ color: 'red' }}>{error.message}</pre>
      <button onClick={() => resetErrorBoundary()}>
        resetErrorBoundary(try again)
      </button>
    </div>
  );
};

// Define FallbackLoading component
const FallbackComp = <div data-test-id="loading">loading...</div>;

// Use createRemoteComponent to create remote component
const Remote1App = createRemoteComponent({
  // loader is used to load remote modules, e.g.: loadRemote('remote1/export-app'), import('remote1/export-app')
  loader: () => loadRemote('remote1/export-app'),
  // fallback is used for displaying components when remote module loading fails
  fallback: FallbackErrorComp,
  // loading is used for displaying components when loading remote modules
  loading: FallbackComp,
});

const App = () => {
  return (
    <BrowserRouter basename="/">
      <Routes>
        <Route path="/" Component={Home} />
        <Route
          path="/remote1/*"
          // Use Remote1App component, will be lazy loaded
          Component={() => (
            <Remote1App
              // Can set className and style, will be automatically injected into the component
              className={styles.remote1}
              style={{ color: 'red' }}
              // name and age are remote component props, will be automatically passed to the remote component
              name={'Ming'}
              age={12}
              // Can set ref, will be forwarded to the remote component, can get ref object to operate DOM
              ref={ref}
              // Use memoryRoute to control child application routing as memoryRouter, will not directly display URL in browser address bar
              memoryRoute={{ entryPath: '/detail' }}
            />
          )}
        />
      </Routes>
    </BrowserRouter>
  );
};

At this point, we have completed loading the application type module.

INFO
  1. The remote module exported by createRemoteComponent will automatically use the react-bridge loading protocol to load the module, making cross-framework rendering of applications possible.

  2. Additionally, createRemoteComponent will automatically handle module loading, module destruction, error handling, loading, routing, and other logic, allowing developers to focus solely on how to use the remote component.

  3. For remote modules exported through createRemoteComponent, you can use them like regular React components: passing className, style, props, ref, and other attributes will be automatically passed through to the remote component, making the user experience almost equivalent to using local components.

API Reference

createBridgeComponent

createBridgeComponent is used to wrap React components into remotely loadable modules.

/**
 * Create a remotely loadable React component
 * @param bridgeInfo - Bridge Component Config information
 * @returns Returns a function that returns an object containing render and destroy methods
 */
function createBridgeComponent<T>(
  bridgeInfo: ProviderFnParams<T>
): () => {
  render(info: RenderFnParams): Promise<void>;
  destroy(info: DestroyParams): Promise<void>;
};

/**
 * Bridge component configuration information
 */
interface ProviderFnParams<T> {
  /** Root component to be remotely loaded */
  rootComponent: React.ComponentType<T>;
  
  /** 
   * Custom render function for custom rendering logic
   * @param App - React element
   * @param id - DOM element or string ID
   * @returns React root element or Promise
   */
  render?: (
    App: React.ReactElement,
    id?: HTMLElement | string,
  ) => RootType | Promise<RootType>;
  
  /**
   * Custom function to create React root node
   * @param container - Container element
   * @param options - Options for creating root node
   * @returns React root node
   */
  createRoot?: (
    container: Element | DocumentFragment,
    options?: CreateRootOptions,
  ) => Root;
}

/**
 * Options for creating React root node
 */
interface CreateRootOptions {
  /** Add prefix to generated React IDs to avoid ID conflicts */
  identifierPrefix?: string;
  
  /** Callback function to handle recoverable errors during React rendering */
  onRecoverableError?: (error: unknown) => void;
  
  /** Transition callbacks for React 18 concurrent features */
  transitionCallbacks?: TransitionCallbacks;
}

createRemoteComponent

createRemoteComponent is used to load remote React components.

/**
 * Create remote React component
 * @param options - Remote component configuration options
 * @returns Returns a React component that can receive props and render remote component
 */
function createRemoteComponent<T, E extends keyof T = 'default'>(
  options: RemoteComponentParams<T, E>
): React.ForwardRefExoticComponent<
  React.PropsWithoutRef<RemoteComponentProps> & React.RefAttributes<HTMLDivElement>
>;

/**
 * Remote component configuration parameters
 */
interface RemoteComponentParams<
  T = Record<string, unknown>,
  E extends keyof T = keyof T
> {
  /** 
   * Function to load remote module
   * Example: () => loadRemote('remote1/export-app') or () => import('remote1/export-app')
   */
  loader: () => Promise<T>;
  
  /** Component displayed when loading remote module */
  loading: React.ReactNode;
  
  /** Error component displayed when loading or rendering remote module fails */
  fallback: React.ComponentType<{ error: Error }>;
  
  /** 
   * Specify module export name
   * Default is 'default'
   */
  export?: E;
}

/**
 * Remote component properties
 */
interface RemoteComponentProps<T = Record<string, unknown>> {
  /** Properties passed to remote component */
  props?: T;

  /** 
   * Memory route configuration, used to control child application routing as memoryRouter
   * Will not directly display URL in browser address bar
   */
  memoryRoute?: { entryPath: string };
  
  /** Base path name */
  basename?: string;
  
  /** Style */
  style?: React.CSSProperties;
  
  /** Class name */
  className?: string;
}

Usage Examples

Using export to specify module export

// remote
export const provider = createBridgeComponent({
  rootComponent: App
});

// host
const Remote1App = createRemoteComponent({
  loader: () => loadRemote('remote1/export-app'),
  export: 'provider', // Specify to use provider export
  fallback: FallbackErrorComp,
  loading: FallbackComp,
});

Using memoryRoute to control routing

function App() {
  return (
    <BrowserRouter basename="/">
      <Routes>
        <Route
          path="/remote1/*"
          Component={() => (
            <Remote1App
              className={styles.remote1}
              style={{ color: 'red' }}
              // Use memoryRoute to control child application routing as memoryRouter
              // Will not directly display URL in browser address bar
              memoryRoute={{ entryPath: '/detail' }}
              // Other properties will be passed to remote component
              props1={'props_value'}
              props2={'another_props_value'}
              ref={ref}
            />
          )}
        />
      </Routes>
    </BrowserRouter>
  );
}