- 后端
- 前端
【免费下载链接】valibot
The modular and type safe schema library for validating structural data 🤖
Valibot 官方提供的@valibot/zod-to-valibot是一个基于 jscodeshift 的自动化迁移工具(codemod),它能够把项目中的 Zod 模式、校验规则、链式方法、类型推导与解析调用批量改写为等价的 Valibot 代码。本文以 codemod/zod-to-valibot/README.md 为主线,结合仓库源码与测试夹具,完整讲解命令行用法、转换覆盖范围、核心映射规则、底层实现原理与测试验证方式,帮助你安全、高效地完成从 Zod 到 Valibot 的迁移。
为什么需要这个 Codemod
Zod 与 Valibot 都是结构化数据的模式校验库,但两者的 API 形态差异明显:Zod 以"方法链 + 校验函数"为主,Valibot 则把校验逻辑拆分为独立的 action,并通过v.pipe()组合;类型推导、解析结果的返回结构、Coerce 的写法也不尽相同。对于拥有大量 schema 定义的老项目,手工迁移既繁琐又容易出错。
@valibot/zod-to-valibot正是为此设计的自动化方案。它利用 Facebook 的 jscodeshift 在 AST(抽象语法树)层面完成代码改写,支持 TypeScript/JavaScript 与 JSX,能够在修改代码的同时保留原有的注释、缩进和大部分函数逻辑,只替换模式定义与调用方式。从 package.json 可以看到,该工具以bin字段暴露zod-to-valibot命令,其唯一运行时依赖是jscodeshift(^17.3.0),版本为 0.1.2,许可证 MIT。
快速上手:一条命令完成迁移
官方 README 给出的基本用法是:
npx @valibot/zod-to-valibot src/**/*该命令会扫描src目录下所有匹配**/*的文件(默认按扩展名过滤,见下文),对其中的 Zod schema 进行就地改写。底层实现位于 cli.mjs:它解析出当前目录下的dist/index.mjs作为 transform 脚本,并定位jscodeshift/bin/jscodeshift.js,最终以jscodeshift -t transformPath <files>的形式执行。
需要说明的是,cli.mjs 在没有任何参数时会打印使用说明并退出(exit code 0),因此直接运行npx @valibot/zod-to-valibot不会误改任何文件——必须先指定目标文件或目录通配符。
命令行选项详解
README 提供了四组常用选项,它们最终都会透传给 jscodeshift 引擎:
# Dry run(预览改动,不写入文件) npx @valibot/zod-to-valibot --dry src/**/* # 输出详细日志 npx @valibot/zod-to-valibot --verbose=2 src/**/* # 指定解析器(默认为 --parser=ts) npx @valibot/zod-to-valibot --parser=babel src/**/* # 指定文件扩展名(默认为 --extensions=ts,tsx,js,jsx) npx @valibot/zod-to-valibot --extensions=ts src/**/*--dry:仅计算并打印改动结果,不触碰源文件。建议在真正执行迁移前先跑一遍,人工核对 diff 是否符合预期。--verbose=2:提升日志详细程度,便于排查"某个文件为什么没被转换"。--parser:选择 AST 解析器。cli.mjs 会检查参数中是否已包含--parser(或--parser=前缀),若没有则自动在参数最前面补上--parser=ts,所以默认即为ts解析器,可覆盖.ts、.tsx、.js、.jsx四种扩展名;手动传入--parser=babel则切换为 babel 解析器。--extensions:默认值同样是 cli.mjs 注入的ts,tsx,js,jsx;你可以按需收窄,例如只处理.ts文件:--extensions=ts。
由于工具本身只是 jscodeshift 的前端封装,README 也注明:其余可用选项与 jscodeshift 保持一致。在本仓库中,cli.mjs 打印的帮助文本列举了--dry、--print(打印输出)、--verbose=2、--parser=ts等常用项,并提示可通过jscodeshift --help查看完整列表。
转换覆盖范围:到底能转换什么
README 把转换能力归纳为四类,仓库源码中 constants.ts 以常量数组的形式给出了精确清单:
1. 基础 schema
any、array、bigint、boolean、custom、date、discriminatedUnion、enum、instanceof、intersection、literal、nan、nativeEnum、null、nullable、record、map、never、number、object、optional、set、string、symbol、tuple、undefined、union、unknown、void,共 29 种。
2. 校验规则(validators)
base64、cuid2、date、datetime、email、emoji、endsWith、finite、includes、int、ip、length、max、min、multipleOf(同时兼容 Zod 的step)、nanoid、negative、nonempty、nonnegative、nonpositive、positive、readonly、regex、safe、size、startsWith、toLowerCase、toUpperCase、trim、url、gt、gte、lt、lte、time、ulid、uuid等。其中base64url、cidr、cuid、duration、jwt由于 Valibot 暂时没有对应 action,会被标记为未实现(见下文"限制与注意事项")。
3. 链式方法与属性
方法(ZOD_METHODS):array、catchall、default、deepPartial、exclude、extend、extract、keyof、omit、optional、or、merge、nullable、nullish、parse、parseAsync、partial、passthrough、pick、refine、required、rest、safeParse、safeParseAsync、strict、strip、spa、transform、unwrap。
属性(ZOD_PROPERTIES):element、description、shape,以及解析结果的data、error。
4. 类型推导
infer、input、output三类类型引用会被改写为 Valibot 的类型工具(详见下文"类型推导的改写")。
核心映射规则:从测试夹具看实际转换
仓库在 codemod/zod-to-valibot/testfixtures下为每种转换场景准备了input.ts/output.ts成对夹具,下面的映射均以这些真实夹具为准。
基础 schema 与校验规则
array-schema夹具展示了数组与内联校验的转换:
// 转换前(input.ts) const Schema2 = z.array(z.string().email()); const Schema5 = z.string().array(); const Schema7 = Schema6.array(); // 转换后(output.ts) const Schema2 = v.array(v.pipe(v.string(), v.email())); const Schema5 = v.array(v.string()); const Schema7 = v.array(Schema6);可以看到三个关键行为:
- 同名基础 schema 直接替换:
z.string()→v.string(),z.array(...)→v.array(...),前缀z统一替换为v; - 链式校验折叠进
v.pipe():z.string().email()变成v.pipe(v.string(), v.email()); - 反向链写法归一化:
z.string().array()与独立变量引用Schema6.array()都被改写为v.array(Schema6)。
对数值与字符串的校验器,转换遵循"按目标类型选择 action"的规则:z.number().min(0)会变成v.minValue(0),z.number().int()、z.string().min(5)等则分别映射到对应的 Valibot action(可对照 validators 目录 中min、max、int、regex、email、url等实现)。transform夹具中z.number().min(0).transform(...)的输出为v.pipe(v.number(), v.minValue(0), v.transform(...)),验证了min在数值上下文中落地为minValue。
Coercion:z.coerce 的特殊映射
README 特别指出:z.coerce.*会转换为v.pipe(v.unknown(), v.toX())。coerce-string-schema夹具证实了这一映射,并且同时支持两种等价写法:
// 转换前 const Schema1 = z.coerce.string(); const Schema2 = z.string({ coerce: true }); // 转换后 const Schema1 = v.pipe(v.unknown(), v.toString()); const Schema2 = v.pipe(v.unknown(), v.toString());在 string/string.ts 的源码实现中,getSchemaComps会同时接收"前缀标记的 coerce"(z.coerce.string())与"选项对象的 coerce"(z.string({ coerce: true }))两种信号;一旦判定为 coercion,就构建v.pipe(v.unknown(), v.toString())(boolean/number/bigint/date 同理,对应v.toBoolean()、v.toNumber()、v.toBigint()、v.toDate(),可通过 schemas 目录 下各 schema 实现确认)。coerce 链上的后续校验同样保留:z.coerce.string().email()输出v.pipe(v.unknown(), v.toString(), v.email())。
对象、联合、可选与更多组合
z.union([...])→v.union([...]),z.discriminatedUnion('key', {...})→v.variant('key', {...});z.optional(...)、z.nullable(...)、z.nullish(...)分别映射为v.optional(...)、v.nullable(...)、v.nullish(...);z.object({ ... })→v.object({ ... }),且对象链上的.strict()、.passthrough()、.strip()会被检测为"对象修饰符"(见 schemas-and-links.ts 中的checkForObjectModifierInChain),分别改写为v.strictObject(...)、v.looseObject(...)与默认的v.object(...);- 对象方法
pick/omit/partial/required/extend/merge/deepPartial等均有对应转换,如z.object({...}).pick({...})会改写为v.pick(v.object({...}), {...}),相应实现分布在 methods 目录; z.record(...)→v.record(...),z.map(...)/z.set(...)→v.map(...)/v.set(...),z.tuple([...])→v.tuple([...]),z.instanceof(Class)→v.instance(Class);- 校验器的
message参数会转换为 Valibot action 的第二个字符串参数,例如z.array(z.string(), { message: "some message" })输出v.array(v.string(), "some message");description选项则被提升为v.pipe(v.array(v.string()), v.description("some description")),其参数提取逻辑在 schemas/helpers.ts 的getTransformedMsgs/getOptions中实现。
解析调用与结果属性的改写
parsing夹具完整展示了运行时 API 的迁移:
// 转换前 const output1 = Schema.parse("to parse"); const result1 = Schema.safeParse("to safeParse"); if (result1.success) { const output = result1.data; } else { const errors = result1.error; } // 转换后 const output1 = v.parse(Schema, "to parse"); const result1 = v.safeParse(Schema, "to safeParse"); if (result1.success) { const output = result1.output; } else { const errors = result1.issues; }要点:
- 方法调用变为独立的顶层函数:
Schema.parse(x)→v.parse(Schema, x),safeParse/parseAsync/safeParseAsync同理,Schema.spa(x)(旧版别名)也统一映射到v.safeParseAsync(Schema, x); - 结果对象属性重命名:
result.data→result.output,result.error→result.issues(见toValiPropExp中data、error两个分支)。
transform 与 refine
z.string().transform(fn)→v.pipe(v.string(), v.transform(fn));连续多个 transform 会依次放入同一个 pipe;z.number().refine(fn)对应改写为 pipe 中的v.check(fn)形态;z.string().regex(...)→v.regex(...);- 被赋给变量的 schema 引用也会被追踪:
const BaseSchema = z.string(); const T = BaseSchema.transform(...)会输出v.pipe(BaseSchema, v.transform(...))——这正是transformSchemasAndLinks递归跟踪变量链接的结果。
类型推导的改写
type-inference场景(见toValiTypeExp)的映射为:
z.infer<typeof Schema>→v.InferOutput<typeof Schema>z.output<typeof Schema>→v.InferOutput<typeof Schema>z.input<typeof Schema>→v.InferInput<typeof Schema>
类型引用同样会从z前缀替换为v前缀。
源码工作机制:两阶段转换
整个 transform 的入口在 src/transform/index.ts,流程分两个阶段:
imports 阶段(transformImports):见 imports/imports.ts。它查找
from "zod"或from "zod/v4"的 import 语句,要求恰好一条 import、恰好一个 specifier,否则判定失败:文件里根本没有 zod import 时直接跳过(skip),import 写法不满足要求时在文件头部插入@valibot-migrate: unable to transform imports from Zod to Valibot: <原因>注释并原样返回。成功后把 import 改写为import * as v from "valibot";若原标识符不是z(如import { z as zod } from "zod"),则保留原标识符作为 Valibot 的命名空间名。当原标识符恰为z时,还会把所有z.xxx成员表达式与z.xxx类型引用批量替换为v.xxx。schemas-and-links 阶段(transformSchemasAndLinks):见 schemas-and-links.ts。它遍历 AST 中所有以 Valibot 标识符为根的成员表达式与
TSTypeReference,从内到外(.reverse())逐个处理调用链:- 识别到 schema 名(如
string)→ 生成对应的 Valibot schema 调用; - 识别到 validator 名(如
email)→ 通过 helpers.ts 的addToPipe把 action 追加进v.pipe(...)(若已是 pipe 则直接追加参数,否则新建 pipe); - 识别到方法名(如
optional、parse)→ 调用对应的方法转换器; - 遇到不认识的属性名或无法安全解析的链,则标记
transformLinks = false,整条链保持原样,避免破坏代码。
转换器通过
ZOD_SCHEMA_TO_TYPE记录每个 schema 的"类型族"(如string→length、number→value、set→size),以便min/max/nonempty等通用校验器在目标类型上选择正确的 Valibot action。值得注意的细节:被赋值的 schema 变量会被追踪。当链的根表达式是
const X = <zod表达式>的右值时,转换器会递归地以变量名X继续扫描文件中所有引用X的表达式(见transformSchemasAndLinksHelper末尾的变量链接追踪),因此const Schema = z.string(); Schema.parse(...)这类跨语句引用也能被正确改写。- 识别到 schema 名(如
如何验证转换结果:测试夹具机制
仓库没有为转换结果提供独立的验收脚本,而是把测试内嵌在 src/utils.ts 的defineTests中:它读取__testfixtures__下每个子目录,查找input*与output*命名的文件对,用 jscodeshift 的 ts 解析器对input执行当前 transform,并将结果与output做逐字比较(expect(output?.trim()).toBe(expectedOutput.trim()))。测试运行器是 vitest,脚本定义在 package.json 的"test": "vitest"。
这意味着每个转换场景都有一条"输入 → 输出"的金标准:如果你在迁移中遇到某个写法的转换结果不符合预期,可以对照testfixtures中对应场景的output.ts,判断是工具的限制还是自己写法超出了覆盖范围。仓库还提供了test-setup.test.ts作为测试装配入口。
限制与注意事项
从源码与夹具可以确认以下几类边界情况,迁移前值得提前排查:
- import 写法有严格要求:必须恰好一条
from "zod"(或"zod/v4")import 且恰好一个 specifier;多条 import、同时存在默认导入与命名导入等写法会导致文件被跳过,并插入说明注释。因此建议在运行 codemod 前先统一 import 风格(如import { z } from "zod"或import * as z from "zod")。 - 部分校验器未实现:
base64url、cidr、cuid、duration、jwt等(见 schemas-and-links.ts 中transformUnimplemented的分支)会保留原调用或需要手工替换为等价实现,属于预期内的降级行为。 - 自定义 schema 的泛型:
z.custom<T>(...)依赖类型参数的透传,改写时需要保证 Valibot 对应 API 的泛型语义一致;源码注释也指出 parser 与类型系统"并不完全同步"(@ts-expect-error),因此对高度依赖泛型推断的代码要额外 review。 - 未知链保持原样:任何无法识别的属性名或非标识符属性(如
schema["email"]())都会让整条链跳过转换,确保不产生错误代码——这同时意味着迁移后需要人工处理这些残留。 - 结果语义的细微差异:
result.error→result.issues、result.data→result.output是 Valibot 的命名约定;z.string({ coerce: true })与z.coerce.string()会统一成v.pipe(v.unknown(), v.toString()),语义上等价但运行时多了一层unknown解析,需要按项目对性能与错误信息的要求确认。 - 先 dry-run,再动手:建议始终先
--dry预览改动,配合--verbose=2观察被跳过的文件,随后用--extensions分批灰度迁移,并用 vitest 的夹具机制作为回归基线。
迁移建议总结
- 迁移前统一 Zod import 写法,确保每个文件只有一条、一个 specifier 的 import;
- 用
npx @valibot/zod-to-valibot --dry src/**/*预览全量改动,检查被跳过文件与未实现 validator 的清单; - 确认无误后再执行真实迁移,并配合类型检查与测试套件(可参考
__testfixtures__的输入输出对)做全量回归; - 对
z.coerce.*、泛型z.custom<T>、结果对象属性访问等特殊写法重点 review,必要时手工微调。
借助这套官方 codemod,从 Zod 迁移到 Valibot 的大部分机械性工作可以交给自动化完成,团队只需把精力集中在少数边界场景与业务语义核对上,是平滑切换 schema 库的务实起点。
- 后端
- 前端
【免费下载链接】valibot
The modular and type safe schema library for validating structural data 🤖
相关推荐
中兴光猫工厂模式解锁终极指南:zteOnu开源工具快速上手与避坑实战
中兴光猫工厂模式解锁终极指南:zteOnu开源工具快速上手与避坑实战 家里的 中兴光猫 是不是总感觉"缺了点什么"?Web管理页面功能寥寥,Telnet默认关闭
后端前端终极Valibot迁移指南:从Zod无缝转换的7个关键步骤
终极Valibot迁移指南:从Zod无缝转换的7个关键步骤 Valibot是一个模块化且类型安全的 schema验证库 ,专门用于验证结构化数据。如果你正在使用
后端前端Valibot 官方 Zod 迁移 Codemod:从 v0.1.0 到 ES2020 构建目标的版本演进与实现剖析
Valibot 官方 Zod 迁移 Codemod:从 v0.1.0 到 ES2020 构建目标的版本演进与实现剖析 @valibot/zod to valib
后端前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考