-
-
Notifications
You must be signed in to change notification settings - Fork 272
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add BridgeReactPlugin to get federation instance #3234
Changes from 7 commits
c7dbc4b
0a52f84
9bff805
153b377
d8dc59f
fbbdcbd
9b01195
a3674d3
3f767c5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,4 +1,5 @@ | ||||||||||||||||||||||||||||||||||||||||||
import React, { forwardRef } from 'react'; | ||||||||||||||||||||||||||||||||||||||||||
import type { FederationHost } from '@module-federation/runtime'; | ||||||||||||||||||||||||||||||||||||||||||
import { | ||||||||||||||||||||||||||||||||||||||||||
ErrorBoundary, | ||||||||||||||||||||||||||||||||||||||||||
ErrorBoundaryPropsWithComponent, | ||||||||||||||||||||||||||||||||||||||||||
|
@@ -18,12 +19,17 @@ 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; | ||||||||||||||||||||||||||||||||||||||||||
}) { | ||||||||||||||||||||||||||||||||||||||||||
instance?: FederationHost; | ||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
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 >>>`, { | ||||||||||||||||||||||||||||||||||||||||||
|
@@ -58,6 +64,7 @@ function createLazyRemoteComponent<T, E extends keyof T>(info: { | |||||||||||||||||||||||||||||||||||||||||
exportName={info.export || 'default'} | ||||||||||||||||||||||||||||||||||||||||||
fallback={info.fallback} | ||||||||||||||||||||||||||||||||||||||||||
ref={ref} | ||||||||||||||||||||||||||||||||||||||||||
instance={info.instance} | ||||||||||||||||||||||||||||||||||||||||||
{...props} | ||||||||||||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||
|
@@ -83,12 +90,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; | ||||||||||||||||||||||||||||||||||||||||||
|
@@ -112,3 +116,13 @@ export function createRemoteComponent<T, E extends keyof T>(info: { | |||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
export function createRemoteComponentWithInstance<T, E extends keyof T>( | ||||||||||||||||||||||||||||||||||||||||||
instance: FederationHost, | ||||||||||||||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||||||||||||||
return (info: LazyRemoteComponentInfo<T, E>) => { | ||||||||||||||||||||||||||||||||||||||||||
return createLazyRemoteComponent({ ...info, instance }); | ||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The function name
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
export type CreateRemoteComponent = typeof createRemoteComponent; |
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,5 +1,24 @@ | ||||||||||||||
export { createRemoteComponent } from './create'; | ||||||||||||||
export { createBridgeComponent } from './provider'; | ||||||||||||||
import type { FederationRuntimePlugin } from '@module-federation/runtime'; | ||||||||||||||
import { createRemoteComponentWithInstance } from './create'; | ||||||||||||||
import { createBridgeComponentWithInstance } from './provider'; | ||||||||||||||
import type { CreateRemoteComponent } from './create'; | ||||||||||||||
import type { CreateBridgeComponent } from './provider'; | ||||||||||||||
|
||||||||||||||
let createRemoteComponent: CreateRemoteComponent; | ||||||||||||||
let createBridgeComponent: CreateBridgeComponent; | ||||||||||||||
function BridgeReactPlugin(): FederationRuntimePlugin { | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These variables are mutable globals which could lead to race conditions and make testing difficult. Consider using a class or module pattern instead. Here's a suggestion:
Suggested change
|
||||||||||||||
return { | ||||||||||||||
name: 'bridge-react-plugin', | ||||||||||||||
beforeInit(args) { | ||||||||||||||
// @ts-ignore | ||||||||||||||
createRemoteComponent = createRemoteComponentWithInstance(args.origin); | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove the |
||||||||||||||
createBridgeComponent = createBridgeComponentWithInstance(args.origin); | ||||||||||||||
return args; | ||||||||||||||
}, | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||||||||||||||
}; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
export { BridgeReactPlugin, createRemoteComponent, createBridgeComponent }; | ||||||||||||||
export type { | ||||||||||||||
ProviderParams, | ||||||||||||||
RenderFnParams, | ||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
import { BridgeReactPlugin } from './index'; | ||
export default BridgeReactPlugin; |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -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 type { FederationHost } from '@module-federation/runtime'; | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
type RenderParams = RenderFnParams & { | ||||||||||||||||||||||||
[key: string]: unknown; | ||||||||||||||||||||||||
Comment on lines
14
to
15
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The RenderParams type extends RenderFnParams but allows arbitrary additional properties through an index signature. This could lead to type safety issues. Consider explicitly defining expected properties or using a more specific type:
Suggested change
For the second code block (lines 134-144): |
||||||||||||||||||||||||
|
@@ -26,13 +26,17 @@ type ProviderFnParams<T> = { | |||||||||||||||||||||||
App: React.ReactElement, | ||||||||||||||||||||||||
id?: HTMLElement | string, | ||||||||||||||||||||||||
) => RootType | Promise<RootType>; | ||||||||||||||||||||||||
instance?: FederationHost; | ||||||||||||||||||||||||
}; | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
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 } = bridgeInfo; | ||||||||||||||||||||||||
LoggerInstance.log( | ||||||||||||||||||||||||
`createBridgeComponent instance from props >>>`, | ||||||||||||||||||||||||
instance, | ||||||||||||||||||||||||
); | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
const RawComponent = (info: { propsInfo: T; appInfo: ProviderParams }) => { | ||||||||||||||||||||||||
const { appInfo, propsInfo, ...restProps } = info; | ||||||||||||||||||||||||
|
@@ -95,15 +99,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 | ||||||||||||||||||||||||
|
@@ -115,7 +117,9 @@ export function createBridgeComponent<T>(bridgeInfo: ProviderFnParams<T>) { | |||||||||||||||||||||||
ReactDOM.unmountComponentAtNode(info.dom); | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
instance?.bridgeHook?.lifecycle?.afterBridgeDestroy?.emit(info); | ||||||||||||||||||||||||
(instance as any)?.bridgeHook?.lifecycle?.afterBridgeDestroy?.emit( | ||||||||||||||||||||||||
info, | ||||||||||||||||||||||||
); | ||||||||||||||||||||||||
}, | ||||||||||||||||||||||||
rawComponent: bridgeInfo.rootComponent, | ||||||||||||||||||||||||
__BRIDGE_FN__: (_args: T) => {}, | ||||||||||||||||||||||||
|
@@ -130,3 +134,11 @@ export function ShadowRoot(info: { children: () => JSX.Element }) { | |||||||||||||||||||||||
|
||||||||||||||||||||||||
return <div ref={domRef}>{root && <info.children />}</div>; | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
export function createBridgeComponentWithInstance<T>(instance: FederationHost) { | ||||||||||||||||||||||||
return (bridgeInfo: ProviderFnParams<T>) => { | ||||||||||||||||||||||||
return createBridgeComponent({ ...bridgeInfo, instance }); | ||||||||||||||||||||||||
}; | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The createBridgeComponentWithInstance function could benefit from memoization since it creates a new function on each call. Consider using React.useCallback or memoizing the returned function:
Suggested change
These suggestions focus on improving type safety and performance while maintaining the code's functionality within their respective commit ranges. |
||||||||||||||||||||||||
|
||||||||||||||||||||||||
export type CreateBridgeComponent = typeof createBridgeComponent; |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function could benefit from error handling for invalid input. Consider validating the instance parameter and adding error handling: