Skip to content
Merged
5 changes: 5 additions & 0 deletions .changeset/neat-paws-doubt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@module-federation/modern-js': patch
---

feat: delete set disableAlias true in @module-federation/modern-js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export function createBaseBridgeComponent<T>({
const beforeBridgeRenderRes =
instance?.bridgeHook?.lifecycle?.beforeBridgeRender?.emit(info) || {};

const rootComponentWithErrorBoundary = (
const BridgeWrapper = ({ basename }: { basename?: string }) => (
<ErrorBoundary
FallbackComponent={fallback as React.ComponentType<FallbackProps>}
>
Expand All @@ -77,13 +77,18 @@ export function createBaseBridgeComponent<T>({
propsInfo={
{
...propsInfo,
basename,
...(beforeBridgeRenderRes as any)?.extraProps,
} as T
}
/>
</ErrorBoundary>
);

const rootComponentWithErrorBoundary = (
<BridgeWrapper basename={basename} />
);

if (bridgeInfo.render) {
await Promise.resolve(
bridgeInfo.render(rootComponentWithErrorBoundary, dom),
Expand Down Expand Up @@ -111,7 +116,7 @@ export function createBaseBridgeComponent<T>({
if ('unmount' in root) {
root.unmount();
} else {
console.warn('Root does not have unmount method');
LoggerInstance.warn('Root does not have unmount method');
}
rootMap.delete(dom);
}
Expand Down
3 changes: 2 additions & 1 deletion packages/bridge/bridge-react/src/provider/versions/legacy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import type { ProviderFnParams } from '../../types';
import { createBaseBridgeComponent } from './bridge-base';
import ReactDOM from 'react-dom';
import { LoggerInstance } from '../../utils';

export interface CreateRootOptions {
identifierPrefix?: string;
Expand Down Expand Up @@ -58,7 +59,7 @@ export function createReact16Or17Root(
* Provide warning for React 18
*/
if (isReact18) {
console.warn(
LoggerInstance.warn(
`[Bridge-React] React 18 detected in legacy mode. ` +
`For better compatibility, please use the version-specific import: ` +
`import { createBridgeComponent } from '@module-federation/bridge-react/v18'`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -840,8 +840,13 @@
"description": "Bridge configuration options",
"type": "object",
"properties": {
"enableBridgeRouter": {
"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.",
"type": "boolean",
"default": false
},
"disableAlias": {
"description": "Disables the default alias setting in the bridge. When true, users must manually handle basename through root component props.",
"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.",
"type": "boolean",
"default": false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -929,9 +929,15 @@ export default {
description: 'Bridge configuration options',
type: 'object',
properties: {
enableBridgeRouter: {
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.',
type: 'boolean',
default: false,
},
disableAlias: {
description:
'Disables the default alias setting in the bridge. When true, users must manually handle basename through root component props.',
'[Deprecated] Use `enableBridgeRouter: false` instead. Disables the default alias setting in the bridge. When true, users must manually handle basename through root component props.',
type: 'boolean',
default: false,
},
Expand Down
90 changes: 78 additions & 12 deletions packages/enhanced/src/wrapper/ModuleFederationPlugin.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import type { WebpackPluginInstance, Compiler } from 'webpack';
import type { moduleFederationPlugin } from '@module-federation/sdk';
import {
bindLoggerToCompiler,
infrastructureLogger,
type moduleFederationPlugin,
} from '@module-federation/sdk';

import type IModuleFederationPlugin from '../lib/container/ModuleFederationPlugin';
import type { ResourceInfo } from '@module-federation/manifest';

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

apply(compiler: Compiler) {
bindLoggerToCompiler(
infrastructureLogger,
compiler,
'EnhancedModuleFederationPlugin',
);

process.env['FEDERATION_WEBPACK_PATH'] =
process.env['FEDERATION_WEBPACK_PATH'] || getWebpackPath(compiler);
const CoreModuleFederationPlugin =
Expand All @@ -29,17 +40,72 @@ export default class ModuleFederationPlugin implements WebpackPluginInstance {
this._mfPlugin = new CoreModuleFederationPlugin(this._options);
this._mfPlugin!.apply(compiler);

// react bridge plugin
const nodeModulesPath = path.resolve(compiler.context, 'node_modules');
const reactPath = path.join(
nodeModulesPath,
'@module-federation/bridge-react',
);
// Check whether react exists
if (
fs.existsSync(reactPath) &&
(!this._options?.bridge || !this._options.bridge.disableAlias)
) {
const checkBridgeReactInstalled = () => {
try {
const userPackageJsonPath = path.resolve(
compiler.context,
'package.json',
);
if (fs.existsSync(userPackageJsonPath)) {
const userPackageJson = JSON.parse(
fs.readFileSync(userPackageJsonPath, 'utf-8'),
);
const userDependencies = {
...userPackageJson.dependencies,
...userPackageJson.devDependencies,
};
return !!userDependencies['@module-federation/bridge-react'];
}
return false;
} catch (error) {
return false;
}
};
const hasBridgeReact = checkBridgeReactInstalled();

const shouldEnableBridgePlugin = () => {
// Priority 1: Explicit enableBridgeRouter configuration
if (this._options?.bridge?.enableBridgeRouter === true) {
return true;
}

// Priority 2: Explicit disable via enableBridgeRouter:false or disableAlias:true
if (
this._options?.bridge?.enableBridgeRouter === false ||
this._options?.bridge?.disableAlias === true
) {
if (this._options?.bridge?.disableAlias === true) {
infrastructureLogger.warn(
'⚠️ [ModuleFederationPlugin] The `disableAlias` option is deprecated and will be removed in a future version.\n' +
' Please use `enableBridgeRouter: false` instead:\n' +
' {\n' +
' bridge: {\n' +
' enableBridgeRouter: false // Use this instead of disableAlias: true\n' +
' }\n' +
' }',
);
}
return false;
}

// Priority 3: Automatic detection based on bridge-react installation
if (hasBridgeReact) {
infrastructureLogger.info(
'💡 [ModuleFederationPlugin] Detected @module-federation/bridge-react in your dependencies.\n' +
' For better control and to avoid future breaking changes, please explicitly set:\n' +
' {\n' +
' bridge: {\n' +
' enableBridgeRouter: true // Explicitly enable bridge router\n' +
' }\n' +
' }',
);
return true;
}

return false;
};

if (shouldEnableBridgePlugin()) {
new ReactBridgePlugin({
moduleFederationOptions: this._options,
}).apply(compiler);
Expand Down
20 changes: 0 additions & 20 deletions packages/modernjs/src/cli/configPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,26 +180,6 @@ export const patchMFConfig = (
...(mfConfig.runtimePlugins || []),
] as RuntimePluginEntry[];

try {
const nodeModulesPath = path.resolve(process.cwd(), 'node_modules');
const bridgeReactPath = path.join(
nodeModulesPath,
'@module-federation/bridge-react',
);
if (
fs.existsSync(bridgeReactPath) &&
(!mfConfig?.bridge || !mfConfig.bridge.disableAlias)
) {
mfConfig.bridge = {
disableAlias: true,
};
logger.debug(
`${PLUGIN_IDENTIFIER} use "@module-federation/modern-js/react" instead of "@module-federation/bridge-react" !`,
);
}
} catch (e) {
// noop
}
patchDTSConfig(mfConfig, isServer);

injectRuntimePlugins(
Expand Down
77 changes: 67 additions & 10 deletions packages/rspack/src/ModuleFederationPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
composeKeyWithSeparator,
moduleFederationPlugin,
} from '@module-federation/sdk';

import { StatsPlugin } from '@module-federation/manifest';
import { ContainerManager, utils } from '@module-federation/managers';
import { DtsPlugin } from '@module-federation/dts-plugin';
Expand Down Expand Up @@ -182,18 +183,74 @@ export class ModuleFederationPlugin implements RspackPluginInstance {
this._statsPlugin.apply(compiler);
}

const checkBridgeReactInstalled = () => {
try {
const userPackageJsonPath = path.resolve(
compiler.context,
'package.json',
);
if (fs.existsSync(userPackageJsonPath)) {
const userPackageJson = JSON.parse(
fs.readFileSync(userPackageJsonPath, 'utf-8'),
);
const userDependencies = {
...userPackageJson.dependencies,
...userPackageJson.devDependencies,
};
return !!userDependencies['@module-federation/bridge-react'];
}
return false;
} catch (error) {
return false;
}
};

const hasBridgeReact = checkBridgeReactInstalled();

// react bridge plugin
const nodeModulesPath = path.resolve(compiler.context, 'node_modules');
const reactPath = path.join(
nodeModulesPath,
'@module-federation/bridge-react',
);
const shouldEnableBridgePlugin = (): boolean => {
// Priority 1: Explicit enableBridgeRouter configuration
if (options?.bridge?.enableBridgeRouter === true) {
return true;
}

// Check whether react exists
if (
fs.existsSync(reactPath) &&
(!options?.bridge || !options.bridge.disableAlias)
) {
// Priority 2: Explicit disable via enableBridgeRouter:false or disableAlias:true
if (
options?.bridge?.enableBridgeRouter === false ||
options?.bridge?.disableAlias === true
) {
if (options?.bridge?.disableAlias === true) {
logger.warn(
'⚠️ [ModuleFederationPlugin] The `disableAlias` option is deprecated and will be removed in a future version.\n' +
' Please use `enableBridgeRouter: false` instead:\n' +
' {\n' +
' bridge: {\n' +
' enableBridgeRouter: false // Use this instead of disableAlias: true\n' +
' }\n' +
' }',
);
}
return false;
}

// Priority 3: Automatic detection based on bridge-react installation
if (hasBridgeReact) {
logger.info(
'💡 [ModuleFederationPlugin] Detected @module-federation/bridge-react in your dependencies.\n' +
' For better control and to avoid future breaking changes, please explicitly set:\n' +
' {\n' +
' bridge: {\n' +
' enableBridgeRouter: true // Explicitly enable bridge router\n' +
' }\n' +
' }',
);
return true;
}

return false;
};

if (shouldEnableBridgePlugin()) {
new ReactBridgePlugin({
moduleFederationOptions: this._options,
}).apply(compiler);
Expand Down
15 changes: 15 additions & 0 deletions packages/sdk/src/types/plugins/ModuleFederationPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,8 +273,23 @@ export interface ModuleFederationPluginOptions {
};
bridge?: {
/**
* Enables bridge router functionality for React applications.
* When enabled, automatically handles routing context and basename injection
* for micro-frontend applications using react-router-dom.
*
* @default false
*/
enableBridgeRouter?: boolean;
/**
* @deprecated Use `enableBridgeRouter: false` instead.
*
* Disables the default alias setting in the bridge.
* When true, users must manually handle basename through root component props.
*
* Migration:
* - `disableAlias: true` → `enableBridgeRouter: false`
* - `disableAlias: false` → `enableBridgeRouter: true`
*
* @default false
*/
disableAlias?: boolean;
Expand Down