React Bridge(for React v16-v19)
@module-federation/bridge-react
提供了用于 React 应用的 bridge 工具函数:
createBridgeComponent
:用于导出应用级别模块,适用于生产者包装其作为应用类型导出的模块
createRemoteComponent
:用于加载应用级别模块,适用于消费者加载作为应用类型加载的模块
React 版本兼容性
@module-federation/bridge-react
支持多个 React 主要版本,以确保在不同 React 版本的应用之间实现无缝集成:
- React 16/17 (Legacy): 完全支持,使用传统
ReactDOM.render
和 ReactDOM.unmountComponentAtNode
API
- React 18: 完全支持,使用新的
createRoot
和 hydrateRoot
API
- React 19: 完全支持,使用
createRoot
和 hydrateRoot
API,但移除了对传统渲染方法的支持
版本特定导入
您可以根据项目中使用的 React 版本,选择特定的导入方式:
// 自动检测 React 版本(适用于 React 16/17/18)
import { createBridgeComponent } from '@module-federation/bridge-react';
// 明确指定 React 16/17 版本(Legacy 模式)
import { createBridgeComponent } from '@module-federation/bridge-react/legacy';
// 明确指定 React 18 版本
import { createBridgeComponent } from '@module-federation/bridge-react/v18';
// 明确指定 React 19 版本
import { createBridgeComponent } from '@module-federation/bridge-react/v19';
这种多版本兼容性使得不同团队可以独立选择和升级他们的 React 版本,同时保持模块联邦集成的稳定性。
查看 Demo
安装
npm install @module-federation/bridge-react@latest
使用示例
导出应用类型模块
注意
使用 @module-federation/bridge-react
后不能将 react-router-dom
设置成 shared,否则构建工具将会提示异常。这是因为 @module-federation/bridge-react
通过代理 react-router-dom
实现了对路由的控制,以保证应用间路由能够正常协同工作。
在生产者项目中
假设我们需要将应用通过 @module-federation/bridge-react
导出为一个应用类型模块,应用入口为 App.tsx 文件:
步骤 1: 创建导出文件
首先,我们新建一个文件 export-app.tsx
,该文件将作为应用类型模块导出的文件。我们需要使用 createBridgeComponent
来包装应用的根组件。
// ./src/export-app.tsx
import App from './src/App.tsx';
import { createBridgeComponent } from '@module-federation/bridge-react';
export default createBridgeComponent({
rootComponent: App
});
步骤 2: 配置模块联邦
在 rsbuild.config.ts
配置文件中,我们需要将 export-app.tsx
作为应用类型模块导出:
// 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'],
}),
],
});
至此,我们已经完成了应用类型模块的导出。
INFO
为什么需要使用 createBridgeComponent?
应用类型模块需要使用 createBridgeComponent
包装有三个主要原因:
- 支持跨框架渲染:使用
createBridgeComponent
包装的组件将符合应用类型消费者的加载协议,使得跨框架渲染成为可能。
- 自动注入 basename:使用
createBridgeComponent
包装的组件将自动注入 basename
,确保生产者应用在消费者项目下正常工作。
- 包装 ErrorBoundary:使用
createBridgeComponent
包装的组件将被 ErrorBoundary 包装,确保在远程加载失败或渲染错误时自动进入 fallback 逻辑。
加载应用类型模块
在消费者项目中
步骤 1: 配置远程模块
在 rsbuild.config.ts
配置中,我们需要注册远程模块,这与其他模块联邦配置没有区别:
// rsbuild.config.ts
export default defineConfig({
plugins: [
pluginReact(),
pluginModuleFederation({
name: 'host',
remotes: {
remote1: 'remote1@http://localhost:2001/mf-manifest.json',
},
}),
],
});
步骤 2: 加载远程应用
在消费者项目中,我们需要使用 createRemoteComponent
来加载应用类型模块:
// ./src/App.tsx
import { createRemoteComponent } from '@module-federation/bridge-react';
import { loadRemote } from '@module-federation/runtime';
// 定义错误回退组件
const FallbackErrorComp = ({ error }) => {
return (
<div>
<h1>加载远程组件时出错</h1>
<p>{error.message}</p>
<button onClick={() => window.location.reload()}>
重试
</button>
</div>
);
};
// 定义加载中组件
const FallbackComp = <div data-test-id="loading">加载中...</div>;
// 使用 createRemoteComponent 创建远程组件
const Remote1App = createRemoteComponent({
// loader 用于加载远程模块,例如:loadRemote('remote1/export-app'),import('remote1/export-app')
loader: () => loadRemote('remote1/export-app'),
// fallback 用于在远程模块加载失败时显示的组件
fallback: FallbackErrorComp,
// loading 用于在加载远程模块时显示的组件
loading: FallbackComp,
});
const App = () => {
return (
<BrowserRouter basename="/">
<Routes>
<Route path="/" Component={Home} />
<Route
path="/remote1/*"
// 使用 Remote1App 组件,将会被懒加载
Component={() => (
<Remote1App
// 设置远程应用的 basename
basename="/remote1"
// 其他属性将传递给远程组件
props1={'props_value'}
props2={'another_props_value'}
/>
)}
/>
</Routes>
</BrowserRouter>
);
};
INFO
关于远程组件加载
- 通过
createRemoteComponent
导出的远程模块将自动使用 react-bridge 加载协议加载模块,使得应用的跨框架渲染成为可能。
- 此外,
createRemoteComponent
将自动处理模块加载、模块销毁、错误处理、加载中、路由等逻辑,让开发者只需要关注如何使用远程组件。
API 参考
createBridgeComponent
createBridgeComponent
用于导出远程 React 组件。
/**
* 创建用于远程加载的 Bridge Component
* @param bridgeInfo - Bridge Component配置信息
* @returns 返回一个提供 render 和 destroy 方法的函数
*/
function createBridgeComponent<T>(
bridgeInfo: ProviderFnParams<T>
): () => {
render(info: RenderFnParams): Promise<void>;
destroy(info: DestroyParams): Promise<void>;
};
/**
* Bridge Component 配置信息
*/
interface ProviderFnParams<T> {
/** 要远程加载的根组件 */
rootComponent: React.ComponentType<T>;
/**
* 自定义渲染函数,用于自定义渲染逻辑
* @param App - React 元素
* @param id - DOM 元素或字符串 ID
* @returns React 根元素或 Promise
*/
render?: (
App: React.ReactElement,
id?: HTMLElement | string,
) => RootType | Promise<RootType>;
/**
* 自定义 createRoot 函数,用于 React 18 及以上版本
* @param container - DOM 容器
* @param options - CreateRoot 选项
* @returns React 根节点
*/
createRoot?: (
container: Element | DocumentFragment,
options?: CreateRootOptions,
) => Root;
/**
* React 18 和 19 的 createRoot 默认选项
* 这些选项将在创建根节点时使用,除非在渲染参数中被 rootOptions 覆盖
* @example
* {
* identifierPrefix: 'app-',
* onRecoverableError: (err) => console.error(err)
* }
*/
defaultRootOptions?: CreateRootOptions;
}
createRemoteComponent
createRemoteComponent
用于加载远程 React 组件。
/**
* 创建远程 React 组件
* @param options - 远程组件配置选项
* @returns 返回一个可以接收 props 并渲染远程组件的 React 组件
*/
function createRemoteComponent<T, E extends keyof T = 'default'>(
options: RemoteComponentParams<T, E>
): React.ForwardRefExoticComponent<
React.PropsWithoutRef<RemoteComponentProps> & React.RefAttributes<HTMLDivElement>
>;
/**
* 远程组件配置参数
*/
interface RemoteComponentParams<
T = Record<string, unknown>,
E extends keyof T = keyof T
> {
/**
* 加载远程模块的函数
* 例如:() => loadRemote('remote1/export-app') 或 () => import('remote1/export-app')
*/
loader: () => Promise<T>;
/** 加载远程模块时显示的组件 */
loading: React.ReactNode;
/** 加载或渲染远程模块失败时显示的错误组件 */
fallback: React.ComponentType<{ error: Error }>;
/**
* 指定模块导出名称
* 默认为 'default'
*/
export?: E;
}
/**
* 远程组件属性
*/
interface RemoteComponentProps<T = Record<string, unknown>> {
/** 传递给远程组件的属性 */
props?: T;
/**
* 内存路由配置,用于将子应用路由作为 memoryRouter 控制
* 不会直接在浏览器地址栏中显示 URL
*/
memoryRoute?: { entryPath: string };
/** 基础路径名 */
basename?: string;
/** 样式 */
style?: React.CSSProperties;
/** 类名 */
className?: string;
}
使用示例
使用 export 指定模块导出
// 远程应用
export const provider = createBridgeComponent({
rootComponent: App
});
// 宿主应用
const Remote1App = createRemoteComponent({
loader: () => loadRemote('remote1/export-app'),
export: 'provider', // 指定使用 provider 导出
fallback: FallbackErrorComp,
loading: FallbackComp,
});
使用 memoryRoute 控制路由
function App() {
return (
<BrowserRouter basename="/">
<Routes>
<Route
path="/remote1/*"
Component={() => (
<Remote1App
className={styles.remote1}
style={{ color: 'red' }}
// 使用 memoryRoute 将子应用路由作为 memoryRouter 控制
// 不会直接在浏览器地址栏中显示 URL
memoryRoute={{ entryPath: '/detail' }}
// 其他属性将传递给远程组件
props1={'props_value'}
props2={'another_props_value'}
ref={ref}
/>
)}
/>
</Routes>
</BrowserRouter>
);
}
为 React 18 和 19 配置 createRoot 选项
对于 React 18 和 19,@module-federation/bridge-react
支持向 createRoot
方法传递选项,允许您自定义根组件的行为:
创建bridge component时设置默认选项
import { createBridgeComponent } from '@module-federation/bridge-react';
export default createBridgeComponent({
rootComponent: App,
// 为使用此组件的所有实例设置默认 createRoot 选项
defaultRootOptions: {
identifierPrefix: 'my-app-',
onRecoverableError: (error) => {
console.error('可恢复的渲染错误:', error);
}
}
});
在渲染时设置选项
// 在消费者应用中
const RemoteApp = createRemoteComponent({
url: 'http://localhost:3001/remoteEntry.js',
scope: 'remote',
module: './App',
});
// 在渲染时传递 rootOptions
<RemoteApp
props={{ message: 'Hello' }}
rootOptions={{
identifierPrefix: 'instance-',
onRecoverableError: (error) => {
console.error('此实例的可恢复错误:', error);
}
}}
/>
渲染时传递的选项将覆盖创建组件时设置的默认选项。