Zod logo

版本控制

更新 — 2025年7月8日

🌐 Update — July 8th, 2025

zod@4.0.0 已发布到 npm。包根目录("zod")现在导出 Zod 4。所有其他子路径未更改,并将永远可用。

要升级到 Zod 4:

🌐 To upgrade to Zod 4:

npm install zod@^4.0.0

如果你正在使用 Zod 4,你现有的导入("zod/v4""zod/v4-mini")将永远继续工作。然而,在升级之后,你可以可选地将导入重写如下:

🌐 If you are using Zod 4, your existing imports ("zod/v4" and "zod/v4-mini") will continue to work forever. However, after upgrading, you can optionally rewrite your imports as follows:

之前之后
Zod 4"zod/v4""zod"
Zod 4 Mini"zod/v4-mini""zod/mini"
Zod 3"zod""zod/v3"

库作者 — 如果你已经按照库作者指南中概述的最佳实践实现了 Zod 4 支持,请将你的同级依赖升级以包含 zod@^4.0.0

// package.json
{
  "peerDependencies": {
    "zod": "^3.25.0 || ^4.0.0"
  }
}

不应需要其他代码更改。 在最新的 3.25.x 版本和 4.0.0 之间未进行任何代码更改。这不需要进行主版本升级。

关于子路径版本控制的一些说明

归根结底,子路径版本控制方案是一种必要的手段,以迫使生态系统以非破坏性的方式升级。如果我一开始就发布了 zod@4.0.0,大多数库可能会天真地提高它们的同行依赖版本,从而在整个生态系统中引发“版本提升雪崩”。

🌐 Ultimately, the subpath versioning scheme was a necessary evil to force the ecosystem to upgrade in a non-breaking way. If I'd published zod@4.0.0 out of the gate, most libraries would have naively bumped their peer dependencies, forcing a "version bump avalanche" across the ecosystem.

目前,整个生态系统对 Zod 4 已经有了广泛支持。没有哪个迁移过程是完全无痛的,但看起来我所担心的“版本雪崩”并没有发生。总体而言,库已经能够同时支持 Zod 3 和 Zod 4:Hono、LangChain、React Hook Form 等。几位生态系统维护者专门联系我,表示逐步添加对 Zod 4 的支持非常方便(通常这类操作需要一个主要版本更新)。长话短说:这种方法非常有效!很少有其他库会受到与 Zod 相同的限制,但我强烈鼓励其他拥有大型相关生态系统的库考虑类似的方法。

Zod 4 中的版本控制

🌐 Versioning in Zod 4

本文介绍了 Zod 4 的版本控制方法,旨在帮助用户和 Zod 相关库生态系统更轻松地迁移到 Zod 4。

🌐 This is a writeup of Zod 4's approach to versioning, with the goal of making it easier for users and Zod's ecosystem of associated libraries to migrate to Zod 4.

通用方法:

🌐 The general approach:

  • Zod 4 最初不会以 zod@4.0.0 的形式在 npm 上发布。相反,它将作为子路径("zod/v4")与 zod@3.25.0 一起导出
  • 尽管如此,Zod 4 仍然被认为是稳定的,并且可以投入生产。
  • Zod 3 将继续从包根目录("zod")导出,同时也将从新的子路径 "zod/v3" 导出。它将继续收到错误修复和稳定性改进。

这种方法类似于 Golang 处理主要版本更改的方式:https://go.dev/doc/modules/major-version

稍后:

🌐 Sometime later:

  • 包根目录("zod")将从导出 Zod 3 切换到 Zod 4
  • 此时 zod@4.0.0 将发布到 npm
  • "zod/v4" 子路径将永远可用

为什么?

🌐 Why?

Zod 在生态系统中占有独特的位置。生态系统中的许多库/框架都接受用户定义的 Zod 模式。这意味着它们面向用户的 API 与 Zod 及其各种类/接口/实用工具紧密耦合。对于这些库/框架来说,对 Zod 的重大更改必然会导致其用户出现兼容性问题。一个 Zod 3 的 ZodType 不能赋值给 Zod 4 的 ZodType

🌐 Zod occupies a unique place in the ecosystem. Many libraries/frameworks in the ecosystem accept user-defined Zod schemas. This means their user-facing API is strongly coupled to Zod and its various classes/interfaces/utilities. For these libraries/frameworks, a breaking change to Zod necessarily causes a breaking change for their users. A Zod 3 ZodType is not assignable to a Zod 4 ZodType.

为什么库不能同时支持 v3 和 v4?

🌐 Why can't libraries just support v3 and v4 simultaneously?

遗憾的是,peerDependencies 的局限性(以及包管理器之间的不一致)使得同时优雅地支持一个库的两个主要版本变得极其困难。

🌐 Unfortunately the limitations of peerDependencies (and inconsistencies between package managers) make it extremely difficult to elegantly support two major versions of one library simultaneously.

如果我天真地将 zod@4.0.0 发布到 npm,Zod 生态系统中的绝大多数库都需要发布一个新的主要版本,以正确支持 Zod 4,包括一些高知名度的库例如 AI SDK。这将在整个生态系统中引发“版本升级雪崩”,并且通常会造成大量的挫败感和工作量。

