
# Rsbuild 集成 {#rsbuild-integration}

> 将 Nitro 用作 Rsbuild 插件，为 Rsbuild 应用添加服务器（实验性功能）。

::warning
Rsbuild 插件目前处于实验阶段，其行为可能在次要版本中发生变化。
::

Nitro 以插件形式集成到 [Rsbuild](https://rsbuild.rs)（v2.3 及更高版本）中，为 Rsbuild 应用添加完整的服务器能力（API 路由、服务端渲染和 WebSocket），并将客户端和服务器构建到同一个 `.output/` 目录，可使用任意[部署预设](/deploy)进行部署。

## 将 Nitro 用作 Rsbuild 插件 {#nitro-as-an-rsbuild-plugin}

在 Rsbuild 配置中添加该插件：

```ts [rsbuild.config.ts]
import { defineConfig } from "@rsbuild/core";
import { nitro } from "nitro/rsbuild";

export default defineConfig({
  plugins: [nitro({ serverDir: "./" })],
});
```

启用插件后，使用 Rsbuild CLI 执行以下命令：

- **`rsbuild dev`**：前后端共用一个开发服务器。Nitro 路由会先于 Rsbuild 中间件处理，因此优先于客户端资源。其他请求先由 Rsbuild 提供服务（编译后的资源、公共目录和 HTML 页面），再交由 Nitro 处理。服务器文件变更时会重新加载服务器代码，客户端代码仍使用 Rsbuild 的热更新。
- **`rsbuild build`**：先将客户端构建到 Nitro 的公共输出目录，再使用已配置的预设构建服务器。带哈希的客户端资源（默认位于 `static/`）会使用不可变缓存响应头提供服务（除非 `/static/**` 路由规则自行设置 `cache-control`），且不会回退到其他处理器：请求不存在的文件时会返回带有 `Cache-Control: no-store` 的 `404`，不会继续到达路由或渲染器。
- **`rsbuild build --watch`**：文件变更时重新构建。每次构建服务器之前都会复制公共资源并重新预渲染路由，新增或删除服务器路由也会触发服务器重建。仅在首次构建之前清理输出目录。使用 `static` 预设时，只有客户端变化会触发重建。
- **`rsbuild preview`**：通过 Nitro 在本地提供生产构建，包括静态资源和 WebSocket 支持。

Nitro 选项通过 `nitro()` 传入（或在 `nitro.config.ts` 中设置）。服务器使用 Rsbuild 的 Rspack 实例，并通过 [`rspack`](/config/build#builder) 构建器配置进行打包。

在开发模式下，对于 Rsbuild 无法提供的资源请求（脚本、样式或图片），即使有捕获所有路由或 SSR 渲染器可以返回页面，也会响应 `404`。

Nitro 的 [`baseURL`](/config/routing#baseurl) 与 Rsbuild 服务器的基础路径相同（[`server.base`](https://rsbuild.rs/config/server/base)）：客户端页面和资源都在该路径下提供。只设置 `server.base` 时，它也会作为 Nitro 的 `baseURL`。

## 环境 {#environments}

该插件使用 Rsbuild [环境](https://rsbuild.rs/guide/advanced/environments)，并添加一个用于构建服务器的 `nitro` 环境。每个 `output.target: "web"` 的环境都是客户端环境（未自定义环境时默认为 `web` 环境），构建产物会输出到公共目录。存在多个客户端环境时，页面（HTML 模板和服务端渲染所需的客户端资源）依次由 `client` 环境、`web` 环境或最先定义的环境负责。

其他 `output.target: "node"` 的环境都是**服务**：Nitro 会将其输出与服务器一起打包，并通过 `nitro/services` 中的 `fetchService()` 调用。 [`services`](/config/build#services) 选项中的服务会在同名的服务器环境中构建：

```ts [rsbuild.config.ts]
export default defineConfig({
  plugins: [nitro({ services: { api: { entry: "./src/api-entry.ts" } } })],
});
```

```ts [routes/api/[...].ts]
import { defineHandler } from "nitro";
import { fetchService } from "nitro/services";

export default defineHandler((event) => fetchService("api", event.req));
```

服务入口会导出一个 `fetch` 方法（形式为 `default.fetch` 或 `fetch`）。服务与服务器共享 Nitro 运行时，因此服务代码可以导入 `nitro/*` 模块（例如 `nitro/runtime-config` 或 `nitro/storage`）。

## 服务端渲染 {#server-side-rendering}

如果你没有定义 `ssr` 环境，Nitro 会检测项目根目录、`app/`、`src/` 或服务器目录中的 `entry-server.(ts|js|tsx|jsx|mts|mjs)` 文件，并为其添加一个 `ssr` 环境。

- **HTML 模板**：当 Rsbuild 的 [`html.template`](https://rsbuild.rs/config/html/template) 中包含 `<!--ssr-outlet-->` 注释时，Nitro 会使用 Rsbuild 生成的 HTML（已注入客户端脚本）渲染页面，并将该注释替换为 `ssr` 入口的响应。
- **完整响应**：否则，`ssr` 入口会自行渲染页面，并由其 `fetch` 方法返回完整的 `Response`。

```ts [rsbuild.config.ts]
import { defineConfig } from "@rsbuild/core";
import { nitro } from "nitro/rsbuild";

export default defineConfig({
  source: { entry: { index: "./app/entry-client.ts" } },
  html: { template: "./index.html" },
  plugins: [nitro({ serverDir: "./" })],
});
```

```ts [app/entry-server.ts]
export default {
  async fetch(req: Request) {
    return `<h1>Rendered for ${new URL(req.url).pathname}</h1>`;
  },
};
```

在开发模式下，页面请求（导航到不带文件扩展名的路径）由 Nitro 渲染，而不是返回 Rsbuild 的客户端 HTML。

HTML 模板会编译进服务器包中（也可以使用[渲染器模板](/docs/renderer)语法，并在上下文中使用 `fetchService`），因此运行时不会再编译模板，包括 Cloudflare Workers 等禁止运行时编译的环境。开发模式下，生成的 HTML 发生变化时会重建服务器。

### 客户端资源 {#client-assets}

不使用 HTML 模板、而是渲染整个文档的 SSR 入口，可以通过 `?assets` 导入客户端模块的脚本和样式 URL：

```ts [app/entry-server.ts]
import clientAssets from "./entry-client.ts?assets=client";

export default {
  async fetch(req: Request) {
    const head = [
      ...clientAssets.css.map(({ href }) => `<link rel="stylesheet" href="${href}">`),
      ...clientAssets.js.map(({ href }) => `<script defer src="${href}"></script>`),
    ].join("");
    const html = `<!doctype html><html><head>${head}</head><body>...</body></html>`;
    return new Response(html, { headers: { "content-type": "text/html" } });
  },
};
```

默认导出为 `{ entry, js, css, merge() }`，与 [Vite](/docs/vite) 相同：

- `js` 按加载顺序列出模块分块及其所需分块中的脚本。Rsbuild 默认生成经典脚本，因此要将它们分别作为脚本添加（带上 `defer`）；`entry` 是最后一项（即模块自身的分块）。
- `css` 列出这些分块中的样式表。
- `merge(...others)` 合并多个结果（例如 `clientAssets.merge(pageAssets)`），并去除重复项。

该模块必须是客户端环境的入口，或通过动态导入（`import()`）加载。`?assets=client`（或 `?assets`）指向负责页面的客户端环境（参见[环境](#environments)），`?assets=<name>` 则指向其他客户端环境。服务器路由和服务代码中都可以使用 `?assets` 导入。

`?assets` 导入的类型已包含在 `nitro/rsbuild` 中（也可以添加 `/// <reference types="nitro/rsbuild/types" />`）。

::note
在开发模式下，Rsbuild 会在浏览器加载动态导入时才编译它们（[`dev.lazyCompilation`](https://rsbuild.rs/config/dev/lazy-compilation)），因此在此之前，动态导入的资源列表为空。Rsbuild 不会输出服务器代码导入的 CSS（`?assets=ssr` 为空）。
::

参见 [Rsbuild SSR HTML 示例](/examples/rsbuild-ssr-html)（HTML 模板）和 [Rsbuild React SSR 示例](/examples/rsbuild-ssr-react)（完整文档，并使用 `?assets` 导入）。

## 框架 {#frameworks}

其他 Rsbuild 插件添加的环境也会得到配置：框架插件添加 `client` 和 `ssr` 环境后，其客户端产物会输出到 Nitro 公共目录，`ssr` 环境则会用作渲染器。

与 [TanStack Start](https://tanstack.com/start) 配合时，在 `@tanstack/react-start/plugin/rsbuild` 的 `tanstackStart()` 插件旁添加 `nitro()`，并禁用它的开发中间件，以便 Nitro 处理发往 `ssr` 环境的所有请求（页面、服务器函数和服务器路由）：

```ts [rsbuild.config.ts]
import { defineConfig } from "@rsbuild/core";
import { pluginReact } from "@rsbuild/plugin-react";
import { tanstackStart } from "@tanstack/react-start/plugin/rsbuild";
import { nitro } from "nitro/rsbuild";

export default defineConfig({
  plugins: [
    pluginReact(),
    tanstackStart({ rsbuild: { installDevServerMiddleware: false } }),
    nitro(),
  ],
});
```

参见 [Rsbuild TanStack Start SSR 示例](/examples/rsbuild-ssr-tss-react)。

## 注意事项 {#good-to-know}

- 支持使用其他运行时运行开发服务器的预设（例如使用 workerd 的 `cloudflare-module`），包括服务器环境。
- 服务器使用 Nitro 的 Rspack 配置构建：Rsbuild 规则和其他插件的转换仅应用于客户端和服务环境，不应用于 `nitro` 环境。服务器构建插件请使用 [`buildPlugins`](/config/build#buildplugins)。
- 使用 HTML 模板时，只会使用负责页面的客户端环境中的 `index.html` 页面。
