☰
Valibot 迁移指南:用 @valibot/zod-to-valibot Codemod 将 Zod 模式自动转换为 Valibot
2026/9/25 17:34:37 网站建设 项目流程
  • 后端
  • 前端

【免费下载链接】valibot

The modular and type safe schema library for validating structural data 🤖

项目地址:https://gitcode.com/gh_mirrors/va/valibot
点击查看免费下载

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);

可以看到三个关键行为:

  1. 同名基础 schema 直接替换:z.string()→v.string(),z.array(...)→v.array(...),前缀z统一替换为v;
  2. 链式校验折叠进v.pipe():z.string().email()变成v.pipe(v.string(), v.email());
  3. 反向链写法归一化: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,流程分两个阶段:

  1. 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。

  2. 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(...)这类跨语句引用也能被正确改写。

如何验证转换结果:测试夹具机制

仓库没有为转换结果提供独立的验收脚本,而是把测试内嵌在 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作为测试装配入口。

限制与注意事项

从源码与夹具可以确认以下几类边界情况,迁移前值得提前排查:

  1. import 写法有严格要求:必须恰好一条from "zod"(或"zod/v4")import 且恰好一个 specifier;多条 import、同时存在默认导入与命名导入等写法会导致文件被跳过,并插入说明注释。因此建议在运行 codemod 前先统一 import 风格(如import { z } from "zod"或import * as z from "zod")。
  2. 部分校验器未实现:base64url、cidr、cuid、duration、jwt等(见 schemas-and-links.ts 中transformUnimplemented的分支)会保留原调用或需要手工替换为等价实现,属于预期内的降级行为。
  3. 自定义 schema 的泛型:z.custom<T>(...)依赖类型参数的透传,改写时需要保证 Valibot 对应 API 的泛型语义一致;源码注释也指出 parser 与类型系统"并不完全同步"(@ts-expect-error),因此对高度依赖泛型推断的代码要额外 review。
  4. 未知链保持原样:任何无法识别的属性名或非标识符属性(如schema["email"]())都会让整条链跳过转换,确保不产生错误代码——这同时意味着迁移后需要人工处理这些残留。
  5. 结果语义的细微差异:result.error→result.issues、result.data→result.output是 Valibot 的命名约定;z.string({ coerce: true })与z.coerce.string()会统一成v.pipe(v.unknown(), v.toString()),语义上等价但运行时多了一层unknown解析,需要按项目对性能与错误信息的要求确认。
  6. 先 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 🤖

项目地址:https://gitcode.com/gh_mirrors/va/valibot
点击查看免费下载

相关推荐

上一篇:Arduino ESP32 核移植指南:为 arduino-esp32 添加全新 SoC 支持的完整流程
下一篇:Loki 仓库内 etcd 官方 Go 客户端 clientv3 完全指南:连接管理、错误处理与配置调优

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询