# 加载应用 本章将介绍如何使用 `createRemoteAppComponent` 在宿主应用中加载和集成远程 React 应用。 ## 什么是 createRemoteAppComponent? `createRemoteAppComponent` 是 React Bridge 的核心 API,用于在宿主应用中加载远程 React 应用。它具有以下特性: - **🚀 自动懒加载** - 远程应用会在需要时才开始加载 - **🔧 生命周期管理** - 自动处理组件的挂载和卸载 - **🛣️ 路由集成** - 无缝集成 React Router,支持 basename 注入 - **⚡ 错误处理** - 内置加载失败和运行时错误处理机制 - **🎨 样式隔离** - 支持组件级样式和类名配置 ## 安装 ```sh [npm] npm install @module-federation/bridge-react@latest ``` ```sh [yarn] yarn add @module-federation/bridge-react@latest ``` ```sh [pnpm] pnpm add @module-federation/bridge-react@latest ``` ## 基本使用 ### 步骤 1: 配置远程模块 在宿主应用的配置文件中,添加远程应用的配置: :::tip 构建工具支持 以下示例使用 Rsbuild 配置,请根据您使用的构建工具进行相应调整: - **Rsbuild**: `@module-federation/rsbuild-plugin` - **Rspack**: `@module-federation/enhanced/rspack` - **Webpack**: `@module-federation/enhanced/webpack` - **Vite**: `@module-federation/vite` ::: ```ts // rsbuild.config.ts import { pluginModuleFederation } from '@module-federation/rsbuild-plugin'; import { defineConfig } from '@rsbuild/core'; export default defineConfig({ plugins: [ pluginModuleFederation({ name: 'host-app', remotes: { 'remote1': 'remote1@http://localhost:3001/remoteEntry.js', }, }), ], }); ``` ### 步骤 2: 创建远程组件 #### 2.1 定义加载和错误组件 首先,创建加载状态和错误处理组件: ```tsx // ./src/components/RemoteComponents.tsx import React from 'react'; // 加载状态组件 export const LoadingComponent = () => ( <div style={{ padding: '20px', textAlign: 'center' }}> <div>远程应用加载中...</div> </div> ); // 错误回退组件 export const ErrorFallback = ({ error }: { error: Error }) => ( <div style={{ padding: '20px', border: '1px solid #ff6b6b', borderRadius: '8px' }}> <h3>远程应用加载失败</h3> <p>错误详情: {error.message}</p> <button onClick={() => window.location.reload()}> 重新加载页面 </button> </div> ); ``` #### 2.2 创建远程应用组件 使用 `createRemoteAppComponent` 创建远程组件: ```tsx // ./src/remotes/Remote1App.tsx import { createRemoteAppComponent } from '@module-federation/bridge-react'; import { loadRemote } from '@module-federation/runtime'; import { LoadingComponent, ErrorFallback } from '../components/RemoteComponents'; export const Remote1App = createRemoteAppComponent({ loader: () => loadRemote('remote1/export-app'), loading: LoadingComponent, fallback: ErrorFallback, }); ``` #### 2.3 主应用路由配置 在主应用中配置路由: ```tsx // ./src/App.tsx import React from 'react'; import { BrowserRouter, Routes, Route } from 'react-router-dom'; import { Remote1App } from './remotes/Remote1App'; // 宿主应用首页组件 const HomePage = () => ( <div style={{ padding: '20px' }}> <h1>宿主应用首页</h1> <p>这是宿主应用的首页内容</p> </div> ); const App = () => { return ( <BrowserRouter> <Routes> <Route path="/" element={<HomePage />} /> <Route path="/remote1/*" // 使用 Remote1App 组件, 将会被懒加载 Component={() => ( <Remote1App // 可设置 className 和 style 样式,将自动注入到组件上 className={styles.remote1} style={{ color: 'red' }} // name 和 age 为远程组件 props, 将自动透传到远程组件 name={'Ming'} age={12} // 可设置 ref, 将自动转发到远程组件,可获取 ref 对象操作 dom ref={ref} /> )} /> </Routes> </BrowserRouter> ); }; export default App; ``` ## 远程组件 Props ### 路由相关属性 - **`basename`**: 设置远程应用的基础路径 - **`memoryRoute`**: 内存路由配置,用于将子应用路由作为 memoryRouter 控制 ### 样式属性 - **`style`**: React.CSSProperties - 设置组件样式 - **`className`**: string - 设置组件类名 ### 引用支持 - **`ref`**: React.Ref\<HTMLDivElement> - 转发引用到内部容器元素,可用于 DOM 操作 ### 数据传递 - **`props`**: 传递给远程组件的属性对象 - 或者直接传递属性,如 `userId={'123'}` ## createRemoteAppComponent API 参考 ### 函数签名 ```tsx function createRemoteAppComponent<T = Record<string, unknown>, E extends keyof T = keyof T>( config: RemoteComponentParams<T, E> ): React.ForwardRefExoticComponent< Omit<RemoteComponentProps<T>, "ref"> & React.RefAttributes<HTMLDivElement> > ``` #### RemoteComponentParams\<T, E> 配置参数接口: ```tsx interface RemoteComponentParams<T = Record<string, unknown>, E extends keyof T = keyof T> { // 远程模块加载器 loader: () => Promise<T>; // 加载状态显示内容 loading: React.ReactNode; // 错误回退组件 fallback: React.ComponentType<{ error: Error }>; // 导出名称(可选) export?: E; // 传递给远程组件的属性(可选) props?: T; } ``` #### RemoteComponentProps\<T> 返回组件的属性接口: ```tsx interface RemoteComponentProps<T = Record<string, unknown>> { // 传递给远程组件的属性 props?: T; // 错误回退组件 fallback?: React.ComponentType<{ error: Error }>; // 加载状态显示内容 loading?: React.ReactNode; // 路由基础路径 basename?: string; // 内存路由配置 memoryRoute?: { entryPath: string; initialState?: Record<string, unknown>; }; // 样式属性 style?: React.CSSProperties; className?: string; // 其他自定义属性 [key: string]: unknown; } ``` ### 参数详解 #### loader - **类型**: `() => Promise<T>` - **必需**: 是 - **作用**: 用于加载远程模块的函数,返回一个 Promise,该 Promise 解析为远程模块对象 - **示例**: ```tsx loader: () => loadRemote('remote1/export-app') loader: () => import('remote1/export-app') ``` #### loading - **类型**: `React.ReactNode` - **必需**: 是 - **作用**: 在远程应用加载期间显示的加载内容,可以是组件、元素或字符串 - **示例**: ```tsx loading: <div>Loading...</div> loading: 'Loading remote app...' loading: <Spinner /> ``` #### fallback - **类型**: `React.ComponentType<{ error: Error }>` - **必需**: 是 - **作用**: 当远程应用加载失败时显示的错误回退组件,会接收错误对象作为 `error` 属性 - **示例**: ```tsx fallback: ({ error }) => <div>Error: {error.message}</div> fallback: ErrorBoundaryComponent ``` #### export - **类型**: `E extends keyof T` (泛型约束,通常是 `string`) - **必需**: 否 - **默认值**: `'default'` - **作用**: 指定要使用的远程模块导出名称 - **示例**: 假设远程模块有以下导出: ```tsx // 远程模块的导出 export default App; // 默认导出 export const provider = App; // 命名导出 provider export const dashboard = Dashboard; // 命名导出 dashboard ``` 在宿主应用中可以这样使用: ```tsx // 使用默认导出(可以省略 export 参数) createRemoteAppComponent({ loader: () => loadRemote('remote1/export-app'), // export: 'default' // 可以省略,默认就是 'default' }) // 使用命名导出 provider createRemoteAppComponent({ loader: () => loadRemote('remote1/export-app'), export: 'provider' }) // 使用命名导出 dashboard createRemoteAppComponent({ loader: () => loadRemote('remote1/export-app'), export: 'dashboard' }) ``` ## Bundle 体积优化 ### React Router 依赖说明 默认情况下,`@module-federation/bridge-react` 会将 `react-router-dom` 打包到你的 bundle 中,这是为了提供以下开箱即用的能力: - ✅ 自动 basename 注入 - 无需手动配置路由基础路径 - ✅ 路由上下文传递 - 自动处理 React Router 上下文 - ✅ 嵌套路由支持 - 完整的路由集成能力 **但是**,如果你的项目满足以下任一条件: - 不需要路由功能(纯组件加载) - 使用非 react-router 的路由框架(如 TanStack Router) - 希望最小化 bundle 体积 **建议关闭** `enableBridgeRouter` 配置来禁用此能力,这将: - ✅ 减小 bundle 体积约 3KB (gzipped) - ✅ 避免不必要的依赖注入 - ✅ 消除潜在的版本冲突风险 ### 如何禁用 Router 依赖 你可以通过 `bridge.enableBridgeRouter` 配置来控制是否包含路由支持: ```ts title="rsbuild.config.ts" import { pluginModuleFederation } from '@module-federation/rsbuild-plugin'; export default { plugins: [ pluginModuleFederation({ name: 'host-app', remotes: { remote1: 'remote1@http://localhost:3001/mf-manifest.json', }, bridge: { // 禁用路由支持以减小 bundle 体积 enableBridgeRouter: false, }, }), ], }; ``` :::tip 配置行为 - **`enableBridgeRouter: false`**: 自动 alias 到 `/base` 入口(不包含 react-router-dom 代码) - **`enableBridgeRouter: true`** 或 **`undefined`**: 包含路由支持(默认行为) ::: ### 何时禁用 Router? **禁用 router** (`enableBridgeRouter: false`) 适用于: - ✅ 应用不使用 react-router - ✅ 想要最小化 bundle 体积 - ✅ 可以手动管理 basename(如果需要) **保持 router 启用**(默认)适用于: - ✅ 应用使用 react-router - ✅ 需要自动 basename 注入 - ✅ 需要路由上下文集成 ### 迁移示例 #### 迁移前:启用 Router(默认) ```tsx import { createRemoteAppComponent } from '@module-federation/bridge-react'; const RemoteApp = createRemoteAppComponent({ loader: () => loadRemote('remote1/app'), loading: <div>Loading...</div>, fallback: ErrorBoundary, }); // basename 自动从路由上下文获取 <RemoteApp /> ``` #### 迁移后:禁用 Router(优化) ```ts title="rsbuild.config.ts" // 配置 pluginModuleFederation({ bridge: { enableBridgeRouter: false, // 禁用 router }, }) ``` ```tsx import { createRemoteAppComponent } from '@module-federation/bridge-react'; const RemoteApp = createRemoteAppComponent({ loader: () => loadRemote('remote1/app'), loading: <div>Loading...</div>, fallback: ErrorBoundary, }); // 无需修改代码!插件会自动 alias 到 /base 入口 <RemoteApp basename="/" /> // 如果需要,手动传递 basename ``` :::info 工作原理 当设置 `enableBridgeRouter: false` 时,Module Federation 插件会自动设置 webpack alias: ``` '@module-federation/bridge-react' → '@module-federation/bridge-react/base' ``` 这意味着你的导入会自动解析到无路由版本,无需修改任何代码! :::