diff --git a/.changeset/small-bats-leave.md b/.changeset/small-bats-leave.md new file mode 100644 index 0000000000..92c33f20be --- /dev/null +++ b/.changeset/small-bats-leave.md @@ -0,0 +1,5 @@ +--- +'@module-federation/bridge-react': patch +--- + +feat: mount bridge api to module instance diff --git a/apps/router-demo/router-host-2000/src/App.tsx b/apps/router-demo/router-host-2000/src/App.tsx index 5ac100be84..bc9c101528 100644 --- a/apps/router-demo/router-host-2000/src/App.tsx +++ b/apps/router-demo/router-host-2000/src/App.tsx @@ -7,11 +7,13 @@ import Navigation from './navigation'; import Detail from './pages/Detail'; import Home from './pages/Home'; import './App.css'; +import BridgeReactPlugin from '@module-federation/bridge-react/plugin'; init({ name: 'federation_consumer', remotes: [], plugins: [ + BridgeReactPlugin(), RetryPlugin({ fetch: { url: 'http://localhost:2008/not-exist-mf-manifest.json', diff --git a/apps/router-demo/router-remote1-2001/rsbuild.config.ts b/apps/router-demo/router-remote1-2001/rsbuild.config.ts index 7abeb0ebdc..8d7ac525d5 100644 --- a/apps/router-demo/router-remote1-2001/rsbuild.config.ts +++ b/apps/router-demo/router-remote1-2001/rsbuild.config.ts @@ -33,6 +33,9 @@ export default defineConfig({ pluginReact(), pluginModuleFederation({ name: 'remote1', + runtimePlugins: [ + require.resolve('@module-federation/bridge-react/plugin'), + ], exposes: { './button': './src/button.tsx', './export-app': './src/export-App.tsx', diff --git a/packages/bridge/bridge-react/package.json b/packages/bridge/bridge-react/package.json index aa3fb33bca..2c770f7c2c 100644 --- a/packages/bridge/bridge-react/package.json +++ b/packages/bridge/bridge-react/package.json @@ -26,6 +26,11 @@ "import": "./dist/router.es.js", "require": "./dist/router.cjs.js" }, + "./plugin": { + "types": "./dist/plugin.d.ts", + "import": "./dist/plugin.es.js", + "require": "./dist/plugin.es.js" + }, "./router-v5": { "types": "./dist/router-v5.d.ts", "import": "./dist/router-v5.es.js", @@ -47,8 +52,7 @@ "@loadable/component": "^5.16.4", "@module-federation/bridge-shared": "workspace:*", "@module-federation/sdk": "workspace:*", - "react-error-boundary": "^4.0.13", - "@module-federation/runtime": "workspace:*" + "react-error-boundary": "^4.0.13" }, "peerDependencies": { "react": ">=16.9.0", @@ -68,6 +72,7 @@ "react-router-dom": "6.22.3", "typescript": "^5.2.2", "vite": "^5.2.14", - "vite-plugin-dts": "^3.9.1" + "vite-plugin-dts": "^3.9.1", + "@module-federation/runtime": "workspace:*" } } diff --git a/packages/bridge/bridge-react/src/create.tsx b/packages/bridge/bridge-react/src/create.tsx index 4098a0788b..9e51e92bb8 100644 --- a/packages/bridge/bridge-react/src/create.tsx +++ b/packages/bridge/bridge-react/src/create.tsx @@ -18,12 +18,16 @@ interface RemoteModule { }; } -function createLazyRemoteComponent(info: { +type LazyRemoteComponentInfo = { loader: () => Promise; loading: React.ReactNode; fallback: ErrorBoundaryPropsWithComponent['FallbackComponent']; export?: E; -}) { +}; + +function createLazyRemoteComponent( + info: LazyRemoteComponentInfo, +) { const exportName = info?.export || 'default'; return React.lazy(async () => { LoggerInstance.log(`createRemoteComponent LazyComponent create >>>`, { @@ -83,12 +87,9 @@ function createLazyRemoteComponent(info: { }); } -export function createRemoteComponent(info: { - loader: () => Promise; - loading: React.ReactNode; - fallback: ErrorBoundaryPropsWithComponent['FallbackComponent']; - export?: E; -}) { +export function createRemoteComponent( + info: LazyRemoteComponentInfo, +) { type ExportType = T[E] extends (...args: any) => any ? ReturnType : never; diff --git a/packages/bridge/bridge-react/src/plugin.ts b/packages/bridge/bridge-react/src/plugin.ts new file mode 100644 index 0000000000..c02e83f0f1 --- /dev/null +++ b/packages/bridge/bridge-react/src/plugin.ts @@ -0,0 +1,20 @@ +import type { FederationRuntimePlugin } from '@module-federation/runtime'; +import type { FederationHost } from '@module-federation/runtime'; + +export type FederationRuntimeType = { + instance: FederationHost | null; +}; + +export const federationRuntime: FederationRuntimeType = { instance: null }; + +function BridgeReactPlugin(): FederationRuntimePlugin { + return { + name: 'bridge-react-plugin', + beforeInit(args) { + federationRuntime.instance = args.origin; + return args; + }, + }; +} + +export default BridgeReactPlugin; diff --git a/packages/bridge/bridge-react/src/provider.tsx b/packages/bridge/bridge-react/src/provider.tsx index 551458d19c..d0f0f02b93 100644 --- a/packages/bridge/bridge-react/src/provider.tsx +++ b/packages/bridge/bridge-react/src/provider.tsx @@ -9,7 +9,7 @@ import type { import { ErrorBoundary } from 'react-error-boundary'; import { RouterContext } from './context'; import { LoggerInstance, atLeastReact18 } from './utils'; -import { getInstance } from '@module-federation/runtime'; +import { federationRuntime } from './plugin'; type RenderParams = RenderFnParams & { [key: string]: unknown; @@ -20,7 +20,7 @@ type DestroyParams = { }; type RootType = HTMLElement | ReactDOMClient.Root; -type ProviderFnParams = { +export type ProviderFnParams = { rootComponent: React.ComponentType; render?: ( App: React.ReactElement, @@ -31,8 +31,11 @@ type ProviderFnParams = { export function createBridgeComponent(bridgeInfo: ProviderFnParams) { return () => { const rootMap = new Map(); - const instance = getInstance(); - LoggerInstance.log(`createBridgeComponent remote instance`, instance); + const instance = federationRuntime.instance; + LoggerInstance.log( + `createBridgeComponent instance from props >>>`, + instance, + ); const RawComponent = (info: { propsInfo: T; appInfo: ProviderParams }) => { const { appInfo, propsInfo, ...restProps } = info; @@ -95,7 +98,6 @@ export function createBridgeComponent(bridgeInfo: ProviderFnParams) { const renderFn = bridgeInfo?.render || ReactDOM.render; renderFn?.(rootComponentWithErrorBoundary, info.dom); } - instance?.bridgeHook?.lifecycle?.afterBridgeRender?.emit(info) || {}; }, @@ -103,7 +105,6 @@ export function createBridgeComponent(bridgeInfo: ProviderFnParams) { LoggerInstance.log(`createBridgeComponent destroy Info`, { dom: info.dom, }); - instance?.bridgeHook?.lifecycle?.beforeBridgeDestroy?.emit(info); // call destroy function diff --git a/packages/bridge/bridge-react/src/remote/index.tsx b/packages/bridge/bridge-react/src/remote/index.tsx index a13a9aaf1d..c437a60d39 100644 --- a/packages/bridge/bridge-react/src/remote/index.tsx +++ b/packages/bridge/bridge-react/src/remote/index.tsx @@ -10,7 +10,7 @@ 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 { getInstance } from '@module-federation/runtime'; +import { federationRuntime } from '../plugin'; declare const __APP_VERSION__: string; export interface RenderFnParams extends ProviderParams { @@ -53,6 +53,7 @@ const RemoteAppWrapper = forwardRef(function ( ...resProps } = props; + const instance = federationRuntime.instance; const rootRef: React.MutableRefObject = ref && 'current' in ref ? (ref as React.MutableRefObject) @@ -60,8 +61,8 @@ const RemoteAppWrapper = forwardRef(function ( const renderDom: React.MutableRefObject = useRef(null); const providerInfoRef = useRef(null); - const hostInstance = getInstance(); - LoggerInstance.log(`RemoteAppWrapper hostInstance >>>`, hostInstance); + + LoggerInstance.log(`RemoteAppWrapper instance from props >>>`, instance); useEffect(() => { const renderTimeout = setTimeout(() => { @@ -84,18 +85,16 @@ const RemoteAppWrapper = forwardRef(function ( LoggerInstance.log( `createRemoteComponent LazyComponent hostInstance >>>`, - hostInstance, + instance, ); const beforeBridgeRenderRes = - hostInstance?.bridgeHook?.lifecycle?.beforeBridgeRender?.emit( + instance?.bridgeHook?.lifecycle?.beforeBridgeRender?.emit( renderProps, ) || {}; // @ts-ignore renderProps = { ...renderProps, ...beforeBridgeRenderRes.extraProps }; providerReturn.render(renderProps); - hostInstance?.bridgeHook?.lifecycle?.afterBridgeRender?.emit( - renderProps, - ); + instance?.bridgeHook?.lifecycle?.afterBridgeRender?.emit(renderProps); }); return () => { @@ -107,7 +106,7 @@ const RemoteAppWrapper = forwardRef(function ( { moduleName, basename, dom: renderDom.current }, ); - hostInstance?.bridgeHook?.lifecycle?.beforeBridgeDestroy?.emit({ + instance?.bridgeHook?.lifecycle?.beforeBridgeDestroy?.emit({ moduleName, dom: renderDom.current, basename, @@ -121,7 +120,7 @@ const RemoteAppWrapper = forwardRef(function ( dom: renderDom.current, }); - hostInstance?.bridgeHook?.lifecycle?.afterBridgeDestroy?.emit({ + instance?.bridgeHook?.lifecycle?.afterBridgeDestroy?.emit({ moduleName, dom: renderDom.current, basename, diff --git a/packages/bridge/bridge-react/vite.config.ts b/packages/bridge/bridge-react/vite.config.ts index 3379bd90ef..7c647cfa59 100644 --- a/packages/bridge/bridge-react/vite.config.ts +++ b/packages/bridge/bridge-react/vite.config.ts @@ -21,6 +21,7 @@ export default defineConfig({ lib: { entry: { index: path.resolve(__dirname, 'src/index.ts'), + plugin: path.resolve(__dirname, 'src/plugin.ts'), router: path.resolve(__dirname, 'src/router.tsx'), 'router-v5': path.resolve(__dirname, 'src/router-v5.tsx'), 'router-v6': path.resolve(__dirname, 'src/router-v6.tsx'), @@ -36,7 +37,6 @@ export default defineConfig({ 'react-router-dom/', 'react-router-dom/index.js', 'react-router-dom/dist/index.js', - '@module-federation/runtime', ], plugins: [ { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 48b2b25761..d0ad0abb2e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1807,9 +1807,6 @@ importers: '@module-federation/bridge-shared': specifier: workspace:* version: link:../bridge-shared - '@module-federation/runtime': - specifier: workspace:* - version: link:../../runtime '@module-federation/sdk': specifier: workspace:* version: link:../../sdk @@ -1817,6 +1814,9 @@ importers: specifier: ^4.0.13 version: 4.0.13(react@18.3.1) devDependencies: + '@module-federation/runtime': + specifier: workspace:* + version: link:../../runtime '@testing-library/react': specifier: 15.0.7 version: 15.0.7(@types/react@18.2.79)(react-dom@18.3.1)(react@18.3.1) @@ -31343,8 +31343,6 @@ packages: peerDependenciesMeta: webpack: optional: true - webpack-sources: - optional: true dependencies: webpack: 5.93.0(@swc/core@1.7.26)(esbuild@0.18.20) webpack-sources: 3.2.3