Strapi OpenAPI 包技术栈解析:Jest、Rollup 工具链与 Zod 到 OpenAPI 3.1 的转换实现
本文围绕 Strapi monorepo 中 @strapi/openapi 包的技术选型文档展开,逐一剖析其构建工具链(Jest / Rollup / Prettier / ESLint)与三个核心运行时依赖(Zod、Debug、类型包)的实际用法。读完本文后,你将理解 Strapi 如何通过"在路由上挂载 Zod schema、再经 z.toJSONSchema 统一转换"这一机制自动生成 OpenAPI 文档,并能定位到每个技术点在源码中的具体实现位置。
包定位与整体技术面
OpenAPI 包(package.json)的 npm 包名为 @strapi/openapi,自述为 "A tool set to help generate and validate API documentation for Strapi projects",当前版本为 5.52.2,要求 Node.js >=20.0.0 <=26.x.x。
该包遵循 monorepo 的统一工具链,与 Strapi 其他包保持一致:
| 工具 | 用途 | 在仓库中的对应物 |
|---|---|---|
| Jest | 单元测试 | jest.config.js、tests |
| Rollup | 构建 | rollup.config.mjs、根目录 rollup.utils.mjs |
| Prettier | 代码格式化 | 根目录 lint-staged 共享配置(如 lint-staged.shared.mjs) |
| ESLint | 代码静态检查 | package.json 中 lint: eslint . --max-warnings=0 脚本 |
值得注意的是,该包刻意保持"轻量足迹"(light footprint):dependencies 中只有三个运行时依赖——debug@4.3.4、openapi-types@12.1.3、zod@4.4.3。类型系统相关依赖(@strapi/types@5.52.2、@types/debug、@types/jest、@types/node、jest@29.6.0)全部放在 devDependencies 中,不会传导给 Strapi 项目的使用者。
Jest 单元测试配置
从 jest.config.js 可以确认包内测试的完整配置:
- 继承 monorepo 根目录的
jest-preset.unit.js预设,保证各包测试行为一致; - 使用
@swc/jest转换 TypeScript 源码; testMatch限定为<rootDir>/__tests__/**/*.test.ts,即测试文件集中在__tests__目录;displayName为Strapi - OpenAPI,在 monorepo 聚合运行时可区分输出;- 显式覆写了
collectCoverageFrom——从源码注释看,这是因为根配置中的<rootDir>/packages/**在包级运行时会被错误解析为packages/core/openapi/packages/**,所以这里改用<rootDir>/src/**/*.{js,ts}并在 CI 场景下排除dist、config文件等。
测试文件覆盖了包的核心能力,包括 zod-to-openapi.test.ts、document-assemblers.test.ts、operation-assemblers.test.ts 等,与后文将讲解的 Zod 转换、Assembler 组装流程一一对应。
Rollup 构建流程
rollup.config.mjs 非常简洁,直接复用 monorepo 的 baseConfig:
import { baseConfig } from '../../../rollup.utils.mjs';
export default baseConfig({
input: './src/index.ts',
outDir: './dist',
rootDir: './src',
});
结合 package.json 的 scripts 可以看到完整构建链:
build=clean --parallel build:code build:types,即清理dist后并行执行代码与类型声明构建;build:code运行rollup -c,由 Rollup 产出 CJS(dist/index.js)与 ESM(dist/index.mjs)双格式;build:types运行tsc -p tsconfig.build.json --emitDeclarationOnly,只输出.d.ts类型声明;watch提供开发时的rollup -c -w监听模式;prepublishOnly在发布前强制clean && build,防止发布过期产物。
exports 字段同时暴露 types / source / import / require 四组条件,其中 "source": "./src/index.ts" 使得 monorepo 内部消费方在开发态可以直接引用 TypeScript 源码。
依赖之一:Zod —— 从路由 schema 到 OpenAPI 文档的核心
这是原技术文档中最重的一节。文档给出的核心机制是:路由对象在 route.request 和 route.response 上携带 Zod schema,Assembler 通过 z.toJSONSchema 将这些 schema 转换为 OpenAPI 的 parameters、request body 与 response 对象,实现位于 src/utils/zod.ts 与 ComponentsWriter 后处理器。
为什么 target 固定为 draft-2020-12
zod.ts 顶部定义了全包的转换选项常量:
export const OPENAPI_SCHEMA_CONVERSION_OPTIONS = {
target: 'draft-2020-12',
io: 'output',
} as const satisfies Pick<z.core.RegistryToJSONSchemaParams, 'target' | 'io'>;
源码注释解释了两个取值的原因:OpenAPI 3.1 采用 JSON Schema 2020-12 方言,而 Zod 4.4.3 没有 openapi-3.1 这一 target,因此用 draft-2020-12 保持 schema 在 3.1 下的合法性;显式指定 io: 'output' 则保留了此前固化的转换方向(按输出模式而非输入模式转换)。
zodToOpenAPI:本地 registry 与 __shared 提升
核心导出函数 zodToOpenAPI(zodSchema, schemaStore, options?) 的转换流程(见 zod.ts L186-L221)可以归纳为四步:
- 用
randomUUID()生成一个临时 id,创建本地z.registry,并把入参 schema 以该 id 注册进去; - 遍历 Strapi 应用持有的
contentAPISchemaRegistry(类型来自@strapi/types的Core.ContentAPISchemaRegistry),把其中所有命名定义也加入本地 registry——这样引用可以按名字解析,而不会落入下文要讲的__shared桶; - 调用
z.toJSONSchema(registry, { ...OPENAPI_SCHEMA_CONVERSION_OPTIONS, uri: toComponentsPath })完成整体转换,toComponentsPath会把引用 URI 统一改写为 OpenAPI 风格的#/components/schemas/<id>; - 执行
liftZodSharedDefinitions后,只返回入参 schema 对应的那一份结果,并剥离$id字段,保证输出文档稳定。
liftZodSharedDefinitions 解决的是一条具体的 Zod 4.4.3 行为:当某个已命名的嵌套 schema 不在转换 registry 中时,Zod 会把它暂时停在 #/components/schemas/__shared#/$defs/<id> 这个"共享桶"里。该函数把 __shared 桶中的定义逐个提升为 components.schemas 下的一等公民条目,并重写所有指向该桶的 $ref;同时通过 options.extractedComponentSchemas 这个"收获袋"把提升出的命名 schema 传给 Assembler,最终由后处理器合并进文档。
ComponentsWriter 后处理器
component-writer.ts 中的 ComponentsWriter 是文档级的收尾环节:它把整个 strapi.contentAPISchemaRegistry 注册进一个新的 Zod registry,再次执行 z.toJSONSchema 转换,经过 liftZodSharedDefinitions 清洗后,将转换结果与 Assembler 阶段收获在 context.registries.extractedComponentSchemas 中的 schema 一起,合并写入 output.data.components.schemas(已有 components 其余字段会被保留)。
两条设计取舍
原技术文档还明确了两条重要提示,均可在源码中得到印证:
- 把 schema 留在路由上,是为了避免 OpenAPI 的领域逻辑泄漏进 Strapi core——包内所有转换只消费路由携带的 Zod 类型,不反向理解 Strapi 的业务模型;
- 循环依赖仍是难点:模型之间的循环引用(relations、components、dynamic zones、media)在最终生成的组件 schema 中难以准确表达。这一点属于当前实现已声明的限制,阅读该包生成的文档时应对此有预期。
依赖之二:Debug 命名空间体系
Strapi 使用 debug 库输出文档生成过程的详细日志。包级实现非常小,见 src/utils/debug.ts:
export const createDebugger = (
section: string | null = null,
namespace: string = DEBUG_NAMESPACE
) => {
return section !== null ? createDebug(`${namespace}:${section}`) : createDebug(namespace);
};
根命名空间定义在 constants.ts 中,即 strapi:core:openapi,各组件调用 createDebugger('xxx') 时自动追加自己的后缀(如 strapi:core:openapi:xxx)。在开发或排查 OpenAPI 文档生成问题时,只需:
DEBUG=strapi:core:openapi:* node -r dotenv/config your-app.js
即可打开全量 debug 输出。该 wrapper 的价值在于调用方不需要记忆并手写完整命名空间,前缀由包统一保证一致。
依赖之三:类型系统
原技术文档列出的两组类型依赖,在 package.json 中的实际形态如下:
@strapi/types@5.52.2(devDependency):提供 Strapi 核心对象的类型表示,如路由(route)与应用对象。包源码中的Core.ContentAPISchemaRegistry(见 zod.ts 的schemaStore参数类型)即来自该包,对应源码目录 packages/core/types;openapi-types@12.1.3(runtime dependency,仅类型用途):提供完整 OpenAPI 文档及其子对象的类型。包内统一使用其OpenAPIV3_1命名空间,例如zodToOpenAPI的返回类型为OpenAPIV3_1.SchemaObject | OpenAPIV3_1.ReferenceObject,ComponentsWriter写入components.schemas时也以Record<string, OpenAPIV3_1.SchemaObject>约束,说明该包产出的是 OpenAPI 3.1 规范的文档对象。
小结与延伸阅读
@strapi/openapi 的技术选型体现了一条清晰的主线:工具链完全复用 monorepo 标准(Jest + Rollup + Prettier + ESLint),运行时依赖收敛到三个(Zod、Debug、openapi-types),核心工程难点集中在"Zod registry 转换 → __shared 桶提升 → ComponentsWriter 合并"这条 schema 落地链路上。若要继续深入,建议按以下顺序阅读仓库内的关联文档与源码:
- 概述与用法:00-intro.md、03-usage.md
- 架构细节:02-architecture.md
- 贡献指南(routes provider、matcher、assembler、processors 等分册):contributing/00-overview.md
- 转换实现与测试:src/utils/zod.ts、tests/zod-to-openapi.test.ts
需要说明的是:以上版本与配置信息均以当前仓库快照(包版本 5.52.2)为准,跨版本使用时请以实际项目的 package.json 与文档为准。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00