🌐 If I naively published zod@4.0.0 to npm, the vast majority of the libraries in Zod's ecosystem would need to publish a new major version to properly support Zod 4, include some high-profile libraries like the AI SDK. It would trigger a "version bump avalanche" across the ecosystem and generally create a huge amount of frustration and work.

通过子路径版本控制,我们解决了这个问题。它为库同时支持 Zod 3 和 Zod 4(包括 Zod Mini)提供了一种直接的方法。他们可以继续为 "zod" 定义单一的 peerDependency;无需使用像 npm 别名、可选 peer 依赖、"zod-compat" 包或其他此类技巧的更复杂解决方案。

🌐 With subpath versioning, we solve this problem. it provides a straightforward way for libraries to support Zod 3 and Zod 4 (including Zod Mini) simultaneously. They can continue defining a single peerDependency on "zod"; no need for more arcane solutions like npm aliases, optional peer dependencies, a "zod-compat" package, or other such hacks.

库将需要将其 "zod" 同行依赖的最低版本提升到 zod@^3.25.0。然后他们可以在实现中同时引用 Zod 3 和 Zod 4:

🌐 Libraries will need to bump the minimum version of their "zod" peer dependency to zod@^3.25.0. They can then reference both Zod 3 and Zod 4 in their implementation:

import * as z3 from "zod/v3"
import * as z4 from "zod/v4"

稍后,一旦对 v4 有了广泛的支持,我们将提升 npm 的主版本,并从包根目录开始导出 Zod 4,完成过渡。(这现在已经发生——请参见本页顶部的说明。)

🌐 Later, once there's broad support for v4, we'll bump the major version on npm and start exporting Zod 4 from the package root, completing the transition. (This has now happened—see the note at the top of this page.)

只要库只从关联的子路径(而非根路径)导入,它们的实现就能在主版本升级后继续工作,无需代码更改。

🌐 As long as libraries are importing exclusively from the associated subpaths (not the root), their implementations will continue to work across the major version bump without code changes.

虽然这看起来可能不太传统(至少对于不使用 Go 语言的人来说是这样),但据我所知,这是唯一一种能够为 Zod 用户和更广泛生态系统中的库提供干净、增量迁移路径的方法。

🌐 While it may seem unorthodox (at least for people who don't use Go!), this is the only approach I'm aware of that enables a clean, incremental migration path for both Zod's users and the libraries in the broader ecosystem.


深入探讨为什么在这种情况下同级依赖不起作用。

🌐 A deeper dive into why peer dependencies don't work in this situation.

想象你是一个库,正在尝试构建一个接受 Zod schema 的函数 acceptSchema。你希望能够接受 Zod 3 或 Zod 4 的 schema。在这个假设中,我设想 Zod 4 作为 zod@4 发布在 npm 上,没有子路径。这里是你的选项:

🌐 Imagine you're a library trying to build a function acceptSchema that accepts a Zod schema. You want to be able to accept Zod 3 or Zod 4 schemas. In this hypothetical, I'm imagine Zod 4 was published as zod@4 on npm, no subpaths. Here are your options:

  1. 使用 npm 别名同时安装 zod@3 和 zod@4 为 dependencies。这样可以工作,但你最终会包含你自己的 Zod 3 和 Zod 4 的副本。你无法保证用户的 Zod 模式是你从依赖中引入的相同 z.ZodType 类的实例(instanceof 检查可能会失败)。
  2. 使用跨多个主版本的同级依赖:"zod@>=3.0.0"……但在开发库时,你仍然需要选择一个版本进行开发。通常你会将其安装为开发依赖。责任在于你,需要仔细确保你的代码在两个版本中逐字逐句地工作。对于 Zod 3 和 Zod 4 来说,这是不可能的,因为一些非常基础的类已经简化或使用了不同的泛型。
  3. 可选的同辈依赖。我就是找不到一个明确的答案,说明如何在所有平台上可靠地确定运行时安装了哪个同辈依赖。网上的很多答案都会说“在 try/catch 中使用动态导入来检查某个包是否存在”。那些人是假设你在后端环境,因为没有前端打包器支持这种操作。当你尝试打包一个未安装的依赖时,这种方法会失败。显然,在构建步骤中处于 try/catch 内无所谓。此外:既然我们在讨论同一个库的多个版本,你需要使用 npm 别名在你的 package.json 中区分这两个版本。即便是最近的 npm v10 版本,也无法处理同辈依赖 + npm 别名的组合。
  4. zod-compat。你在网上看到的这种极度模糊的解决方案是“为每个版本定义表示某些基本功能的接口”。基本上,一些工具类型库可以用它来近似真实实现。这容易出错,工作量巨大,需要与真实实现保持同步,而且最终库开发的是你库的一个影子版本,可能缺少细节。它也只适用于类型:如果某个库依赖 Zod 中的任何运行时代码,它就会崩溃。

因此,需要子路径。

🌐 Hence, subpaths.

On this page