diff --git a/e2e/cases/module-federation/v1-without-entry/index.test.ts b/e2e/cases/module-federation/v1-without-entry/index.test.ts new file mode 100644 index 0000000000..465af21f04 --- /dev/null +++ b/e2e/cases/module-federation/v1-without-entry/index.test.ts @@ -0,0 +1,9 @@ +import { expect, rspackTest } from '@e2e/helper'; + +rspackTest('should start MF app in dev without entry', async ({ devOnly }) => { + await expect(devOnly()).resolves.toBeTruthy(); +}); + +rspackTest('should build MF app without entry', async ({ build }) => { + await expect(build()).resolves.toBeTruthy(); +}); diff --git a/e2e/cases/module-federation/v1-without-entry/rsbuild.config.ts b/e2e/cases/module-federation/v1-without-entry/rsbuild.config.ts new file mode 100644 index 0000000000..ced8bd3b09 --- /dev/null +++ b/e2e/cases/module-federation/v1-without-entry/rsbuild.config.ts @@ -0,0 +1,25 @@ +import { defineConfig } from '@rsbuild/core'; +import { pluginReact } from '@rsbuild/plugin-react'; + +export default defineConfig({ + plugins: [pluginReact()], + moduleFederation: { + options: { + name: 'remote', + exposes: { + './Button': './src/Button', + }, + filename: 'remoteEntry.js', + shared: { + react: { + singleton: true, + requiredVersion: '^19.0.0', + }, + 'react-dom': { + singleton: true, + requiredVersion: '^19.0.0', + }, + }, + }, + }, +}); diff --git a/e2e/cases/module-federation/v1-without-entry/src/Button.jsx b/e2e/cases/module-federation/v1-without-entry/src/Button.jsx new file mode 100644 index 0000000000..5979864f0a --- /dev/null +++ b/e2e/cases/module-federation/v1-without-entry/src/Button.jsx @@ -0,0 +1,5 @@ +export const Button = () => ( + +); diff --git a/e2e/cases/module-federation/v2-without-entry/index.test.ts b/e2e/cases/module-federation/v2-without-entry/index.test.ts new file mode 100644 index 0000000000..465af21f04 --- /dev/null +++ b/e2e/cases/module-federation/v2-without-entry/index.test.ts @@ -0,0 +1,9 @@ +import { expect, rspackTest } from '@e2e/helper'; + +rspackTest('should start MF app in dev without entry', async ({ devOnly }) => { + await expect(devOnly()).resolves.toBeTruthy(); +}); + +rspackTest('should build MF app without entry', async ({ build }) => { + await expect(build()).resolves.toBeTruthy(); +}); diff --git a/e2e/cases/module-federation/v2-without-entry/rsbuild.config.ts b/e2e/cases/module-federation/v2-without-entry/rsbuild.config.ts new file mode 100644 index 0000000000..7e38899768 --- /dev/null +++ b/e2e/cases/module-federation/v2-without-entry/rsbuild.config.ts @@ -0,0 +1,16 @@ +import { pluginModuleFederation } from '@module-federation/rsbuild-plugin'; +import { defineConfig } from '@rsbuild/core'; +import { pluginReact } from '@rsbuild/plugin-react'; + +export default defineConfig({ + plugins: [ + pluginReact(), + pluginModuleFederation({ + name: 'remote', + exposes: { + './Button': './src/Button', + }, + shared: ['react', 'react-dom'], + }), + ], +}); diff --git a/e2e/cases/module-federation/v2-without-entry/src/Button.jsx b/e2e/cases/module-federation/v2-without-entry/src/Button.jsx new file mode 100644 index 0000000000..5979864f0a --- /dev/null +++ b/e2e/cases/module-federation/v2-without-entry/src/Button.jsx @@ -0,0 +1,5 @@ +export const Button = () => ( + +); diff --git a/packages/core/src/plugins/entry.ts b/packages/core/src/plugins/entry.ts index 8bed299116..9c1656b941 100644 --- a/packages/core/src/plugins/entry.ts +++ b/packages/core/src/plugins/entry.ts @@ -1,5 +1,5 @@ -import { castArray, color, createVirtualModule } from '../helpers'; -import type { RsbuildEntryDescription, RsbuildPlugin } from '../types'; +import { castArray, color, createVirtualModule, isObject } from '../helpers'; +import type { RsbuildEntryDescription, RsbuildPlugin, Rspack } from '../types'; export const pluginEntry = (): RsbuildPlugin => ({ name: 'rsbuild:entry', @@ -32,8 +32,31 @@ export const pluginEntry = (): RsbuildPlugin => ({ } }); - api.onBeforeCreateCompiler(({ bundlerConfigs }) => { - if (bundlerConfigs.every((config) => !config.entry)) { + // Check missing entry config + api.onBeforeCreateCompiler({ + order: 'post', + handler: ({ bundlerConfigs }) => { + const hasEntry = bundlerConfigs.some((config) => config.entry); + if (hasEntry) { + return; + } + + const isModuleFederationPlugin = (plugin: Rspack.Plugin) => + isObject(plugin) && + plugin.constructor.name === 'ModuleFederationPlugin'; + + const hasModuleFederation = bundlerConfigs.some(({ plugins }) => + plugins?.some(isModuleFederationPlugin), + ); + + // Allow entry to be left empty when module federation is enabled + if (hasModuleFederation) { + bundlerConfigs.forEach((config) => { + config.entry = {}; + }); + return; + } + throw new Error( `${color.dim('[rsbuild:config]')} Could not find any entry module, please make sure that ${color.yellow( 'src/index.(ts|js|tsx|jsx|mts|cts|mjs|cjs)', @@ -41,7 +64,7 @@ export const pluginEntry = (): RsbuildPlugin => ({ 'source.entry', )} configuration.`, ); - } + }, }); }, });