Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
eb2b405
fix(rsbuild-plugin): support app SSR node target with custom environment
ScriptedAlchemy Feb 12, 2026
c5c811b
Merge branch 'main' into fix/rsbuild-plugin-app-ssr-node-env
ScriptedAlchemy Feb 12, 2026
2e0824b
chore(core): trigger canary publish rerun
ScriptedAlchemy Feb 12, 2026
d10edc0
chore(core): trigger ci rerun
ScriptedAlchemy Feb 12, 2026
0cab4a9
refactor(rsbuild-plugin): auto-detect default environment by caller
ScriptedAlchemy Feb 12, 2026
b2c772c
fix(rsbuild-plugin): scope node target MF to selected env
ScriptedAlchemy Feb 12, 2026
0c3313f
Merge branch 'main' into fix/rsbuild-plugin-app-ssr-node-env
ScriptedAlchemy Feb 13, 2026
2b1724e
Merge remote-tracking branch 'origin/main' into fix/rsbuild-plugin-ap…
ScriptedAlchemy Feb 14, 2026
38715bf
fix(rsbuild-plugin): use SSR MF config for node target envs
ScriptedAlchemy Feb 14, 2026
6e58b44
fix(rsbuild-plugin): scope plugin instance to configured env for ssr
ScriptedAlchemy Feb 14, 2026
36ce9d4
fix(core): support node SSR manifest remotes and auto publicPath
ScriptedAlchemy Feb 14, 2026
ac8eefe
fix(rsbuild-plugin): stabilize SSR node manifest/publicPath flow
ScriptedAlchemy Feb 14, 2026
894a4ea
fix(sdk): preserve empty ssrPublicPath semantics
ScriptedAlchemy Feb 14, 2026
7205567
test(enhanced): harden reshake fixture bootstrap
ScriptedAlchemy Feb 14, 2026
a0f19f0
test(enhanced): stabilize treeshake fixtures under load
ScriptedAlchemy Feb 14, 2026
5b93df0
build(rsbuild-plugin): use bundler module resolution
ScriptedAlchemy Feb 14, 2026
7acb6b2
test(metro-core): stabilize iOS maestro visibility waits
ScriptedAlchemy Feb 14, 2026
4b16b2d
Merge branch 'main' into fix/rsbuild-plugin-app-ssr-node-env
ScriptedAlchemy Feb 14, 2026
172e069
Merge branch 'main' into fix/rsbuild-plugin-app-ssr-node-env
ScriptedAlchemy Feb 15, 2026
165bd50
Merge remote-tracking branch 'origin/main' into fix/rsbuild-plugin-ap…
ScriptedAlchemy Feb 16, 2026
1eeb46e
Merge branch 'main' into fix/rsbuild-plugin-app-ssr-node-env
ScriptedAlchemy Feb 16, 2026
a30201e
Merge branch 'main' into fix/rsbuild-plugin-app-ssr-node-env
ScriptedAlchemy Feb 19, 2026
7f039bb
Merge branch 'main' into fix/rsbuild-plugin-app-ssr-node-env
ScriptedAlchemy Feb 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/quick-forks-wonder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@module-federation/rsbuild-plugin': patch
---

