> For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt.

# 模块联邦

[模块联邦（Module Federation）](https://module-federation.io/) 支持不同应用在独立构建后，于运行时暴露和消费模块。当你需要确认消费者能否加载真实的生产者、解析共享依赖并正确执行暴露模块时，可以用 Rstest 测试这条联邦边界。

[@module-federation/rstest](https://github.com/module-federation/core) 为 Rstest 构建配置模块联邦。它支持 Node 和 JSDOM 测试环境以及 Browser Mode。

## 安装 \{#install}

将 `@module-federation/rstest` 添加到现有 Rstest 项目。


```sh [npm]
npm add @module-federation/rstest -D
```

```sh [yarn]
yarn add @module-federation/rstest -D
```

```sh [pnpm]
pnpm add @module-federation/rstest -D
```

```sh [bun]
bun add @module-federation/rstest -D
```

```sh [deno]
deno add npm:@module-federation/rstest -D
```

该插件只负责配置消费远程模块的测试消费者。生产者仍需使用自己的模块联邦构建插件进行构建，例如 [Rslib 的 `mf` 格式](https://rslib.rs/guide/advanced/module-federation)。

## 基本用法 \{#basic-usage}

注册 `federation` 插件，并使用与应用代码导入一致的生产者名称。最简单的形式是消费一个构建到本地 CommonJS 入口的 Node 生产者，不需要启动任何服务：

```ts title="rstest.config.ts"
import path from 'node:path';
import { federation } from '@module-federation/rstest';
import { defineConfig } from '@rstest/core';

export default defineConfig({
  plugins: [
    federation({
      name: 'host',
      remoteType: 'commonjs',
      remotes: {
        remote: `commonjs ${path.resolve(__dirname, '../remote/dist/mf/remoteEntry.cjs')}`,
      },
    }),
  ],
});
```

然后像应用代码一样，通过远程模块的标识符导入暴露模块。使用动态 `import()` 加载，可以让生产者容器先完成初始化：

```ts title="tests/federated-modules.test.ts"
import { expect, test } from '@rstest/core';

test('calls the federated formatPrice module', async () => {
  const { default: formatPrice } = await import('remote/formatPrice');

  expect(formatPrice(1250)).toBe('$12.50');
});
```

这种方式会实际经过远程模块加载和模块联邦运行时，而不是用 mock 替换联邦边界。为了通过类型检查，在 `remotes.d.ts` 里声明这些远程模块：

```ts title="remotes.d.ts"
declare module 'remote/formatPrice' {
  const formatPrice: (cents: number, currency?: string) => string;
  export default formatPrice;
}
```

## 配置远程模块 \{#configure-remotes}

`federation()` 插件接受和模块联邦构建插件相同的选项：`remotes`、`shared`、`remoteType` 等。测试里要注意两点：

- **远程入口的目标环境。** Node 和 JSDOM 测试运行在 Rstest 的 Node runner 里，所以要指向面向 Node 的远程入口，例如 `remoteEntry.cjs`。Browser Mode 消费的是浏览器构建产物中的 `remoteEntry.js`。
- **HTTP 生产者。** 通过 HTTP 提供的生产者写成 `remote@http://localhost:3001/remoteEntry.cjs` 这样的地址，并设置 `remoteType: 'script'`。服务要在测试运行前启动，见[用 `globalSetup` 启动生产者](#serve-remotes-with-globalsetup)。

### 插件和 `federation` 配置项的关系 \{#the-plugin-and-the-federation-option}

[`federation`](/zh/config/test/federation.md) 配置项只是 Rstest 内部的运行时兼容开关。它负责安装垫片，让模块联邦运行时能在 Rstest 的 Node worker 里加载 chunk，本身不配置任何模块联邦的内容：没有远程模块，没有暴露模块，也没有共享依赖。

`federation()` 插件才做真正的配置，并且在基于 Node 的测试环境下会自动打开这个开关。因此在 Node 和 JSDOM 测试里使用插件时，不需要再设置 `federation: true`。Browser Mode 的情况不同，见 [Browser mode](#browser-mode)。

## 用 `globalSetup` 启动生产者 \{#serve-remotes-with-globalsetup}

通过 HTTP 提供的生产者，必须在测试 worker 导入消费者应用之前就能访问。使用 [`globalSetup`](/zh/config/test/global-setup.md) 统一启动服务，并在 `teardown` 里停止，不要在每个测试文件里各起一次：

```ts title="global-setup.ts"
import { createServer, type Server } from 'node:http';

let server: Server;

export async function setup() {
  server = createServer(/* 提供生产者的构建产物 */);
  await new Promise<void>((resolve) => server.listen(3001, resolve));
}

export async function teardown() {
  await new Promise<void>((resolve) => server.close(() => resolve()));
}
```

```ts title="rstest.config.ts"
export default defineConfig({
  globalSetup: './global-setup.ts',
  // ...
});
```

## Browser mode \{#browser-mode}

当生产者必须在真实浏览器中运行时，先配置 [Browser Mode](/zh/guide/browser-testing/getting-started.md)，然后在该项目的 Rstest 配置中保留同一个 `federation` 插件。插件会从解析后的配置中检测 `browser.enabled`，改用 Web 端的模块联邦运行时，而不是 Node 专用默认值。远程入口要指向浏览器构建产物中的 `remoteEntry.js`：

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

export default defineConfig({
  federation: true,
  globalSetup: './global-setup.ts',
  browser: {
    enabled: true,
    provider: 'playwright',
  },
  plugins: [
    pluginReact(),
    federation({
      name: 'host',
      remoteType: 'script',
      remotes: { remote: 'remote@http://localhost:3001/mf/remoteEntry.js' },
      shared: {
        react: { singleton: true },
        'react-dom': { singleton: true },
      },
    }),
  ],
});
```

Browser Mode 下插件不会自动打开 `federation` 配置项。但 `globalSetup` 文件始终在 Node 进程里执行，产物里同样带着模块联邦运行时，没有这个开关，`globalSetup` 文件一加载就会报错，生产者服务也就起不来。所以 Browser Mode 项目只要用了 `globalSetup`，就要手动写上 `federation: true`。

## 了解更多 \{#further-reading}

- [`federation`](/zh/config/test/federation.md) 参考文档介绍了独立配置项、CLI flag 和 Rstest 运行时行为。
- [Rstest 官方集成指南](https://module-federation.io/integrations/build-tool/rstest.html)介绍了如何复用 Rsbuild 的模块联邦配置、插件选项和生产者构建。
- [Node 示例](https://github.com/rstackjs/rstack-examples/tree/main/rstest/module-federation-node)：用 Rslib 构建的本地 CommonJS 生产者，不需要 HTTP 服务。
- [Browser 示例](https://github.com/rstackjs/rstack-examples/tree/main/rstest/module-federation-browser)：通过 HTTP 提供的联邦 React 组件，在 Chromium 中测试。
