Skip to content

Commit aa7daae

Browse files
authored
feat(react-bridge): Introduce enableBridgeRouter Configuration for Enhanced Bridge Router Control (#4129)
1 parent a9c10e4 commit aa7daae

File tree

9 files changed

+187
-47
lines changed

9 files changed

+187
-47
lines changed

.changeset/neat-paws-doubt.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@module-federation/modern-js': patch
3+
---
4+
5+
feat: delete set disableAlias true in @module-federation/modern-js

packages/bridge/bridge-react/src/provider/versions/bridge-base.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export function createBaseBridgeComponent<T>({
6464
const beforeBridgeRenderRes =
6565
instance?.bridgeHook?.lifecycle?.beforeBridgeRender?.emit(info) || {};
6666

67-
const rootComponentWithErrorBoundary = (
67+
const BridgeWrapper = ({ basename }: { basename?: string }) => (
6868
<ErrorBoundary
6969
FallbackComponent={fallback as React.ComponentType<FallbackProps>}
7070
>
@@ -77,13 +77,18 @@ export function createBaseBridgeComponent<T>({
7777
propsInfo={
7878
{
7979
...propsInfo,
80+
basename,
8081
...(beforeBridgeRenderRes as any)?.extraProps,
8182
} as T
8283
}
8384
/>
8485
</ErrorBoundary>
8586
);
8687

88+
const rootComponentWithErrorBoundary = (
89+
<BridgeWrapper basename={basename} />
90+
);
91+
8792
if (bridgeInfo.render) {
8893
await Promise.resolve(
8994
bridgeInfo.render(rootComponentWithErrorBoundary, dom),
@@ -111,7 +116,7 @@ export function createBaseBridgeComponent<T>({
111116
if ('unmount' in root) {
112117
root.unmount();
113118
} else {
114-
console.warn('Root does not have unmount method');
119+
LoggerInstance.warn('Root does not have unmount method');
115120
}
116121
rootMap.delete(dom);
117122
}

packages/bridge/bridge-react/src/provider/versions/legacy.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import type { ProviderFnParams } from '../../types';
66
import { createBaseBridgeComponent } from './bridge-base';
77
import ReactDOM from 'react-dom';
8+
import { LoggerInstance } from '../../utils';
89

910
export interface CreateRootOptions {
1011
identifierPrefix?: string;
@@ -58,7 +59,7 @@ export function createReact16Or17Root(
5859
* Provide warning for React 18
5960
*/
6061
if (isReact18) {
61-
console.warn(
62+
LoggerInstance.warn(
6263
`[Bridge-React] React 18 detected in legacy mode. ` +
6364
`For better compatibility, please use the version-specific import: ` +
6465
`import { createBridgeComponent } from '@module-federation/bridge-react/v18'`,

packages/enhanced/src/schemas/container/ModuleFederationPlugin.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -840,8 +840,13 @@
840840
"description": "Bridge configuration options",
841841
"type": "object",
842842
"properties": {
843+
"enableBridgeRouter": {
844+
"description": "Enables bridge router functionality for React applications. When enabled, automatically handles routing context and basename injection for micro-frontend applications using react-router-dom.",
845+
"type": "boolean",
846+
"default": false
847+
},
843848
"disableAlias": {
844-
"description": "Disables the default alias setting in the bridge. When true, users must manually handle basename through root component props.",
849+
"description": "[Deprecated] Use `enableBridgeRouter: false` instead. Disables the default alias setting in the bridge. When true, users must manually handle basename through root component props.",
845850
"type": "boolean",
846851
"default": false
847852
}

packages/enhanced/src/schemas/container/ModuleFederationPlugin.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -929,9 +929,15 @@ export default {
929929
description: 'Bridge configuration options',
930930
type: 'object',
931931
properties: {
932+
enableBridgeRouter: {
933+
description:
934+
'Enables bridge router functionality for React applications. When enabled, automatically handles routing context and basename injection for micro-frontend applications using react-router-dom.',
935+
type: 'boolean',
936+
default: false,
937+
},
932938
disableAlias: {
933939
description:
934-
'Disables the default alias setting in the bridge. When true, users must manually handle basename through root component props.',
940+
'[Deprecated] Use `enableBridgeRouter: false` instead. Disables the default alias setting in the bridge. When true, users must manually handle basename through root component props.',
935941
type: 'boolean',
936942
default: false,
937943
},

packages/enhanced/src/wrapper/ModuleFederationPlugin.ts

Lines changed: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import type { WebpackPluginInstance, Compiler } from 'webpack';
2-
import type { moduleFederationPlugin } from '@module-federation/sdk';
2+
import {
3+
bindLoggerToCompiler,
4+
infrastructureLogger,
5+
type moduleFederationPlugin,
6+
} from '@module-federation/sdk';
7+
38
import type IModuleFederationPlugin from '../lib/container/ModuleFederationPlugin';
49
import type { ResourceInfo } from '@module-federation/manifest';
510

@@ -21,6 +26,12 @@ export default class ModuleFederationPlugin implements WebpackPluginInstance {
2126
}
2227

2328
apply(compiler: Compiler) {
29+
bindLoggerToCompiler(
30+
infrastructureLogger,
31+
compiler,
32+
'EnhancedModuleFederationPlugin',
33+
);
34+
2435
process.env['FEDERATION_WEBPACK_PATH'] =
2536
process.env['FEDERATION_WEBPACK_PATH'] || getWebpackPath(compiler);
2637
const CoreModuleFederationPlugin =
@@ -29,17 +40,72 @@ export default class ModuleFederationPlugin implements WebpackPluginInstance {
2940
this._mfPlugin = new CoreModuleFederationPlugin(this._options);
3041
this._mfPlugin!.apply(compiler);
3142

32-
// react bridge plugin
33-
const nodeModulesPath = path.resolve(compiler.context, 'node_modules');
34-
const reactPath = path.join(
35-
nodeModulesPath,
36-
'@module-federation/bridge-react',
37-
);
38-
// Check whether react exists
39-
if (
40-
fs.existsSync(reactPath) &&
41-
(!this._options?.bridge || !this._options.bridge.disableAlias)
42-
) {
43+
const checkBridgeReactInstalled = () => {
44+
try {
45+
const userPackageJsonPath = path.resolve(
46+
compiler.context,
47+
'package.json',
48+
);
49+
if (fs.existsSync(userPackageJsonPath)) {
50+
const userPackageJson = JSON.parse(
51+
fs.readFileSync(userPackageJsonPath, 'utf-8'),
52+
);
53+
const userDependencies = {
54+
...userPackageJson.dependencies,
55+
...userPackageJson.devDependencies,
56+
};
57+
return !!userDependencies['@module-federation/bridge-react'];
58+
}
59+
return false;
60+
} catch (error) {
61+
return false;
62+
}
63+
};
64+
const hasBridgeReact = checkBridgeReactInstalled();
65+
66+
const shouldEnableBridgePlugin = () => {
67+
// Priority 1: Explicit enableBridgeRouter configuration
68+
if (this._options?.bridge?.enableBridgeRouter === true) {
69+
return true;
70+
}
71+
72+
// Priority 2: Explicit disable via enableBridgeRouter:false or disableAlias:true
73+
if (
74+
this._options?.bridge?.enableBridgeRouter === false ||
75+
this._options?.bridge?.disableAlias === true
76+
) {
77+
if (this._options?.bridge?.disableAlias === true) {
78+
infrastructureLogger.warn(
79+
'⚠️ [ModuleFederationPlugin] The `disableAlias` option is deprecated and will be removed in a future version.\n' +
80+
' Please use `enableBridgeRouter: false` instead:\n' +
81+
' {\n' +
82+
' bridge: {\n' +
83+
' enableBridgeRouter: false // Use this instead of disableAlias: true\n' +
84+
' }\n' +
85+
' }',
86+
);
87+
}
88+
return false;
89+
}
90+
91+
// Priority 3: Automatic detection based on bridge-react installation
92+
if (hasBridgeReact) {
93+
infrastructureLogger.info(
94+
'💡 [ModuleFederationPlugin] Detected @module-federation/bridge-react in your dependencies.\n' +
95+
' For better control and to avoid future breaking changes, please explicitly set:\n' +
96+
' {\n' +
97+
' bridge: {\n' +
98+
' enableBridgeRouter: true // Explicitly enable bridge router\n' +
99+
' }\n' +
100+
' }',
101+
);
102+
return true;
103+
}
104+
105+
return false;
106+
};
107+
108+
if (shouldEnableBridgePlugin()) {
43109
new ReactBridgePlugin({
44110
moduleFederationOptions: this._options,
45111
}).apply(compiler);

packages/modernjs/src/cli/configPlugin.ts

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -180,26 +180,6 @@ export const patchMFConfig = (
180180
...(mfConfig.runtimePlugins || []),
181181
] as RuntimePluginEntry[];
182182

183-
try {
184-
const nodeModulesPath = path.resolve(process.cwd(), 'node_modules');
185-
const bridgeReactPath = path.join(
186-
nodeModulesPath,
187-
'@module-federation/bridge-react',
188-
);
189-
if (
190-
fs.existsSync(bridgeReactPath) &&
191-
(!mfConfig?.bridge || !mfConfig.bridge.disableAlias)
192-
) {
193-
mfConfig.bridge = {
194-
disableAlias: true,
195-
};
196-
logger.debug(
197-
`${PLUGIN_IDENTIFIER} use "@module-federation/modern-js/react" instead of "@module-federation/bridge-react" !`,
198-
);
199-
}
200-
} catch (e) {
201-
// noop
202-
}
203183
patchDTSConfig(mfConfig, isServer);
204184

205185
injectRuntimePlugins(

packages/rspack/src/ModuleFederationPlugin.ts

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
composeKeyWithSeparator,
1111
moduleFederationPlugin,
1212
} from '@module-federation/sdk';
13+
1314
import { StatsPlugin } from '@module-federation/manifest';
1415
import { ContainerManager, utils } from '@module-federation/managers';
1516
import { DtsPlugin } from '@module-federation/dts-plugin';
@@ -182,18 +183,74 @@ export class ModuleFederationPlugin implements RspackPluginInstance {
182183
this._statsPlugin.apply(compiler);
183184
}
184185

186+
const checkBridgeReactInstalled = () => {
187+
try {
188+
const userPackageJsonPath = path.resolve(
189+
compiler.context,
190+
'package.json',
191+
);
192+
if (fs.existsSync(userPackageJsonPath)) {
193+
const userPackageJson = JSON.parse(
194+
fs.readFileSync(userPackageJsonPath, 'utf-8'),
195+
);
196+
const userDependencies = {
197+
...userPackageJson.dependencies,
198+
...userPackageJson.devDependencies,
199+
};
200+
return !!userDependencies['@module-federation/bridge-react'];
201+
}
202+
return false;
203+
} catch (error) {
204+
return false;
205+
}
206+
};
207+
208+
const hasBridgeReact = checkBridgeReactInstalled();
209+
185210
// react bridge plugin
186-
const nodeModulesPath = path.resolve(compiler.context, 'node_modules');
187-
const reactPath = path.join(
188-
nodeModulesPath,
189-
'@module-federation/bridge-react',
190-
);
211+
const shouldEnableBridgePlugin = (): boolean => {
212+
// Priority 1: Explicit enableBridgeRouter configuration
213+
if (options?.bridge?.enableBridgeRouter === true) {
214+
return true;
215+
}
191216

192-
// Check whether react exists
193-
if (
194-
fs.existsSync(reactPath) &&
195-
(!options?.bridge || !options.bridge.disableAlias)
196-
) {
217+
// Priority 2: Explicit disable via enableBridgeRouter:false or disableAlias:true
218+
if (
219+
options?.bridge?.enableBridgeRouter === false ||
220+
options?.bridge?.disableAlias === true
221+
) {
222+
if (options?.bridge?.disableAlias === true) {
223+
logger.warn(
224+
'⚠️ [ModuleFederationPlugin] The `disableAlias` option is deprecated and will be removed in a future version.\n' +
225+
' Please use `enableBridgeRouter: false` instead:\n' +
226+
' {\n' +
227+
' bridge: {\n' +
228+
' enableBridgeRouter: false // Use this instead of disableAlias: true\n' +
229+
' }\n' +
230+
' }',
231+
);
232+
}
233+
return false;
234+
}
235+
236+
// Priority 3: Automatic detection based on bridge-react installation
237+
if (hasBridgeReact) {
238+
logger.info(
239+
'💡 [ModuleFederationPlugin] Detected @module-federation/bridge-react in your dependencies.\n' +
240+
' For better control and to avoid future breaking changes, please explicitly set:\n' +
241+
' {\n' +
242+
' bridge: {\n' +
243+
' enableBridgeRouter: true // Explicitly enable bridge router\n' +
244+
' }\n' +
245+
' }',
246+
);
247+
return true;
248+
}
249+
250+
return false;
251+
};
252+
253+
if (shouldEnableBridgePlugin()) {
197254
new ReactBridgePlugin({
198255
moduleFederationOptions: this._options,
199256
}).apply(compiler);

packages/sdk/src/types/plugins/ModuleFederationPlugin.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,8 +273,23 @@ export interface ModuleFederationPluginOptions {
273273
};
274274
bridge?: {
275275
/**
276+
* Enables bridge router functionality for React applications.
277+
* When enabled, automatically handles routing context and basename injection
278+
* for micro-frontend applications using react-router-dom.
279+
*
280+
* @default false
281+
*/
282+
enableBridgeRouter?: boolean;
283+
/**
284+
* @deprecated Use `enableBridgeRouter: false` instead.
285+
*
276286
* Disables the default alias setting in the bridge.
277287
* When true, users must manually handle basename through root component props.
288+
*
289+
* Migration:
290+
* - `disableAlias: true` → `enableBridgeRouter: false`
291+
* - `disableAlias: false` → `enableBridgeRouter: true`
292+
*
278293
* @default false
279294
*/
280295
disableAlias?: boolean;

0 commit comments

Comments
 (0)