Fix app-mode `target: 'node'` handling to respect custom `environment` names, improve missing-environment errors, auto-detect default environment names by caller/tooling when `environment` is omitted, and ensure selected node-target environments still receive federation plugin injection for commonjs-like SSR outputs.
45 changes: 44 additions & 1 deletion apps/website-new/docs/en/guide/build-plugins/plugins-rsbuild.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,10 @@ If you need to use the Module Federation runtime capabilities, please install [@
export declare const pluginModuleFederation: (moduleFederationOptions: ModuleFederationOptions, rsbuildOptions?: RSBUILD_PLUGIN_OPTIONS) => RsbuildPlugin;

type RSBUILD_PLUGIN_OPTIONS = {
target?: 'web' | 'node' | 'dual';
ssr?: boolean;
ssrDir?: string;
environment?: string;
}
```

Expand All @@ -109,7 +112,7 @@ Additional configuration for the Rsbuild plugin.

:::tip

Only supported when used as a global plugin in Rslib.
`target: 'dual'` is only supported when used as a global plugin in Rslib/Rspress.

:::

Expand All @@ -120,6 +123,46 @@ Used to specify the target runtime environment for the output. When set to `dual

After generating SSR output with `target: 'dual'`, you can refer to [Create a Modern.js Consumer](../../../practice/frameworks/modern/index), create a consumer, and integrate the corresponding Rslib SSR producer for development.

For Rsbuild app SSR, use `target: 'node'` with `environment` to apply Module Federation to a specific app environment.

```ts title='rsbuild.config.ts'
import { pluginModuleFederation } from '@module-federation/rsbuild-plugin';
import { defineConfig } from '@rsbuild/core';

export default defineConfig({
environments: {
client: {},
ssr: {},
},
plugins: [
pluginModuleFederation(
{
name: 'host',
remotes: {
remote: 'remote@http://localhost:3001/mf-manifest.json',
},
},
{
target: 'node',
environment: 'ssr',
},
),
],
});
```

#### environment

* Type: `string`
* Default: auto-detected by caller/tooling:
* Rslib: `'mf'`
* Rsbuild app + `target: 'web'`: `'web'`
* Rsbuild app + `target: 'node'`: `'node'`
* Rspress + `target: 'web'`: `'web'`
* Rspress + `target: 'node'`: `'node'`

Environment name used by `target: 'node'` to select which environment config receives Node-target federation behavior.


#### ssr

Expand Down
45 changes: 44 additions & 1 deletion apps/website-new/docs/zh/guide/build-plugins/plugins-rsbuild.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,10 @@ export default defineConfig({
export declare const pluginModuleFederation: (moduleFederationOptions: ModuleFederationOptions, rsbuildOptions?: RSBUILD_PLUGIN_OPTIONS) => RsbuildPlugin;

type RSBUILD_PLUGIN_OPTIONS = {
target?: 'web' | 'node' | 'dual';
ssr?: boolean;
ssrDir?: string;
environment?: string;
}
```

Expand All @@ -108,7 +111,7 @@ Rsbuild 插件额外配置。

:::tip

仅支持 Rslib 全局插件。
`target: 'dual'` 仅支持 Rslib/Rspress 全局插件。

:::

Expand All @@ -119,6 +122,46 @@ Rsbuild 插件额外配置。

使用 `target: 'dual'` 生成 SSR 产物后,可参考 [创建 Modern.js 消费者](../../../practice/frameworks/modern/index) 创建消费者,并接入对应的 Rslib SSR 生产者进行开发。

对于 Rsbuild App 的 SSR,可使用 `target: 'node'` + `environment`,将 Module Federation 应用到指定环境。

```ts title='rsbuild.config.ts'
import { pluginModuleFederation } from '@module-federation/rsbuild-plugin';
import { defineConfig } from '@rsbuild/core';

export default defineConfig({
environments: {
client: {},
ssr: {},
},
plugins: [
pluginModuleFederation(
{
name: 'host',
remotes: {
remote: 'remote@http://localhost:3001/mf-manifest.json',
},
},
{
target: 'node',
environment: 'ssr',
},
),
],
});
```

#### environment

* 类型:`string`
* 默认值:按调用方/工具自动推断:
* Rslib:`'mf'`
* Rsbuild App + `target: 'web'`:`'web'`
* Rsbuild App + `target: 'node'`:`'node'`
* Rspress + `target: 'web'`:`'web'`
* Rspress + `target: 'node'`:`'node'`

在 `target: 'node'` 下用于指定要应用 Node Federation 行为的环境名称。


#### ssr

Expand Down
47 changes: 47 additions & 0 deletions packages/rsbuild-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,53 @@ export default defineConfig({
});
```

### Rsbuild App SSR (Node target with custom environment)

Use `target: 'node'` with an explicit `environment` to apply federation to a
specific Rsbuild app environment (for example `ssr`).

```ts
import { pluginModuleFederation } from '@module-federation/rsbuild-plugin';
import { defineConfig } from '@rsbuild/core';

export default defineConfig({
environments: {
client: {},
ssr: {},
},
plugins: [
pluginModuleFederation(
{
name: 'host',
remotes: {
remote: 'remote@http://localhost:3001/mf-manifest.json',
},
},
{
target: 'node',
environment: 'ssr',
},
),
],
});
```

`target: 'dual'` support remains scoped to Rslib/Rspress workflows.

### Default environment detection

If `environment` is omitted, the plugin will choose a default per tool:

- **Rslib**: `mf`
- **Rsbuild app**:
- `target: 'web'` → `web`
- `target: 'node'` → `node`
- **Rspress**:
- `target: 'web'` → `web`
- `target: 'node'` → `node`

You can still override with `environment` when your project uses custom names.

### Rslib Module

```js
Expand Down
75 changes: 68 additions & 7 deletions packages/rsbuild-plugin/src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import {
RSPRESS_SSG_MD_ENV_NAME,
} from '../constant';
import {
ENV_NAME,
patchNodeConfig,
patchNodeMFConfig,
patchToolsTspack,
Expand All @@ -47,7 +46,7 @@ type RSBUILD_PLUGIN_OPTIONS = {
ssr?: boolean;
// ssr dir, default is ssr
ssrDir?: string;
// target copy environment name, default is mf
// target environment name. If omitted, defaults are inferred by caller/tool.
environment?: string;
};

Expand Down Expand Up @@ -75,6 +74,33 @@ export { RSBUILD_PLUGIN_MODULE_FEDERATION_NAME, PLUGIN_NAME, SSR_DIR };
const LIB_FORMAT = ['umd', 'modern-module'];

const DEFAULT_MF_ENVIRONMENT_NAME = 'mf';
const DEFAULT_WEB_ENVIRONMENT_NAME = 'web';
const DEFAULT_NODE_ENVIRONMENT_NAME = 'node';

const resolveDefaultEnvironmentName = ({
callerName,
target,
}: {
callerName: string;
target: RSBUILD_PLUGIN_OPTIONS['target'];
}) => {
if (callerName === CALL_NAME_MAP.RSLIB) {
return DEFAULT_MF_ENVIRONMENT_NAME;
}

if (callerName === CALL_NAME_MAP.RSPRESS) {
if (target === 'node') {
return DEFAULT_NODE_ENVIRONMENT_NAME;
}
return DEFAULT_WEB_ENVIRONMENT_NAME;
}

if (target === 'node') {
return DEFAULT_NODE_ENVIRONMENT_NAME;
}

return DEFAULT_WEB_ENVIRONMENT_NAME;
};

function isStoryBook(rsbuildConfig: RsbuildConfig) {
if (
Expand Down Expand Up @@ -122,7 +148,7 @@ export const pluginModuleFederation = (
target = 'web',
ssr = undefined,
ssrDir = SSR_DIR,
environment = DEFAULT_MF_ENVIRONMENT_NAME,
environment: configuredEnvironment,
} = rsbuildOptions || {};
if (ssr) {
throw new Error(
Expand All @@ -140,6 +166,12 @@ export const pluginModuleFederation = (
const isRslib = callerName === CALL_NAME_MAP.RSLIB;
const isRspress = callerName === CALL_NAME_MAP.RSPRESS;
const isSSR = target === 'dual';
const environment =
configuredEnvironment ??
resolveDefaultEnvironmentName({
callerName,
target,
});

if (isSSR && !isStoryBook(originalRsbuildConfig)) {
if (!isRslib && !isRspress) {
Expand Down Expand Up @@ -255,8 +287,18 @@ export const pluginModuleFederation = (
});
}
} else if (target === 'node') {
const mfEnv = config.environments![ENV_NAME]!;
patchToolsTspack(mfEnv, (config, { environment }) => {
const nodeTargetEnv = config.environments?.[environment];
if (!nodeTargetEnv) {
const availableEnvironments = Object.keys(config.environments || {});
const availableEnvironmentsLabel =
availableEnvironments.length > 0
? availableEnvironments.join(', ')
: '(none)';
throw new Error(
`Can not find environment '${environment}' when using target: 'node'. Available environments: ${availableEnvironmentsLabel}.`,
);
}
patchToolsTspack(nodeTargetEnv, (config, { environment }) => {
config.target = 'async-node';
});
}
Expand Down Expand Up @@ -360,7 +402,26 @@ export const pluginModuleFederation = (
throw new Error('Can not get bundlerConfigs!');
}
bundlerConfigs.forEach((bundlerConfig) => {
if (!isMFFormat(bundlerConfig) && !isRspress) {
const bundlerConfigName = bundlerConfig.name || '';
const isNodeTargetEnvironmentConfig =
target === 'node' && bundlerConfigName === environment;
const isRspressSSGEnvironmentConfig = isRspressSSGConfig(
bundlerConfig.name,
);

if (
target === 'node' &&
!isNodeTargetEnvironmentConfig &&
!isRspressSSGEnvironmentConfig
) {
return;
}

if (
!isMFFormat(bundlerConfig) &&
!isRspress &&
!isNodeTargetEnvironmentConfig
) {
Comment on lines +440 to +444

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restrict node-target flow to the selected environment

This condition still allows any MF-format bundler config to enter the node-target processing path even when its name does not match the configured environment, so target: 'node' can still affect extra environments. In builds with multiple MF-format environments, if the selected env is processed first, patchNodeMFConfig(moduleFederationOptions) mutates shared options and later non-selected environments receive node runtime/library settings, which can break client/web outputs; this is reproducible with environment: 'ssr' and an env order like ssr then client.

Useful? React with 👍 / 👎.

return;
} else if (isStoryBook(originalRsbuildConfig)) {
bundlerConfig.output!.uniqueName = `${moduleFederationOptions.name} -storybook - host`;
Expand Down Expand Up @@ -435,7 +496,7 @@ export const pluginModuleFederation = (
bundlerConfig.output!.chunkLoadingGlobal = `chunk_${moduleFederationOptions.name} `;
}

if (target === 'node' && isMFFormat(bundlerConfig)) {
if (isNodeTargetEnvironmentConfig) {
patchNodeConfig(bundlerConfig, moduleFederationOptions);
patchNodeMFConfig(moduleFederationOptions);
}
Expand Down
Loading
Loading