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.`,
);
- }
+ },
});
},
});