使用 Nitro + mono-jsx 实现零配置 JSX 服务端渲染(Server Entry 实战指南)
【免费下载链接】nitroNext Generation Server Toolkit. Create web servers with everything you need and deploy them wherever you prefer.项目地址: https://gitcode.com/GitHub_Trending/ni/nitro
导读
本文以仓库中的 mono-jsx 示例 为核心,讲解如何在 Nitro 中利用server.tsx服务器入口(Server Entry)把 JSX 直接编译为 HTML 响应,从而用最少的配置搭建一个 JSX 驱动的服务端渲染页面。读完本文,你将掌握 Nitro 对server.tsx的自动检测机制、tsconfig.json中jsxImportSource的配置方法,以及 oxc 编译器如何读取该配置完成 JSX 转换的底层原理。
从最小示例认识 mono-jsx 集成
示例目录 examples/mono-jsx 的 README 用一段极简代码展示了整个集成方式,核心文件 server.tsx 全文如下:
export default () => ( <html> <h1>Nitro + mono-jsx works!</h1> </html> );整个集成只需要三步,不需要任何 Nitro 配置:
- 创建
server.tsx文件,export default一个返回 JSX 的函数; - 在
tsconfig.json中把 JSX 编译器指向mono-jsx(详见下文"工程配置"一节); - 在
package.json中声明mono-jsx依赖并运行nitro dev/nitro build。
正如原文档所述,Nitro 会自动检测server.tsx,并使用 mono-jsx 将 JSX 转换为 HTML;只要导出的函数返回 JSX,Nitro 就会把渲染出的 HTML 作为响应返回给客户端。
值得注意的是,nitro.config.ts 是一个完全空的配置:
import { defineConfig } from "nitro"; export default defineConfig({});这印证了"零配置"的结论:JSX 渲染能力不需要任何 Nitro 侧显式声明,一切由文件约定与 tsconfig 驱动。
Nitro 如何自动检测 server.tsx:Server Entry 机制
mono-jsx 示例之所以能工作,依赖的是 Nitro 的Server Entry(服务器入口)机制。根据官方文档 Nitro Server Entry,该机制的核心规则如下:
- Nitro 默认会在
serverDir(若设置)或项目根目录自动查找server.ts(以及.js、.mjs、.mts、.tsx、.jsx等变体),server.tsx正是其中之一; - 找到后,Nitro 会将其注册为一个 catch-all(
/**)路由,即所有未被具体路由匹配的请求都会进入该处理函数; - 特定路由永远优先:例如存在
routes/api/hello.ts时,/api/hello由它处理,而/about这类未匹配请求才会落到 server entry; - 在请求生命周期中,server entry 运行于路由匹配之后、renderer(渲染器) 之前(参考 Lifecycle 文档)。
因此,把返回 JSX 的函数导出为server.tsx的默认导出,就相当于告诉 Nitro:"所有未被路由处理的请求,都渲染这段 JSX 作为 HTML 页面返回"。这与"创建一个独立页面路由"不同——它是一种兜底性的响应逻辑,天然适合整站入口、SPA 外壳或框架挂载场景。
与 middleware 的区别
需要特别澄清:server entry 是兜底处理器而非全局中间件。它不会对已被路由处理的请求执行。如果需要在每一个请求(包括已匹配路由的请求)上执行鉴权、日志、预处理等横切逻辑,应使用 middleware;如果只是需要在所有未匹配路径上返回 JSX 页面,则 server entry(即本文的server.tsx方案)是正确的选择。
工程配置:tsconfig 中的 jsxImportSource
mono-jsx 示例能正确编译的关键在 tsconfig.json:
{ "extends": "nitro/tsconfig", "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "mono-jsx" } }两个配置项的作用分别是:
"jsx": "react-jsx":启用"自动运行时"(automatic runtime)JSX 转换模式。在此模式下,源码中的<html>、<h1>等 JSX 会被编译为对jsx()/jsxs()工厂函数的调用,而不是直接调用React.createElement;"jsxImportSource": "mono-jsx":把 JSX 工厂函数的导入来源指向mono-jsx包。编译器会自动生成import { jsx as _jsx } from "mono-jsx/jsx-runtime"(或等价导入),由 mono-jsx 的运行时把 JSX 节点树序列化为 HTML 字符串。
这就是"JSX 转换为 HTML"的机制核心:JSX 只是语法糖,真正决定渲染产物的是jsxImportSource指向的运行时。mono-jsx 提供的运行时负责把元素树拼装成 HTML,因此不需要任何浏览器端框架参与。
配套工程文件
示例还提供了两种运行方式:
package.json 声明了 npm 脚本与依赖:
{ "type": "module", "scripts": { "dev": "nitro dev", "build": "nitro build" }, "devDependencies": { "mono-jsx": "latest", "nitro": "latest" } }pnpm dev(或npm run dev):启动开发服务器,支持热更新,修改server.tsx会即时生效;pnpm build(或npm run build):执行生产构建,输出可部署的产物。
vite.config.ts 提供了以 Vite 插件方式接入 Nitro 的写法:
import { defineConfig } from "vite"; import { nitro } from "nitro/vite"; export default defineConfig({ plugins: [nitro()] });两种方式等价:既可以直接使用nitroCLI(package.json 脚本),也可以通过nitro/vite插件把 Nitro 嵌入 Vite 构建管线。
底层原理:oxc 编译器如何读取 jsxImportSource
从源码看,Nitro 的构建系统会把tsconfig.json中的 JSX 配置直接传递给 oxc 转换器,从而决定 JSX 如何编译。相关实现位于 src/build/rollup/config.ts(Rollup 构建路径)与 src/build/rolldown/config.ts(Rolldown 构建路径),两者的关键逻辑一致:
const tsc = nitro.options.typescript.tsConfig?.compilerOptions; // ... jsx: { runtime: tsc?.jsx === "react" ? "classic" : "automatic", pragma: tsc?.jsxFactory, pragmaFrag: tsc?.jsxFragmentFactory, importSource: tsc?.jsxImportSource, development: nitro.options.dev, // ... }这段代码揭示了完整映射关系:
| tsconfig 配置项 | oxc jsx 选项 | 作用 |
|---|---|---|
jsx: "react-jsx" | runtime: "automatic" | 使用自动运行时,无需手动导入jsx工厂 |
jsxFactory | pragma | 经典模式下的工厂函数名 |
jsxFragmentFactory | pragmaFrag | 经典模式下的 Fragment 工厂名 |
jsxImportSource | importSource | 自动运行时从哪个包导入jsx工厂(mono-jsx 即由此注入) |
| (开发模式) | development: nitro.options.dev | 开发态下的 JSX 转换差异 |
也就是说,mono-jsx 示例的jsxImportSource: "mono-jsx"会一路流入 oxc 的importSource配置,最终使编译产物从mono-jsx导入 JSX 工厂函数完成 HTML 序列化。同时,Nitro 构建配置中的扩展名列表(见 src/build/config.ts、src/build/vite/plugin.ts 与 src/config/resolvers/paths.ts)均包含.tsx、.jsx,从解析层面保证了server.tsx能被正常扫描、打包与执行。
扩展:把 server.tsx 方案接入更复杂的场景
掌握了"server entry + JSX"这一模式后,可以按需演进:
1. 结合路由与 API
server entry 只处理未被路由匹配的请求。你可以在routes/目录下继续编写 API 路由(参考 api-routes 示例),例如routes/api/hello.ts返回 JSON,而server.tsx负责渲染页面——两者互不干扰,优先级由 Nitro 的路由匹配保证。
2. 自定义 server entry 文件
如果不想用默认文件名,可在 nitro.config.ts 中指定serverEntry:
import { defineConfig } from "nitro"; export default defineConfig({ serverEntry: "./nitro.server.tsx", });serverEntry也支持对象形式,用于显式声明处理器格式:
export default defineConfig({ serverEntry: { handler: "./server.tsx", format: "web", // "web"(默认)或 "node" }, });format为"web"时要求导出 Web 兼容的fetch(request: Request): Response处理器;为"node"时则期望(req, res)风格的 Node 处理器(对应server.node.ts命名约定,Nitro 会自动转换)。设置serverEntry: false可完全禁用自动检测。
3. 返回 undefined 交给渲染器
server entry 的返回值语义很重要:返回 JSX(即返回响应)则请求在此结束;若函数不返回任何值,Nitro 会把请求继续交给 renderer(如renderer.ts或index.html)处理。因此可以写出"先尝试 JSX 渲染、未命中再降级到渲染器"的链式逻辑。
4. 其他 JSX 运行时对比
仓库中还提供了采用不同 JSX 运行时的同类示例 nano-jsx 示例,其server.tsx结构与 mono-jsx 几乎一致,仅jsxImportSource指向nano-jsx。这印证了该模式的通用性:Nitro 并不绑定任何 JSX 运行时,只要该运行时提供自动 JSX 工厂,就能通过jsxImportSource无缝接入。选择 mono-jsx 还是 nano-jsx,取决于你对运行时体积、API 风格和生态的具体偏好。
运行与验证
在 examples/mono-jsx 目录下执行:
pnpm install # 安装 nitro 与 mono-jsx 依赖 pnpm dev # 启动开发服务器浏览器访问开发地址,即可看到Nitro + mono-jsx works!的 HTML 页面。终端会输出 Nitro 检测到 server entry 的提示日志(形如Detected server.tsx as server entry.),确认自动检测生效。生产环境执行pnpm build产出可部署构建,可将产物部署到 Node、Bun、Deno、Cloudflare Workers 等任意支持的目标运行时(参见 部署运行时文档)。
小结
- Nitro 自动把
server.tsx识别为 server entry(catch-all 路由),无需任何显式配置; tsconfig.json中jsx: "react-jsx"+jsxImportSource: "mono-jsx"决定 JSX 的编译目标与 HTML 运行时来源;- Nitro 构建系统(Rollup/Rolldown)将 tsconfig 的 JSX 选项透传给 oxc 转换器(src/build/rollup/config.ts),完成从 JSX 到 HTML 的编译链路;
- server entry 是兜底处理器而非全局中间件,跨请求横切逻辑应使用 middleware;
- 该模式对任意提供 JSX 自动运行时的包通用,可轻松替换为 nano-jsx 等其他实现。
【免费下载链接】nitroNext Generation Server Toolkit. Create web servers with everything you need and deploy them wherever you prefer.项目地址: https://gitcode.com/GitHub_Trending/ni/nitro
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考