Rsbuild 集成

将 Nitro 用作 Rsbuild 插件,为 Rsbuild 应用添加服务器(实验性功能)。

Warning

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

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

#将 Nitro 用作 Rsbuild 插件

在 Rsbuild 配置中添加该插件:

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 构建器配置进行打包。

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

Nitro 的 baseURL 与 Rsbuild 服务器的基础路径相同(server.base):客户端页面和资源都在该路径下提供。只设置 server.base 时,它也会作为 Nitro 的 baseURL。

#环境

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

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

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

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

#服务端渲染

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

  • HTML 模板:当 Rsbuild 的 html.template 中包含 <!--ssr-outlet--> 注释时,Nitro 会使用 Rsbuild 生成的 HTML(已注入客户端脚本)渲染页面,并将该注释替换为 ssr 入口的响应。
  • 完整响应:否则,ssr 入口会自行渲染页面,并由其 fetch 方法返回完整的 Response。
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: "./" })],
});

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

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

#客户端资源

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

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 相同:

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

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

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

Note

在开发模式下,Rsbuild 会在浏览器加载动态导入时才编译它们(dev.lazyCompilation),因此在此之前,动态导入的资源列表为空。Rsbuild 不会输出服务器代码导入的 CSS(?assets=ssr 为空)。

参见 Rsbuild SSR HTML 示例(HTML 模板)和 Rsbuild React SSR 示例(完整文档,并使用 ?assets 导入)。

#框架

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

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

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 示例。

#注意事项

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