-
-
Notifications
You must be signed in to change notification settings - Fork 206
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(module-federation/bridge): Enhance Bridge capabilities and fix some issues #2792
Conversation
🦋 Changeset detectedLatest commit: 87b0110 The changes in this PR will be included in the next version bump. This PR includes changesets to release 36 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
✅ Deploy Preview for module-federation-docs ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
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.
Summary
This pull request enhances the capabilities of the Module Federation Bridge and fixes some issues. The key changes are:
- Introduced support for a custom
provider.render
function, allowing users to customize the rendering logic of the bridge component. - Added support for passing
ref
and other props to remote components, enabling users to access the rendered DOM elements. - Fixed issues related to types and React Router DOM proxy functionality.
These changes aim to improve the flexibility and usability of the Module Federation Bridge, providing users with more control over the rendering and integration of remote components within their applications.
File Summaries
File | Summary |
---|---|
packages/bridge/bridge-react-webpack-plugin/src/index.ts | The code updates the webpack configuration by modifying the resolve alias. It adds a new alias entry for the target file path while preserving existing aliases, ensuring that the new alias can be overridden by user-defined aliases. |
packages/bridge/bridge-shared/src/type.ts | The code defines two interfaces: ProviderParams and RenderFnParams. ProviderParams includes optional properties for module configuration, while RenderFnParams extends ProviderParams and adds a required 'dom' property of type HTMLElement. |
packages/bridge/bridge-react/src/create.tsx | The code enhances the bridge component's functionality by adding support for custom rendering, ref passing, and prop forwarding. It introduces a new interface for render function parameters and improves type definitions for remote components. |
packages/bridge/bridge-react/src/provider.tsx | The code enhances the bridge component's functionality by adding support for custom rendering, ref passing, and improved error handling. It introduces a flexible rendering mechanism that adapts to different React versions and allows users to provide their own render logic. |
packages/bridge/bridge-react/src/remote/index.tsx | The code enhances a React bridge component for module federation. It introduces support for custom render logic, passing refs and props to remote components, and improves type handling and React Router DOM proxy functionality. |
packages/bridge/bridge-react/src/router-v5.tsx | The code enhances router functionality by introducing WrapperRouter and WrapperRouterProvider components. These components adapt routing behavior based on context, supporting both memory and browser routing scenarios with improved flexibility for module federation. |
packages/bridge/bridge-react/src/router-v6.tsx | The code enhances router functionality by introducing WrapperRouter and WrapperRouterProvider components. These components dynamically choose between memory and browser routers based on context, allowing for flexible routing configurations in different environments. |
packages/dts-plugin/src/core/configurations/remotePlugin.ts | The code configures TypeScript compiler options by merging default options with user-defined options from tsconfig.json. It also prepares a list of files to compile, including exposed components, declaration files, and additional specified files. |
packages/manifest/src/StatsManager.ts | The code filters files from a remote entry chunk, excluding hot update and CSS files. It then asserts that at least one file is found and that there is exactly one file for the remote entry chunk. |
packages/node/src/index.ts | The code exports several plugins related to module federation and chunk management in a webpack environment. These plugins include ChunkCorrelationPlugin, RemotePublicPathPlugin, EntryChunkTrackerPlugin, and UniverseEntryChunkTrackerPlugin. |
packages/node/src/plugins/UniverseEntryChunkTrackerPlugin.ts | The UniverseEntryChunkTrackerPlugin is implemented to track entry chunks in a webpack compilation. It injects a script that adds module filenames to a global cache set and uses webpack's EntryPlugin to include this script in the build process. |
packages/runtime/src/utils/preload.ts | The code enhances remote module loading functionality by adding hooks for script creation and handling different scenarios for loading remote entries. It also improves error handling and type checking for script element creation. |
packages/enhanced/src/wrapper/ModuleFederationPlugin.ts | The code defines a ModuleFederationPlugin class with methods for applying the plugin to a webpack compiler and retrieving stats resource information. It imports necessary types and utilities for module federation and webpack integration. |
packages/runtime/src/type/config.ts | The code defines types for configuration options in a module federation system. It includes properties for scope, dependencies, loading strategy, and a loaded flag. Additionally, it introduces a SharedGetter type for module loading functions. |
packages/runtime/src/utils/share.ts | The code enhances the configuration of shared modules by merging user-provided settings with default values. It handles various properties including 'loaded', 'version', and 'scope', providing flexibility in how these are defined and processed. |
packages/bridge/bridge-react/src/router.tsx | The code enhances router functionality by introducing wrapper components for BrowserRouter and RouterProvider. These wrappers add support for memory routing and custom basenames, improving flexibility in routing configurations. |
Commits reviewed:
67fa05bad65536ba0315051b0c868901a97187f1...5b1bbc47a7d016fb7f1da3b153474214a56944ba
0de1c8365c73245995c5d5b5e68cbda00046b6de...042da07a33bab70d0a4db7688664d6ef68b8bf94
67fa05bad65536ba0315051b0c868901a97187f1...04480a024ceb093025746847a741ac892aeed2f4
67fa05bad65536ba0315051b0c868901a97187f1...04480a024ceb093025746847a741ac892aeed2f4
67fa05bad65536ba0315051b0c868901a97187f1...04480a024ceb093025746847a741ac892aeed2f4
67fa05bad65536ba0315051b0c868901a97187f1...5b1bbc47a7d016fb7f1da3b153474214a56944ba
67fa05bad65536ba0315051b0c868901a97187f1...5b1bbc47a7d016fb7f1da3b153474214a56944ba
67fa05bad65536ba0315051b0c868901a97187f1...7fdae671f2077fa066a27ee1a9e432cf9c9b61fa
67fa05bad65536ba0315051b0c868901a97187f1...7fdae671f2077fa066a27ee1a9e432cf9c9b61fa
67fa05bad65536ba0315051b0c868901a97187f1...7fdae671f2077fa066a27ee1a9e432cf9c9b61fa
67fa05bad65536ba0315051b0c868901a97187f1...ba17cea4ce06456e4387f609f499137e0687845d
67fa05bad65536ba0315051b0c868901a97187f1...ba17cea4ce06456e4387f609f499137e0687845d
67fa05bad65536ba0315051b0c868901a97187f1...ba17cea4ce06456e4387f609f499137e0687845d
67fa05bad65536ba0315051b0c868901a97187f1...dec12d98c53c9a988ff2f900c13907ef8a6073d3
67fa05bad65536ba0315051b0c868901a97187f1...dec12d98c53c9a988ff2f900c13907ef8a6073d3
67fa05bad65536ba0315051b0c868901a97187f1...dec12d98c53c9a988ff2f900c13907ef8a6073d3
67fa05bad65536ba0315051b0c868901a97187f1...950e83e4480220573f5b672eba6539a2c970b892
67fa05bad65536ba0315051b0c868901a97187f1...950e83e4480220573f5b672eba6539a2c970b892
67fa05bad65536ba0315051b0c868901a97187f1...950e83e4480220573f5b672eba6539a2c970b892
67fa05bad65536ba0315051b0c868901a97187f1...fa6aba4b7190960dcafd5b6c120d02db3bb56dab
67fa05bad65536ba0315051b0c868901a97187f1...fa6aba4b7190960dcafd5b6c120d02db3bb56dab
67fa05bad65536ba0315051b0c868901a97187f1...fa6aba4b7190960dcafd5b6c120d02db3bb56dab
67fa05bad65536ba0315051b0c868901a97187f1...38d001cb346c3145e30ea874a3305af37e044e48
67fa05bad65536ba0315051b0c868901a97187f1...38d001cb346c3145e30ea874a3305af37e044e48
67fa05bad65536ba0315051b0c868901a97187f1...38d001cb346c3145e30ea874a3305af37e044e48
67fa05bad65536ba0315051b0c868901a97187f1...7c7ab867cb24dca4ac47524f20296dca67e26e9b
67fa05bad65536ba0315051b0c868901a97187f1...7c7ab867cb24dca4ac47524f20296dca67e26e9b
67fa05bad65536ba0315051b0c868901a97187f1...6bc3a47109aa2b732c99e87a620d544756214db8
67fa05bad65536ba0315051b0c868901a97187f1...7c7ab867cb24dca4ac47524f20296dca67e26e9b
67fa05bad65536ba0315051b0c868901a97187f1...6bc3a47109aa2b732c99e87a620d544756214db8
67fa05bad65536ba0315051b0c868901a97187f1...6bc3a47109aa2b732c99e87a620d544756214db8
67fa05bad65536ba0315051b0c868901a97187f1...d5f3392c5482bd4853f9656ed066623478d9d06c
67fa05bad65536ba0315051b0c868901a97187f1...d5f3392c5482bd4853f9656ed066623478d9d06c
67fa05bad65536ba0315051b0c868901a97187f1...d5f3392c5482bd4853f9656ed066623478d9d06c
67fa05bad65536ba0315051b0c868901a97187f1...955e6b26bb43a87cd8f6547eec9107164bbda0a3
67fa05bad65536ba0315051b0c868901a97187f1...955e6b26bb43a87cd8f6547eec9107164bbda0a3
67fa05bad65536ba0315051b0c868901a97187f1...955e6b26bb43a87cd8f6547eec9107164bbda0a3
67fa05bad65536ba0315051b0c868901a97187f1...0196c57ad9d04cd810cdac67c74bd2ed5760192c
67fa05bad65536ba0315051b0c868901a97187f1...0196c57ad9d04cd810cdac67c74bd2ed5760192c
67fa05bad65536ba0315051b0c868901a97187f1...0196c57ad9d04cd810cdac67c74bd2ed5760192c
67fa05bad65536ba0315051b0c868901a97187f1...eb42c4abd1ddd32421d40b40974e8786083d45ed
67fa05bad65536ba0315051b0c868901a97187f1...a52efbf17f4ee914f870eb5058d2b839f0227c3c
67fa05bad65536ba0315051b0c868901a97187f1...a52efbf17f4ee914f870eb5058d2b839f0227c3c
67fa05bad65536ba0315051b0c868901a97187f1...a52efbf17f4ee914f870eb5058d2b839f0227c3c
67fa05bad65536ba0315051b0c868901a97187f1...35aba94c843b8308f35cae4c91d6df75c74f49da
67fa05bad65536ba0315051b0c868901a97187f1...35aba94c843b8308f35cae4c91d6df75c74f49da
67fa05bad65536ba0315051b0c868901a97187f1...35aba94c843b8308f35cae4c91d6df75c74f49da
67fa05bad65536ba0315051b0c868901a97187f1...dfa66662d9e725875474ca9cf3007521598a0d35
67fa05bad65536ba0315051b0c868901a97187f1...a69c4ec510225b3cbc19a10204ce3cc180d77291
67fa05bad65536ba0315051b0c868901a97187f1...dfa66662d9e725875474ca9cf3007521598a0d35
67fa05bad65536ba0315051b0c868901a97187f1...2b8aab4a80efe08ab78d2f174610e0c659bbb966
67fa05bad65536ba0315051b0c868901a97187f1...a69c4ec510225b3cbc19a10204ce3cc180d77291
67fa05bad65536ba0315051b0c868901a97187f1...a69c4ec510225b3cbc19a10204ce3cc180d77291
const updatedAlias = { | ||
// allow this config to be override | ||
[this.alias]: targetFilePath, | ||
...originalAlias, | ||
}; |
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 current implementation might unintentionally override user-defined aliases. Reverse the order of the spread operator and the new alias to prioritize user configurations:
const updatedAlias = { | |
// allow this config to be override | |
[this.alias]: targetFilePath, | |
...originalAlias, | |
}; | |
const updatedAlias = { | |
...originalAlias, | |
// allow this config to be override | |
[this.alias]: targetFilePath, | |
}; |
This change ensures that user-defined aliases take precedence over the plugin's default alias.
export interface RenderFnParams extends ProviderParams { | ||
dom?: any; | ||
} | ||
|
||
interface RemoteModule { | ||
provider: () => { | ||
render: ( | ||
info: ProviderParams & { | ||
dom: any; | ||
}, | ||
info: RenderFnParams, | ||
) => void; | ||
destroy: (info: { dom: any }) => void; | ||
}; |
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 'RemoteModule' interface defines 'provider' as a function returning an object with 'render' and 'destroy' methods. Consider making these methods optional to allow for more flexible implementations:
export interface RenderFnParams extends ProviderParams { | |
dom?: any; | |
} | |
interface RemoteModule { | |
provider: () => { | |
render: ( | |
info: ProviderParams & { | |
dom: any; | |
}, | |
info: RenderFnParams, | |
) => void; | |
destroy: (info: { dom: any }) => void; | |
}; | |
interface RemoteModule { | |
provider: () => { | |
render?: ( | |
info: RenderFnParams, | |
) => void; | |
destroy?: (info: { dom: any }) => void; | |
}; | |
} |
if (bridgeInfo?.render) { | ||
Promise.resolve(bridgeInfo?.render( | ||
<RawComponent | ||
propsInfo={propsInfo} | ||
appInfo={{ | ||
name, | ||
basename, | ||
memoryRoute, | ||
}} | ||
/>, | ||
info.dom | ||
)).then((root: RootType) => rootMap.set(info.dom, root)); |
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 custom render function is called with a Promise.resolve, which may not be necessary. Consider simplifying the code:
if (bridgeInfo?.render) { | |
Promise.resolve(bridgeInfo?.render( | |
<RawComponent | |
propsInfo={propsInfo} | |
appInfo={{ | |
name, | |
basename, | |
memoryRoute, | |
}} | |
/>, | |
info.dom | |
)).then((root: RootType) => rootMap.set(info.dom, root)); | |
if (bridgeInfo?.render) { | |
const root = await bridgeInfo.render( | |
<RawComponent | |
propsInfo={propsInfo} | |
appInfo={{ | |
name, | |
basename, | |
memoryRoute, | |
}} | |
/>, | |
info.dom | |
); | |
rootMap.set(info.dom, root); | |
} |
This change removes the unnecessary Promise.resolve and simplifies the code structure.
async destroy(info: { dom: HTMLElement }) { | ||
LoggerInstance.log(`createBridgeComponent destroy Info`, { | ||
dom: info.dom, | ||
}); | ||
if (atLeastReact18(React)) { | ||
const root = rootMap.get(info.dom); | ||
root?.unmount(); | ||
(root as ReactDOMClient.Root)?.unmount(); | ||
} else { | ||
ReactDOM.unmountComponentAtNode(info.dom); | ||
} |
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 destroy method doesn't remove the root from the rootMap after unmounting. Add cleanup to prevent memory leaks:
async destroy(info: { dom: HTMLElement }) { | |
LoggerInstance.log(`createBridgeComponent destroy Info`, { | |
dom: info.dom, | |
}); | |
if (atLeastReact18(React)) { | |
const root = rootMap.get(info.dom); | |
root?.unmount(); | |
(root as ReactDOMClient.Root)?.unmount(); | |
} else { | |
ReactDOM.unmountComponentAtNode(info.dom); | |
} | |
async destroy(info: { dom: HTMLElement }) { | |
LoggerInstance.log(`createBridgeComponent destroy Info`, { | |
dom: info.dom, | |
}); | |
if (atLeastReact18(React)) { | |
const root = rootMap.get(info.dom); | |
(root as ReactDOMClient.Root)?.unmount(); | |
rootMap.delete(info.dom); | |
} else { | |
ReactDOM.unmountComponentAtNode(info.dom); | |
} | |
} |
This ensures that the rootMap is properly cleaned up when components are destroyed.
const RouterProvider = (ReactRouterDom as any)['Router' + 'Provider']; | ||
const createMemoryRouter = (ReactRouterDom as any)['create' + 'MemoryRouter']; | ||
const createBrowserRouter = (ReactRouterDom as any)[ | ||
'create' + 'BrowserRouter' | ||
]; |
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.
Replace dynamic property access with direct imports to improve code readability and maintainability.
const RouterProvider = (ReactRouterDom as any)['Router' + 'Provider']; | |
const createMemoryRouter = (ReactRouterDom as any)['create' + 'MemoryRouter']; | |
const createBrowserRouter = (ReactRouterDom as any)[ | |
'create' + 'BrowserRouter' | |
]; | |
import { RouterProvider, createMemoryRouter, createBrowserRouter } from 'react-router-dom/dist/index.js'; | |
// Then use these imported functions directly |
This change will make the code more explicit and easier to understand, while also potentially improving performance by allowing better static analysis and tree-shaking.
routerContextProps, | ||
WraperRouterProps: props, | ||
}); | ||
if (!routerContextProps) return <ReactRouterDom.BrowserRouter {...props} />; |
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 condition if (!routerContextProps)
is always false because of the fallback to an empty object on line 14. This renders the check ineffective. Consider removing this condition or adjusting the logic if there's a specific scenario you're trying to handle.
if (!routerContextProps) return <RouterProvider {...props} />; | ||
|
||
if (routerContextProps.memoryRoute) { | ||
const MemeoryRouterInstance = createMemoryRouter(routers, { |
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.
Correct the typo in 'MemeoryRouterInstance' to 'MemoryRouterInstance' for consistency and clarity.
const MemeoryRouterInstance = createMemoryRouter(routers, { | |
const MemoryRouterInstance = createMemoryRouter(routers, { |
const createBrowserRouter = (ReactRouterDom as any)[ | ||
'create' + 'BrowserRouter' | ||
]; |
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.
Replace the string concatenation method used to access router creation functions with direct property access. This improves code readability and reduces the risk of runtime errors.
const createBrowserRouter = (ReactRouterDom as any)[ | |
'create' + 'BrowserRouter' | |
]; | |
const createBrowserRouter = (ReactRouterDom as any).createBrowserRouter; |
Apply this change to the 'createMemoryRouter' assignment as well.
@@ -52,6 +53,7 @@ function WraperRouterProvider( | |||
const createBrowserRouter = (ReactRouterDom as any)[ | |||
'create' + 'BrowserRouter' | |||
]; | |||
|
|||
if (!routerContextProps) return <RouterProvider {...props} />; |
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.
Add a type check for 'props' before spreading it into RouterProvider. This ensures type safety and prevents potential runtime errors if 'props' is undefined or null.
if (!routerContextProps) return <RouterProvider {...props} />; | |
if (!routerContextProps) return <RouterProvider {...(props || {})} />; |
.github/workflows/e2e-router.yml
Outdated
|
||
- name: Run condition check script | ||
id: check-ci | ||
run: node tools/scripts/ci-is-affected.mjs --appName=3005-runtime-host |
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.
You need to include the subapp in the app name as well
@@ -1,6 +1,6 @@ | |||
# React Bridge | |||
|
|||
`@module-federation/bridge-react` 提供了用于 React 应用的 `bridge` 工具函数,其提供的 `createBridgeComponent` 可用于导出应用级别模块,`createRemoteComponent` 用于加载应用级别模块。[Demo](https://github.com/module-federation/core/core/tree/main/apps/router-demo) | |||
`@module-federation/bridge-react` 提供了用于 React 应用的 `bridge` 工具函数,其提供的 `createBridgeComponent` 可用于导出应用级别模块,`createRemoteComponent` 用于加载应用级别模块。[Demo](https://github.com/module-federation/core/tree/main/apps/router-demo) |
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.
Increases which parameters can be passed to the created component
@@ -0,0 +1,113 @@ | |||
# remote2 | |||
|
|||
## 1.0.15 |
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.
This file can be deleted
} | ||
}, | ||
destroy(info: { dom: HTMLElement }) { | ||
async destroy(info: { dom: HTMLElement }) { |
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.
Need to eliminate asynchrony
@@ -22,6 +22,8 @@ export default defineConfig({ | |||
entry: { | |||
index: path.resolve(__dirname, 'src/index.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'), |
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 rollup plugin can be used to optimize the two router code issues
Description
In this pr i have supported:
provider.render
can be provided by user, so that user can customize their render login. the usage is like this:ref
and other props, thus user can get the render root dom . The usage is like this:Related Issue
Types of changes
Checklist