diff --git a/.changeset/chatty-days-march.md b/.changeset/chatty-days-march.md
new file mode 100644
index 00000000000..3f2fab4c3e7
--- /dev/null
+++ b/.changeset/chatty-days-march.md
@@ -0,0 +1,5 @@
+---
+'@module-federation/bridge-react': patch
+---
+
+feat(bridge-react): enable custom createRoot in bridge-react
diff --git a/.changeset/neat-types-fix.md b/.changeset/neat-types-fix.md
new file mode 100644
index 00000000000..ae5d8908f24
--- /dev/null
+++ b/.changeset/neat-types-fix.md
@@ -0,0 +1,5 @@
+---
+'@module-federation/bridge-react': patch
+---
+
+refactor(bridge-react): centralize type definitions into a single file for better maintainability and consistency
diff --git a/apps/website-new/docs/en/practice/bridge/react-bridge.mdx b/apps/website-new/docs/en/practice/bridge/react-bridge.mdx
index 14eef4889ec..f39eaa2c8c8 100644
--- a/apps/website-new/docs/en/practice/bridge/react-bridge.mdx
+++ b/apps/website-new/docs/en/practice/bridge/react-bridge.mdx
@@ -1,11 +1,12 @@
# 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.
+- `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](https://github.com/module-federation/core/tree/main/apps/router-demo)
+
### Installation
import { PackageManagerTabs } from '@theme';
@@ -41,7 +42,7 @@ export default createBridgeComponent({
});
```
-- Step 2: In the rsbuild.config.ts configuration file, we need to export `export-app.tsx` as an application type module
+- Step 2: In the `rsbuild.config.ts` configuration file, we need to export `export-app.tsx` as an application type module
```ts
// rsbuild.config.ts
@@ -60,7 +61,6 @@ export default defineConfig({
],
});
```
-
At this point, we have completed the export of the application type module.
:::info
@@ -77,7 +77,7 @@ Why do application type modules need to be wrapped with `createBridgeComponent`?
> Host
-- Step 1: In the rsbuild.config.ts configuration, we need to register remote modules, which is no different from other Module Federation configurations.
+- Step 1: In the `rsbuild.config.ts` configuration, we need to register remote modules, which is no different from other Module Federation configurations.
```ts
// rsbuild.config.ts
@@ -100,64 +100,63 @@ export default defineConfig({
// ./src/App.tsx
import React from 'react';
import { createRemoteComponent } from '@module-federation/bridge-react';
-import { loadRemote } from '@module-federation/enhanced/runtime'
import styles from './index.module.less';
-// define FallbackErrorComp Component
-const FallbackErrorComp = (info: any) => {
+// Define FallbackErrorComp component
+const FallbackErrorComp = ({ error, resetErrorBoundary }) => {
return (
This is ErrorBoundary Component
Something went wrong:
-
{info?.error.message}
-
info.resetErrorBoundary()}>
+ {error.message}
+ resetErrorBoundary()}>
resetErrorBoundary(try again)
);
};
-// define FallbackLoading Component
+// Define FallbackLoading component
const FallbackComp = loading...
;
-// use createRemoteComponent to export remote component
+// Use createRemoteComponent to create remote component
const Remote1App = createRemoteComponent({
- // loader is for loading remote module, for example: loadRemote('remote1/export-app')、import('remote1/export-app')
+ // loader is used to load remote modules, e.g.: loadRemote('remote1/export-app'), import('remote1/export-app')
loader: () => loadRemote('remote1/export-app'),
- // fallback 用于在加载远程模块失败时展示的组件
- // fallback is for error handling
+ // fallback is used for displaying components when remote module loading fails
fallback: FallbackErrorComp,
- // loading is for loading state
+ // loading is used for displaying components when loading remote modules
loading: FallbackComp,
});
const App = () => {
- return (
+ return (
+
- (
)}
/>
- )
+
+ );
};
```
-
-:::
-
At this point, we have completed loading the application type module.
:::info
@@ -173,126 +172,141 @@ making the user experience almost equivalent to using local components.
:::
-### Methods
+
+### API Reference
#### createBridgeComponent
+`createBridgeComponent` is used to wrap React components into remotely loadable modules.
+
```tsx
-export declare function createBridgeComponent(bridgeInfo: ProviderFnParams): () => {
- render(info: RenderFnParams): Promise;
- destroy(info: {
- dom: HTMLElement;
- }): Promise;
+/**
+ * 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(
+ bridgeInfo: ProviderFnParams
+): () => {
+ render(info: RenderFnParams): Promise;
+ destroy(info: DestroyParams): Promise;
};
-type ProviderFnParams = {
+/**
+ * Bridge component configuration information
+ */
+interface ProviderFnParams {
+ /** Root component to be remotely loaded */
rootComponent: React.ComponentType;
+
+ /**
+ * 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;
-};
-
-export declare interface RenderFnParams extends ProviderParams {
- dom: HTMLElement;
+
+ /**
+ * 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;
}
-export declare interface ProviderParams {
- moduleName?: string;
- basename?: string;
- memoryRoute?: {
- entryPath: string;
- };
- style?: React.CSSProperties;
- className?: string;
+/**
+ * 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;
}
-
```
-* `bridgeInfo`
- * type:
-```tsx
-type ProviderFnParams = {
- rootComponent: React.ComponentType;
- render?: (
- App: React.ReactElement,
- id?: HTMLElement | string,
- ) => RootType | Promise;
-};
-```
-
- * Purpose: Used to pass the root component
- * ReturnType
- * type:
-
- ```tsx
- () => {
- render(info: {
- moduleName?: string;
- basename?: string;
- memoryRoute?: {
- entryPath: string;
- };
- style?: React.CSSProperties;
- className?: string;
- dom?: HTMLElement;
- }): Promise;
- destroy(info: { dom: HTMLElement}): Promise;
- }
- ```
-
#### createRemoteComponent
-```tsx
-import { createRemoteComponent } from '@module-federation/bridge-react';
-import type { ProviderParams } from '@module-federation/bridge-react';
-
-function createRemoteComponent(
- options: {
- // loader is for loading remote module, for example: loadRemote('remote1/export-app')、import('remote1/export-app')
- loader: () => Promise,
- // default is default, used to specify the export of the module
- export?: E;
- // loading is for loading state
- loading: React.ReactNode;
- // fallback is for error handling
- fallback: ComponentType<{ error: any; }>;
- }
-): (props: {
- basename?: ProviderParams['basename'];
- memoryRoute?: { entryPath: string };
-} & RawComponentType) => React.JSX.Element;
-```
-
-* `options`
- * `loader`
- * type: `() => Promise`
- * Purpose: Used to load remote modules, for example: `loadRemote('remote1/export-app')`, `import('remote1/export-app')`
+`createRemoteComponent` is used to load remote React components.
```tsx
-const Remote1App = createRemoteComponent({
- // loader is for loading remote module, for example: loadRemote('remote1/export-app')、import('remote1/export-app')
- loader: () => loadRemote('remote1/export-app'),
- // fallback is for error handling
- fallback: FallbackErrorComp,
- // loading is for loading state
- loading: FallbackComp,
-});
+/**
+ * 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(
+ options: RemoteComponentParams
+): React.ForwardRefExoticComponent<
+ React.PropsWithoutRef & React.RefAttributes
+>;
+
+/**
+ * Remote component configuration parameters
+ */
+interface RemoteComponentParams<
+ T = Record,
+ E extends keyof T = keyof T
+> {
+ /**
+ * Function to load remote module
+ * Example: () => loadRemote('remote1/export-app') or () => import('remote1/export-app')
+ */
+ loader: () => Promise;
+
+ /** 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;
+}
-const Remote2App = createRemoteComponent({
- // loader is for loading remote module, for example: loadRemote('remote2/export-app')、import('remote2/export-app')
- loader: () => import('remote2/export-app'),
- // fallback is for error handling
- fallback: FallbackErrorComp,
- // loading is for loading state
- loading: FallbackComp,
-});
+/**
+ * Remote component properties
+ */
+interface RemoteComponentProps> {
+ /** 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
-```
- * `export`
- * type: `string`
- * Purpose: Can specify the export of the module
```tsx
// remote
export const provider = createBridgeComponent({
@@ -302,48 +316,36 @@ export const provider = createBridgeComponent({
// host
const Remote1App = createRemoteComponent({
loader: () => loadRemote('remote1/export-app'),
- export: 'provider'
-});
-```
- * `loading`
- * type: `React.ReactNode`
- * Purpose: Component displayed when loading remote modules
- * `fallback`
- * type: `ComponentType<{ error: any; }>`
- * Purpose: Component displayed when loading, rendering remote modules
-
-* `ReturnType`
- * type: `(props: PropsInfo)=> React.JSX.Element`
- * Purpose: Used to render remote module components
-
-```tsx
-const Remote1App = createRemoteComponent({
- // loader is for loading remote module, for example: loadRemote('remote1/export-app')、import('remote1/export-app')
- loader: () => loadRemote('remote1/export-app'),
- // fallback is for error handling
+ export: 'provider', // Specify to use provider export
fallback: FallbackErrorComp,
- // loading is for loading state
loading: FallbackComp,
});
+```
+#### Using memoryRoute to control routing
+```tsx
function App() {
- return (
-
-
+
+ (
)}
/>
-
- )
+
+
+ );
}
-```
diff --git a/apps/website-new/docs/zh/practice/bridge/react-bridge.mdx b/apps/website-new/docs/zh/practice/bridge/react-bridge.mdx
index a2dcfad63ac..145679952ef 100644
--- a/apps/website-new/docs/zh/practice/bridge/react-bridge.mdx
+++ b/apps/website-new/docs/zh/practice/bridge/react-bridge.mdx
@@ -26,7 +26,6 @@ import { PackageManagerTabs } from '@theme';
:::danger
请注意:使用 `@module-federation/bridge-react` 后不能将 `react-router-dom` 设置成 shared,否则构建工具将会提示异常。这是因为 `@module-federation/bridge-react` 通过代理 `react-router-dom` 实现了对路由的控制,以保证应用间路由能够正常协同工作。
-
:::
> 在生产者项目中,假设我们需要将应用通过 `@module-federation/bridge-react` 导出为一个应用类型模块,应用入口为 App.tsx 文件
@@ -43,7 +42,7 @@ export default createBridgeComponent({
});
```
-- Step2: 在 rsbuild.config.ts 配置文件中,我们需要将 `export-app.tsx` 作为应用类型模块导出
+- Step2: 在 `rsbuild.config.ts` 配置文件中,我们需要将 `export-app.tsx` 作为应用类型模块导出
```ts
// rsbuild.config.ts
@@ -78,7 +77,7 @@ export default defineConfig({
> Host
-- Step1: 在 rsbuild.config.ts 配置中,我们需要注册远程模块,这点与其它 Module Federation 配置无异。
+- Step1: 在 `rsbuild.config.ts` 配置中,我们需要注册远程模块,这点与其它 Module Federation 配置无异。
```ts
// rsbuild.config.ts
@@ -104,13 +103,13 @@ import { createRemoteComponent } from '@module-federation/bridge-react';
import styles from './index.module.less';
// 定义 FallbackErrorComp 组件
-const FallbackErrorComp = (info: any) => {
+const FallbackErrorComp = ({ error, resetErrorBoundary }) => {
return (
This is ErrorBoundary Component
Something went wrong:
-
{info?.error.message}
-
info.resetErrorBoundary()}>
+ {error.message}
+ resetErrorBoundary()}>
resetErrorBoundary(try again)
@@ -120,7 +119,7 @@ const FallbackErrorComp = (info: any) => {
// 定义 FallbackLoading 组件
const FallbackComp = loading...
;
-// 使用 createRemoteComponent 导出远程组件
+// 使用 createRemoteComponent 创建远程组件
const Remote1App = createRemoteComponent({
// loader 用于加载远程模块,例如:loadRemote('remote1/export-app')、import('remote1/export-app')
loader: () => loadRemote('remote1/export-app'),
@@ -131,10 +130,11 @@ const Remote1App = createRemoteComponent({
});
const App = () => {
- return (
+ return (
+
- (
@@ -147,11 +147,14 @@ const App = () => {
age={12}
// 可设置 ref, 将自动转发到远程组件,可获取 ref 对象操作 dom
ref={ref}
+ // 通过 memoryRoute 来将子应用路由控制为 memoryRouter,将不会直接将 url 展示在浏览器地址上
+ memoryRoute={{ entryPath: '/detail' }}
/>
)}
/>
- )
+
+ );
};
```
至此,我们完成了应用类型模块的加载。
@@ -170,124 +173,140 @@ const App = () => {
:::
-### 方法
+### API 参考
#### createBridgeComponent
+`createBridgeComponent` 用于将 React 组件包装成可远程加载的模块。
+
```tsx
-export declare function createBridgeComponent(bridgeInfo: ProviderFnParams): () => {
- render(info: RenderFnParams): Promise;
- destroy(info: {
- dom: HTMLElement;
- }): Promise;
+/**
+ * 创建一个可远程加载的 React 组件
+ * @param bridgeInfo - Bridge Component Config information
+ * @returns 返回一个函数,该函数返回包含 render 和 destroy 方法的对象
+ */
+function createBridgeComponent(
+ bridgeInfo: ProviderFnParams
+): () => {
+ render(info: RenderFnParams): Promise;
+ destroy(info: DestroyParams): Promise;
};
-type ProviderFnParams = {
+/**
+ * 桥接组件配置信息
+ */
+interface ProviderFnParams {
+ /** 需要被远程加载的根组件 */
rootComponent: React.ComponentType;
+
+ /**
+ * 自定义渲染函数,用于自定义渲染逻辑
+ * @param App - React 元素
+ * @param id - DOM 元素或字符串 ID
+ * @returns React 根元素或 Promise
+ */
render?: (
App: React.ReactElement,
id?: HTMLElement | string,
) => RootType | Promise;
-};
-
-export declare interface RenderFnParams extends ProviderParams {
- dom: HTMLElement;
+
+ /**
+ * 自定义创建 React 根节点的函数
+ * @param container - 容器元素
+ * @param options - 创建根节点的选项
+ * @returns React 根节点
+ */
+ createRoot?: (
+ container: Element | DocumentFragment,
+ options?: CreateRootOptions,
+ ) => Root;
}
-export declare interface ProviderParams {
- moduleName?: string;
- basename?: string;
- memoryRoute?: {
- entryPath: string;
- };
- style?: React.CSSProperties;
- className?: string;
+/**
+ * 创建 React 根节点的选项
+ */
+interface CreateRootOptions {
+ /** 为生成的 React ID 添加前缀,用于避免 ID 冲突 */
+ identifierPrefix?: string;
+
+ /** 处理 React 在渲染过程中可恢复的错误的回调函数 */
+ onRecoverableError?: (error: unknown) => void;
+
+ /** 过渡回调函数,用于 React 18 的并发特性 */
+ transitionCallbacks?: TransitionCallbacks;
}
-
-```
-
-* `bridgeInfo`
- * type:
-```tsx
-type ProviderFnParams = {
- rootComponent: React.ComponentType;
- render?: (
- App: React.ReactElement,
- id?: HTMLElement | string,
- ) => RootType | Promise;
-};
```
- * 作用: 用于传递根组件
- * ReturnType
- * type:
-
- ```tsx
- () => {
- render(info: {
- moduleName?: string;
- basename?: string;
- memoryRoute?: {
- entryPath: string;
- };
- style?: React.CSSProperties;
- className?: string;
- dom?: HTMLElement;
- }): Promise;
- destroy(info: { dom: HTMLElement}): Promise;
- }
- ```
-
#### createRemoteComponent
-```tsx
-import { createRemoteComponent } from '@module-federation/bridge-react';
-import type { ProviderParams } from '@module-federation/bridge-react';
-
-function createRemoteComponent(
- options: {
- // 加载远程应用的函数,例如:loadRemote('remote1/export-app')、import('remote1/export-app')
- loader: () => Promise,
- // 默认为 default,用于指定模块的 export
- export?: E;
- loading: React.ReactNode;
- fallback: ComponentType<{ error: any; }>;
- }
-): (props: {
- basename?: ProviderParams['basename'];
- memoryRoute?: { entryPath: string };
-} & RawComponentType) => React.JSX.Element;
-```
-
-* `options`
- * `loader`
- * type: `() => Promise`
- * 作用: 用于加载远程模块的函数,例如:`loadRemote('remote1/export-app')`、`import('remote1/export-app')`
+`createRemoteComponent` 用于加载远程 React 组件。
```tsx
-const Remote1App = createRemoteComponent({
- // loader 用于加载远程模块,例如:loadRemote('remote1/export-app')、import('remote1/export-app')
- loader: () => loadRemote('remote1/export-app'),
- // fallback 用于在加载远程模块失败时展示的组件
- fallback: FallbackErrorComp,
- // loading 用于在加载远程模块时展示的组件
- loading: FallbackComp,
-});
+/**
+ * 创建远程 React 组件
+ * @param options - 远程组件配置选项
+ * @returns 返回一个 React 组件,可以接收 props 并渲染远程组件
+ */
+function createRemoteComponent(
+ options: RemoteComponentParams
+): React.ForwardRefExoticComponent<
+ React.PropsWithoutRef & React.RefAttributes
+>;
+
+/**
+ * 远程组件配置参数
+ */
+interface RemoteComponentParams<
+ T = Record,
+ E extends keyof T = keyof T
+> {
+ /**
+ * 加载远程模块的函数
+ * 例如:() => loadRemote('remote1/export-app') 或 () => import('remote1/export-app')
+ */
+ loader: () => Promise;
+
+ /** 加载远程模块时显示的组件 */
+ loading: React.ReactNode;
+
+ /** 加载或渲染远程模块失败时显示的错误组件 */
+ fallback: React.ComponentType<{ error: Error }>;
+
+ /**
+ * 指定模块的导出名称
+ * 默认为 'default'
+ */
+ export?: E;
+}
-const Remote2App = createRemoteComponent({
- // loader 用于加载远程模块,例如:loadRemote('remote2/export-app')、import('remote2/export-app')
- loader: () => import('remote2/export-app'),
- // fallback 用于在加载远程模块失败时展示的组件
- fallback: FallbackErrorComp,
- // loading 用于在加载远程模块时展示的组件
- loading: FallbackComp,
-});
+/**
+ * 远程组件属性
+ */
+interface RemoteComponentProps> {
+ /** 传递给远程组件的属性 */
+ props?: T;
+
+ /**
+ * 内存路由配置,用于将子应用路由控制为 memoryRouter
+ * 将不会直接将 URL 展示在浏览器地址栏上
+ */
+ memoryRoute?: { entryPath: string };
+
+ /** 基础路径名 */
+ basename?: string;
+
+ /** 样式 */
+ style?: React.CSSProperties;
+
+ /** 类名 */
+ className?: string;
+}
+```
+### 使用示例
+
+#### 使用 export 指定模块导出
-```
- * `export`
- * type: `string`
- * 作用: 可以指定模块的 export
```tsx
// remote
export const provider = createBridgeComponent({
@@ -297,48 +316,36 @@ export const provider = createBridgeComponent({
// host
const Remote1App = createRemoteComponent({
loader: () => loadRemote('remote1/export-app'),
- export: 'provider'
-});
-```
- * `loading`
- * type: `React.ReactNode`
- * 作用: 加载远程模块时显示的组件
- * `fallback`
- * type: `ComponentType<{ error: any; }>`
- * 作用: 加载、渲染远程模块过程中展示的错误
-
-* `ReturnType`
- * type: `(props: PropsInfo)=> React.JSX.Element`
- * 作用: 用于渲染远程模块组件
-
-```tsx
-const Remote1App = createRemoteComponent({
- // loader 用于加载远程模块,例如:loadRemote('remote1/export-app')、import('remote1/export-app')
- loader: () => loadRemote('remote1/export-app'),
- // fallback 用于在加载远程模块失败时展示的组件
+ export: 'provider', // 指定使用 provider 导出
fallback: FallbackErrorComp,
- // loading 用于在加载远程模块时展示的组件
loading: FallbackComp,
});
+```
+#### 使用 memoryRoute 控制路由
+```tsx
function App() {
- return (
-
-
+
+ (
)}
/>
-
- )
+
+
+ );
}
-```
diff --git a/packages/bridge/bridge-react/__tests__/bridge.spec.tsx b/packages/bridge/bridge-react/__tests__/bridge.spec.tsx
index 786a33432ab..b09fbdf6314 100644
--- a/packages/bridge/bridge-react/__tests__/bridge.spec.tsx
+++ b/packages/bridge/bridge-react/__tests__/bridge.spec.tsx
@@ -102,4 +102,36 @@ describe('bridge', () => {
expect(getHtml(container)).toMatch('hello world');
expect(ref.current).not.toBeNull();
});
+
+ it('createRemoteComponent with custom createRoot prop', async () => {
+ const renderMock = vi.fn();
+
+ function Component({ props }: { props?: Record }) {
+ return life cycle render {props?.msg}
;
+ }
+ const BridgeComponent = createBridgeComponent({
+ rootComponent: Component,
+ createRoot: () => {
+ return {
+ render: renderMock,
+ unmount: vi.fn(),
+ };
+ },
+ });
+ const RemoteComponent = createRemoteComponent({
+ loader: async () => {
+ return {
+ default: BridgeComponent,
+ };
+ },
+ fallback: () =>
,
+ loading: loading
,
+ });
+
+ const { container } = render( );
+ expect(getHtml(container)).toMatch('loading');
+
+ await sleep(200);
+ expect(renderMock).toHaveBeenCalledTimes(1);
+ });
});
diff --git a/packages/bridge/bridge-react/src/index.ts b/packages/bridge/bridge-react/src/index.ts
index 146de283507..6f61382ce25 100644
--- a/packages/bridge/bridge-react/src/index.ts
+++ b/packages/bridge/bridge-react/src/index.ts
@@ -1,6 +1,3 @@
export { createRemoteComponent } from './remote/create';
export { createBridgeComponent } from './provider/create';
-export type {
- ProviderParams,
- RenderFnParams,
-} from '@module-federation/bridge-shared';
+export type { ProviderParams, RenderFnParams } from './types';
diff --git a/packages/bridge/bridge-react/src/provider/compat.ts b/packages/bridge/bridge-react/src/provider/compat.ts
index e09c34c1aa1..815fec41de4 100644
--- a/packages/bridge/bridge-react/src/provider/compat.ts
+++ b/packages/bridge/bridge-react/src/provider/compat.ts
@@ -1,15 +1,5 @@
import ReactDOM from 'react-dom';
-
-interface CreateRootOptions {
- identifierPrefix?: string;
- onRecoverableError?: (error: unknown) => void;
- transitionCallbacks?: unknown;
-}
-
-interface Root {
- render(children: React.ReactNode): void;
- unmount(): void;
-}
+import { CreateRootOptions, Root } from '../types';
const isReact18 = ReactDOM.version.startsWith('18');
@@ -29,7 +19,6 @@ export function createRoot(
// For React 16/17, simulate the new root API using render/unmountComponentAtNode
return {
render(children: React.ReactNode) {
- // @ts-ignore - React 17's render method is deprecated but still functional
ReactDOM.render(children, container);
},
unmount() {
@@ -52,11 +41,16 @@ export function hydrateRoot(
return (ReactDOM as any).hydrateRoot(container, initialChildren, options);
}
- // For React 16/17, simulate the new root API using hydrate
+ // For React 16/17, simulate the new root API using hydrate/unmountComponentAtNode
return {
render(children: React.ReactNode) {
- // @ts-ignore - React 17's hydrate method is deprecated but still functional
- ReactDOM.hydrate(children, container);
+ // For the initial render, use hydrate
+ if (children === initialChildren) {
+ ReactDOM.hydrate(children, container);
+ } else {
+ // For subsequent renders, use regular render
+ ReactDOM.render(children, container);
+ }
},
unmount() {
ReactDOM.unmountComponentAtNode(container);
diff --git a/packages/bridge/bridge-react/src/provider/context.tsx b/packages/bridge/bridge-react/src/provider/context.tsx
index 6af19e1b765..f6843a5f8ee 100644
--- a/packages/bridge/bridge-react/src/provider/context.tsx
+++ b/packages/bridge/bridge-react/src/provider/context.tsx
@@ -1,4 +1,4 @@
import React from 'react';
-import { ProviderParams } from '@module-federation/bridge-shared';
+import { ProviderParams } from '../types';
export const RouterContext = React.createContext(null);
diff --git a/packages/bridge/bridge-react/src/provider/create.tsx b/packages/bridge/bridge-react/src/provider/create.tsx
index 64e4592e774..366f7cc4d1f 100644
--- a/packages/bridge/bridge-react/src/provider/create.tsx
+++ b/packages/bridge/bridge-react/src/provider/create.tsx
@@ -2,32 +2,21 @@ import * as React from 'react';
import ReactDOM from 'react-dom';
import type {
ProviderParams,
- RenderFnParams,
-} from '@module-federation/bridge-shared';
+ ProviderFnParams,
+ RootType,
+ DestroyParams,
+ RenderParams,
+} from '../types';
import { ErrorBoundary } from 'react-error-boundary';
import { RouterContext } from './context';
import { LoggerInstance } from '../utils';
import { federationRuntime } from './plugin';
-import { createRoot } from './compat';
+import { createRoot as defaultCreateRoot } from './compat';
-type RenderParams = RenderFnParams & {
- [key: string]: unknown;
-};
-type DestroyParams = {
- moduleName: string;
- dom: HTMLElement;
-};
-type RootType = HTMLElement | ReturnType;
-
-export type ProviderFnParams = {
- rootComponent: React.ComponentType;
- render?: (
- App: React.ReactElement,
- id?: HTMLElement | string,
- ) => RootType | Promise;
-};
-
-export function createBridgeComponent(bridgeInfo: ProviderFnParams) {
+export function createBridgeComponent({
+ createRoot = defaultCreateRoot,
+ ...bridgeInfo
+}: ProviderFnParams) {
return () => {
const rootMap = new Map();
const instance = federationRuntime.instance;
@@ -80,34 +69,37 @@ export function createBridgeComponent(bridgeInfo: ProviderFnParams) {
);
- if (bridgeInfo?.render) {
- // in case bridgeInfo?.render is an async function, resolve this to promise
- Promise.resolve(
- bridgeInfo?.render(rootComponentWithErrorBoundary, dom),
- ).then((root: RootType) => rootMap.set(info.dom, root));
+ if (bridgeInfo.render) {
+ await Promise.resolve(
+ bridgeInfo.render(rootComponentWithErrorBoundary, dom),
+ ).then((root: RootType) => rootMap.set(dom, root));
} else {
- let root = rootMap.get(info.dom);
+ let root = rootMap.get(dom);
// do not call createRoot multiple times
if (!root) {
- root = createRoot(info.dom);
- rootMap.set(info.dom, root);
+ root = createRoot(dom);
+ rootMap.set(dom, root);
+ }
+
+ if ('render' in root) {
+ root.render(rootComponentWithErrorBoundary);
}
- root.render(rootComponentWithErrorBoundary);
}
instance?.bridgeHook?.lifecycle?.afterBridgeRender?.emit(info) || {};
},
destroy(info: DestroyParams) {
+ const { dom } = info;
LoggerInstance.debug(`createBridgeComponent destroy Info`, info);
- const root = rootMap.get(info.dom);
+ const root = rootMap.get(dom);
if (root) {
if ('unmount' in root) {
root.unmount();
} else {
ReactDOM.unmountComponentAtNode(root as HTMLElement);
}
- rootMap.delete(info.dom);
+ rootMap.delete(dom);
}
instance?.bridgeHook?.lifecycle?.afterBridgeDestroy?.emit(info);
},
diff --git a/packages/bridge/bridge-react/src/remote/component.tsx b/packages/bridge/bridge-react/src/remote/component.tsx
index 2a5ce1810ac..9112d9bb650 100644
--- a/packages/bridge/bridge-react/src/remote/component.tsx
+++ b/packages/bridge/bridge-react/src/remote/component.tsx
@@ -6,38 +6,13 @@ import React, {
forwardRef,
} from 'react';
import * as ReactRouterDOM from 'react-router-dom';
-import type { ProviderParams } from '@module-federation/bridge-shared';
import { dispatchPopstateEnv } from '@module-federation/bridge-shared';
-import { ErrorBoundaryPropsWithComponent } from 'react-error-boundary';
import { LoggerInstance, pathJoin, getRootDomDefaultClassName } from '../utils';
import { federationRuntime } from '../provider/plugin';
-
-declare const __APP_VERSION__: string;
-export interface RenderFnParams extends ProviderParams {
- dom?: any;
- fallback: ErrorBoundaryPropsWithComponent['FallbackComponent'];
-}
-
-interface RemoteModule {
- provider: () => {
- render: (
- info: ProviderParams & {
- dom: any;
- },
- ) => void;
- destroy: (info: { dom: any }) => void;
- };
-}
-
-interface RemoteAppParams {
- moduleName: string;
- providerInfo: NonNullable;
- exportName: string | number | symbol;
- fallback: ErrorBoundaryPropsWithComponent['FallbackComponent'];
-}
+import { RemoteComponentProps, RemoteAppParams } from '../types';
const RemoteAppWrapper = forwardRef(function (
- props: RemoteAppParams & RenderFnParams,
+ props: RemoteAppParams & RemoteComponentProps,
ref,
) {
const {
diff --git a/packages/bridge/bridge-react/src/remote/create.tsx b/packages/bridge/bridge-react/src/remote/create.tsx
index fe138b55ca3..ec0f179ab1d 100644
--- a/packages/bridge/bridge-react/src/remote/create.tsx
+++ b/packages/bridge/bridge-react/src/remote/create.tsx
@@ -1,33 +1,19 @@
import React, { forwardRef } from 'react';
-import {
- ErrorBoundary,
- ErrorBoundaryPropsWithComponent,
-} from 'react-error-boundary';
+import { ErrorBoundary } from 'react-error-boundary';
import { LoggerInstance } from '../utils';
import RemoteApp from './component';
-import type { ProviderParams } from '@module-federation/bridge-shared';
-
-export interface RenderFnParams extends ProviderParams {
- dom?: any;
-}
-
-interface RemoteModule {
- provider: () => {
- render: (info: RenderFnParams) => void;
- destroy: (info: { dom: any }) => void;
- };
-}
+import {
+ RemoteComponentParams,
+ RemoteComponentProps,
+ RemoteModule,
+} from '../types';
-type LazyRemoteComponentInfo = {
- loader: () => Promise;
- loading: React.ReactNode;
- fallback: ErrorBoundaryPropsWithComponent['FallbackComponent'];
- export?: E;
-};
+type LazyRemoteComponentInfo = RemoteComponentParams;
-function createLazyRemoteComponent(
- info: LazyRemoteComponentInfo,
-) {
+function createLazyRemoteComponent<
+ T = Record,
+ E extends keyof T = keyof T,
+>(info: LazyRemoteComponentInfo) {
const exportName = info?.export || 'default';
return React.lazy(async () => {
LoggerInstance.debug(`createRemoteComponent LazyComponent create >>>`, {
@@ -49,10 +35,7 @@ function createLazyRemoteComponent(
if (exportName in m && typeof exportFn === 'function') {
const RemoteAppComponent = forwardRef<
HTMLDivElement,
- {
- basename?: ProviderParams['basename'];
- memoryRoute?: ProviderParams['memoryRoute'];
- }
+ RemoteComponentProps
>((props, ref) => {
return (
(
});
}
-export function createRemoteComponent(
- info: LazyRemoteComponentInfo,
-) {
- type ExportType = T[E] extends (...args: any) => any
- ? ReturnType
- : never;
-
- type RawComponentType = '__BRIDGE_FN__' extends keyof ExportType
- ? ExportType['__BRIDGE_FN__'] extends (...args: any) => any
- ? Parameters[0]
- : {}
- : {};
-
+export function createRemoteComponent<
+ T = Record,
+ E extends keyof T = keyof T,
+>(info: LazyRemoteComponentInfo) {
const LazyComponent = createLazyRemoteComponent(info);
- return forwardRef(
- (props, ref) => {
- return (
-
-
-
-
-
- );
- },
- );
+ return forwardRef((props, ref) => {
+ return (
+
+
+
+
+
+ );
+ });
}
diff --git a/packages/bridge/bridge-react/src/types.ts b/packages/bridge/bridge-react/src/types.ts
new file mode 100644
index 00000000000..77e1c63314e
--- /dev/null
+++ b/packages/bridge/bridge-react/src/types.ts
@@ -0,0 +1,128 @@
+import * as React from 'react';
+import { ErrorBoundaryPropsWithComponent } from 'react-error-boundary';
+
+/**
+ * Options for creating a React root
+ */
+export interface CreateRootOptions {
+ identifierPrefix?: string;
+ onRecoverableError?: (error: unknown) => void;
+ transitionCallbacks?: unknown;
+}
+
+/**
+ * Interface for a React root object
+ */
+export interface Root {
+ render(children: React.ReactNode): void;
+ unmount(): void;
+}
+
+/**
+ * Type for a root element, which can be either an HTMLElement or a React root
+ */
+export type RootType = HTMLElement | Root;
+
+/**
+ * Parameters for the render function
+ */
+export interface RenderParams {
+ moduleName?: string;
+ basename?: string;
+ memoryRoute?: {
+ entryPath: string;
+ initialState?: Record;
+ };
+ dom: HTMLElement;
+ [key: string]: unknown;
+}
+
+/**
+ * Parameters for the destroy function
+ */
+export interface DestroyParams {
+ moduleName: string;
+ dom: HTMLElement;
+}
+
+/**
+ * Parameters for the provider function
+ */
+export interface ProviderParams {
+ moduleName?: string;
+ basename?: string;
+ memoryRoute?: {
+ entryPath: string;
+ initialState?: Record;
+ };
+ style?: React.CSSProperties;
+ className?: string;
+}
+
+/**
+ * Parameters for the render function, extending ProviderParams
+ */
+export interface RenderFnParams extends ProviderParams {
+ dom: HTMLElement;
+ fallback?: React.ComponentType<{ error: Error }>;
+ [key: string]: unknown;
+}
+
+/**
+ * Parameters for the provider function
+ */
+export interface ProviderFnParams {
+ rootComponent: React.ComponentType;
+ render?: (
+ App: React.ReactElement,
+ id?: HTMLElement | string,
+ ) => RootType | Promise;
+ createRoot?: (
+ container: Element | DocumentFragment,
+ options?: CreateRootOptions,
+ ) => Root;
+}
+
+/**
+ * Parameters for the remote component
+ */
+export interface RemoteComponentProps> {
+ props?: T;
+ fallback?: React.ComponentType<{ error: Error }>;
+ loading?: React.ReactNode;
+ [key: string]: unknown;
+}
+
+/**
+ * Parameters for the remote component loader
+ */
+export interface RemoteComponentParams<
+ T = Record,
+ E extends keyof T = keyof T,
+> {
+ loader: () => Promise;
+ loading: React.ReactNode;
+ fallback: React.ComponentType<{ error: Error }>;
+ export?: E;
+ props?: T;
+}
+
+/**
+ * Interface for a remote module provider
+ */
+export interface RemoteModule {
+ provider: () => {
+ render: (info: RenderFnParams) => void;
+ destroy: (info: { dom: any }) => void;
+ };
+}
+
+/**
+ * Parameters for a remote app component
+ */
+export interface RemoteAppParams extends ProviderParams {
+ moduleName: string;
+ providerInfo: NonNullable;
+ exportName: string | number | symbol;
+ fallback: ErrorBoundaryPropsWithComponent['FallbackComponent'];
+}