Skip to content

Commit

Permalink
feat: add BridgeReactPlugin to get federation instance (#3234)
Browse files Browse the repository at this point in the history
Co-authored-by: Zack Jackson <[email protected]>
  • Loading branch information
danpeen and ScriptedAlchemy authored Nov 20, 2024
1 parent 8e47a69 commit ae5ee1e
Show file tree
Hide file tree
Showing 10 changed files with 67 additions and 33 deletions.
5 changes: 5 additions & 0 deletions .changeset/small-bats-leave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@module-federation/bridge-react': patch
---

feat: mount bridge api to module instance
2 changes: 2 additions & 0 deletions apps/router-demo/router-host-2000/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
3 changes: 3 additions & 0 deletions apps/router-demo/router-remote1-2001/rsbuild.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
11 changes: 8 additions & 3 deletions packages/bridge/bridge-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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:*"
}
}
17 changes: 9 additions & 8 deletions packages/bridge/bridge-react/src/create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,16 @@ interface RemoteModule {
};
}

function createLazyRemoteComponent<T, E extends keyof T>(info: {
type LazyRemoteComponentInfo<T, E extends keyof T> = {
loader: () => Promise<T>;
loading: React.ReactNode;
fallback: ErrorBoundaryPropsWithComponent['FallbackComponent'];
export?: E;
}) {
};

function createLazyRemoteComponent<T, E extends keyof T>(
info: LazyRemoteComponentInfo<T, E>,
) {
const exportName = info?.export || 'default';
return React.lazy(async () => {
LoggerInstance.log(`createRemoteComponent LazyComponent create >>>`, {
Expand Down Expand Up @@ -83,12 +87,9 @@ function createLazyRemoteComponent<T, E extends keyof T>(info: {
});
}

export function createRemoteComponent<T, E extends keyof T>(info: {
loader: () => Promise<T>;
loading: React.ReactNode;
fallback: ErrorBoundaryPropsWithComponent['FallbackComponent'];
export?: E;
}) {
export function createRemoteComponent<T, E extends keyof T>(
info: LazyRemoteComponentInfo<T, E>,
) {
type ExportType = T[E] extends (...args: any) => any
? ReturnType<T[E]>
: never;
Expand Down
20 changes: 20 additions & 0 deletions packages/bridge/bridge-react/src/plugin.ts
Original file line number Diff line number Diff line change
@@ -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;
13 changes: 7 additions & 6 deletions packages/bridge/bridge-react/src/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -20,7 +20,7 @@ type DestroyParams = {
};
type RootType = HTMLElement | ReactDOMClient.Root;

type ProviderFnParams<T> = {
export type ProviderFnParams<T> = {
rootComponent: React.ComponentType<T>;
render?: (
App: React.ReactElement,
Expand All @@ -31,8 +31,11 @@ type ProviderFnParams<T> = {
export function createBridgeComponent<T>(bridgeInfo: ProviderFnParams<T>) {
return () => {
const rootMap = new Map<any, RootType>();
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;
Expand Down Expand Up @@ -95,15 +98,13 @@ export function createBridgeComponent<T>(bridgeInfo: ProviderFnParams<T>) {
const renderFn = bridgeInfo?.render || ReactDOM.render;
renderFn?.(rootComponentWithErrorBoundary, info.dom);
}

instance?.bridgeHook?.lifecycle?.afterBridgeRender?.emit(info) || {};
},

async destroy(info: DestroyParams) {
LoggerInstance.log(`createBridgeComponent destroy Info`, {
dom: info.dom,
});

instance?.bridgeHook?.lifecycle?.beforeBridgeDestroy?.emit(info);

// call destroy function
Expand Down
19 changes: 9 additions & 10 deletions packages/bridge/bridge-react/src/remote/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -53,15 +53,16 @@ const RemoteAppWrapper = forwardRef(function (
...resProps
} = props;

const instance = federationRuntime.instance;
const rootRef: React.MutableRefObject<HTMLDivElement | null> =
ref && 'current' in ref
? (ref as React.MutableRefObject<HTMLDivElement | null>)
: useRef(null);

const renderDom: React.MutableRefObject<HTMLElement | null> = useRef(null);
const providerInfoRef = useRef<any>(null);
const hostInstance = getInstance();
LoggerInstance.log(`RemoteAppWrapper hostInstance >>>`, hostInstance);

LoggerInstance.log(`RemoteAppWrapper instance from props >>>`, instance);

useEffect(() => {
const renderTimeout = setTimeout(() => {
Expand All @@ -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 () => {
Expand All @@ -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,
Expand All @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion packages/bridge/bridge-react/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand All @@ -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: [
{
Expand Down
8 changes: 3 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit ae5ee1e

Please sign in to comment.