首页
/ shadcn/ui Registry 完全指南:registry.json 编写、include 拆分、地址方案与 GitHub 直连

shadcn/ui Registry 完全指南:registry.json 编写、include 拆分、地址方案与 GitHub 直连

2026-09-04 11:24:18作者:董宙帆

本篇基于 shadcn/ui 仓库中的 Registry 编写参考文档(registry.md)展开,系统讲解 shadcn 注册表(Registry)的两种形态、根注册表元数据、include 模块化拆分、item 字段规范、依赖地址方案与 GitHub 注册表直连机制。读完后你可以独立完成一个可被 CLI 安装的第三方注册表,理解其地址解析与构建校验的底层实现,并用官方命令完成构建、验证与安装演练。

1. 注册表的两种形态:Source Registry 与 Built Registry

在动手编写之前,必须先建立正确的心理模型。shadcn 的注册表有两种形态:

  • Source registry(源码注册表):项目或仓库中手工编写的 registry.json。它可以使用 include 拆分,且文件路径直接指向真实源文件。
  • Built registry(构建后注册表):生成给 CLI 消费者使用的 JSON 文件,通常托管在 public/r 目录下。使用 npx shadcn@latest build 从源码注册表生成这一形态。

CLI 安装器消费的是 registry item 的载荷(payload)。源码注册表本质上是一种“从真实文件手工编写这些载荷”的方式。

一个容易忽略的事实是:registry item 并不局限于 React 组件。它可以分发组件、hooks、工具函数、设计 token、页面、配置文件、文档、规则、工作流、模板、MCP 文件以及其他项目文件。这一点在 官方根注册表 中可以得到印证——其中既有 registry:ui 类型的组件 item,也有带 registry:pageregistry:fileregistry:component 混合文件集且携带 registryDependenciesregistry:block item(如 dashboard-01),文件数量达数千行,是真实的、可直接对照的编写范本。

2. 根 registry.json:元数据与 item 定义

根注册表文件应当定义注册表元数据,并提供 itemsinclude 二者之一。最小示例:

{
  "$schema": "https://ui.shadcn.com/schema/registry.json",
  "name": "acme",
  "homepage": "https://acme.com",
  "items": [
    {
      "name": "absolute-url",
      "type": "registry:lib",
      "title": "Absolute URL",
      "description": "A utility to turn any path into an absolute URL.",
      "files": [
        {
          "path": "lib/absolute-url.ts",
          "type": "registry:lib"
        }
      ]
    }
  ]
}

根注册表规则(与实现一一对应):

  • registry.json 必须包含 namehomepage
  • items 是 registry item 定义数组;
  • include 可用于将大型源码注册表拆分为多个文件;
  • 被 include 的注册表文件可以省略 namehomepage

这些规则并非文档空谈,加载器在 loader.tsvalidateRootRegistry 中会逐项检查根文件缺少 namehomepage 时抛出带有明确字段名的校验错误,并提示“被 include 的注册表文件可以省略这些字段”。在 schema 层,schema.ts 中的 registryBaseSchema 也通过 refine 强制“itemsinclude 至少定义其一”,而最终的 registrySchema 则要求根级 namehomepageitems 均为必填。若你想做 IDE 校验,仓库提供了可下载编辑的 JSON Schema 文件,见 registry-item.jsonschema.ts 中的注释明确要求两处保持同步)。

2.1 item 常用字段

一个较完整的 item 定义,覆盖 npm 依赖、注册表依赖与 CSS 变量注入:

{
  "name": "login-form",
  "type": "registry:block",
  "title": "Login Form",
  "description": "A login form with email and password fields.",
  "dependencies": ["zod"],
  "registryDependencies": ["button", "input", "label"],
  "files": [
    {
      "path": "blocks/login-form.tsx",
      "type": "registry:block"
    }
  ],
  "cssVars": {
    "light": {
      "brand": "oklch(0.62 0.18 250)"
    },
    "dark": {
      "brand": "oklch(0.72 0.16 250)"
    }
  }
}

关键字段说明:

  • name:可安装的 item 名称。它不一定是文件路径;
  • type:registry item 类型之一,如 registry:uiregistry:blockregistry:libregistry:hookregistry:fileregistry:pageregistry:themeregistry:styleregistry:fontregistry:item
  • files:该 item 复制或生成的源文件;
  • dependencies:npm 运行时依赖;
  • devDependencies:npm 开发依赖;
  • registryDependencies:该 item 依赖的其他 registry item;
  • cssVarscsstailwindenvVarsdocs:可选的安装期附加内容。

从源码结构看,完整可写的类型枚举比文档列举的更多:schema.ts 中的 registryItemTypeSchema 还包含 registry:componentregistry:base,以及内部专用的 registry:exampleregistry:internal。官方注册表里的 sidebar item 就展示了 tailwind(注入 Tailwind 主题扩展)与 cssVars(light/dark 双模式变量)的真实用法。

2.2 文件路径规则

  • 文件路径相对于声明该 item 的 registry.json
  • registry:fileregistry:page 类型的文件必须提供 target
  • 源码注册表中的文件路径禁止使用远程 URL;
  • 保持源文件“可复制可粘贴”:不要引入应用私有的隐藏导入。

target 必填这一点在 schema 层是硬性约束:registryItemFileSchema 用 discriminated union 区分两类文件——registry:file/registry:page 要求 target: z.string() 必填,其他类型 target 可选。对照 apps/v4/registry.jsondashboard-01 的写法可以直观理解 target 的语义:registry:page 文件指定 "target": "app/dashboard/page.tsx"registry:file 指定 "target": "app/dashboard/data.json",即“源路径 → 安装后落位路径”的映射。

路径安全约束同样由加载器强制执行。loader.tsvalidateRegistryItemFiles 会拒绝四类路径:远程 URL、绝对路径、含 .. 的父级穿越,以及解析后逃逸出 chunk 目录的路径——错误信息都会精确指出是哪个 item 的哪个文件违反了哪条规则。

3. 使用 include 拆分大型注册表

大型注册表可以用 include 保持模块化:

{
  "$schema": "https://ui.shadcn.com/schema/registry.json",
  "name": "acme",
  "homepage": "https://acme.com",
  "include": ["registry/ui/registry.json", "registry/blocks/registry.json"]
}

include 规则:

  • include 路径相对于声明它的 registry.json
  • include 路径必须显式指向一个 registry.json 文件;
  • 不得使用远程 URL、绝对路径或父级穿越(..);
  • item 的文件路径相对于声明该 item 的注册表文件;
  • item 名称在解析后的整个注册表范围内重复会导致构建失败。

被 include 的文件示例:

{
  "items": [
    {
      "name": "button",
      "type": "registry:ui",
      "files": [
        {
          "path": "button.tsx",
          "type": "registry:ui"
        }
      ]
    }
  ]
}

若该文件位于 registry/ui/registry.json,则 button.tsxregistry/ui/button.tsx 读取,而构建后输出的 item 路径是相对于根注册表的。

从源码结构看,这条“路径重写”由 loader.tsrewriteRegistryItemFilePaths 完成:加载时记录每个 item 的“来源三元组”(注册表文件、所在目录、item 下标),构建时把源路径解析为绝对路径后再 path.relative(rootDir, ...) 相对根目录重写,统一为 / 分隔。也就是说,被 include 文件中的相对路径在源文件中是“局部”的,但在构建产物中一律呈现为“全局”的,这是理解 include 语义的关键。

加载器对 include 的校验比文档规则更细,值得实现者或排错者知晓:

  • 根文件命名约束:使用 include 的根文件必须命名为 registry.jsonloader.ts);
  • include 路径校验resolveIncludePath 依次拒绝远程 URL、绝对路径、父级穿越、非 registry.json 结尾的 basename,并强制解析结果必须仍位于 registry 根目录内;
  • 深度上限MAX_INCLUDE_DEPTH = 32,超过会提示“展平 include 树”;
  • 环检测:include 链成环会报 Registry include cycle detected 并打印完整调用链;
  • 重复 include 检测:同一个 registry.json 在解析树中出现两次会直接报错,并要求“移入唯一的公共 include”;
  • 重名检测validateDuplicateItems 会给出两个冲突 item 各自的来源(registryFile items[i]),方便定位重名位置。

4. registryDependencies:是地址,不是文件路径

registryDependencies 的每一项都是 item 地址(address),不是文件路径:

{
  "name": "login-form",
  "type": "registry:block",
  "registryDependencies": ["button", "@acme/input", "acme/ui/card#v1.2.0"],
  "files": [
    {
      "path": "blocks/login-form.tsx",
      "type": "registry:block"
    }
  ]
}

依赖规则:

  • 裸名称(如 "button")指官方 shadcn item;
  • 裸名称永远不指同注册表或同仓库的 item;
  • 命名空间依赖使用 @namespace/item-name
  • GitHub 依赖使用 owner/repo/item-name
  • 需要时用 owner/repo/item-name#ref 钉住 GitHub 依赖版本;
  • ref 不会被继承:如果 owner/repo/foo#v2 依赖同仓库 v2 上的 bar,必须显式写成 owner/repo/bar#v2
  • 不要使用 "./bar" 这类相对依赖。

4.1 地址方案分类表

面对一个 item 字符串时,应当先做分类:

地址 方案 含义
button shadcn 官方 shadcn item button
@acme/button namespace 配置好的注册表 @acme 中的 item button
@acme/ui/button namespace 配置好的注册表 @acme 中的 item ui/button
https://example.com/r/button.json url 该 URL 上的构建后 item JSON
./button.json file 磁盘上的构建后 item JSON
acme/ui/button github GitHub 仓库 acme/ui 中的 item button
acme/ui/forms/login#main github GitHub 仓库 acme/ui 中 ref main 上的 item forms/login

对 namespace 和 GitHub 地址,带斜杠的 item 名是允许的,且它们是 item 名而非文件路径。以 .json 结尾的地址保持 file 地址优先级,因此 acme/ui/data/schema.json 会被视为文件路径而不是 GitHub item 地址。

4.2 CLI 的解析顺序

这个分类逻辑在 CLI 中有精确的纯函数实现。address.tsresolveItemAddress 按固定顺序尝试四种方案:

  1. isUrl 命中 → url 方案;
  2. isLocalFile 命中 → file 方案(这就是 .json 结尾优先于 GitHub 解析的原因);
  3. parser.ts 用正则 ^(@a-zA-Z0-9?)\/(.+)$ 匹配 @namespace/itemnamespace 方案;
  4. resolveGitHubItemAddress 匹配 owner/repo/item[#ref](要求至少 3 段路径)→ github 方案;
  5. 其余一律回落到 shadcn 官方方案。

GitHub 段还有严格的大小写/字符约束:owner 需匹配 GITHUB_OWNER_PATTERN(字母数字与连字符,不允许连续连字符),repo 需匹配 GITHUB_REPO_PATTERN 且不得为 ./..,ref 不允许空白、控制字符或以 - 开头(address.ts)。这些细节解释了为什么一个看似合法的地址字符串会被拒绝。

5. GitHub 注册表:让公共仓库直接充当源码注册表

任何根目录带有 registry.json 的公开 GitHub 仓库都可以直接作为源码注册表被 CLI 消费。地址格式:

owner/repo/item-name[#ref]

规则:

  • 前两个路径段是 GitHub owner 与 repo;
  • 其余所有路径段构成 registry item 名;
  • 源入口始终是根 registry.json
  • GitHub 注册表是被 CLI 直接消费的源码注册表,不需要 shadcn build,也不需要生成 item JSON 文件;
  • include 遵循与本地注册表相同的源码注册表规则;
  • 目前仅支持 github.com 上的公开仓库;私有仓库与 GitHub Enterprise 需要明确的产品决策。

5.1 为什么必须先把 ref 解析成 commit SHA

实现 GitHub 注册表拉取时,应当在读取源文件之前把 ref 解析为 commit SHA。不要直接从 raw.githubusercontent.com 读取流动的 ref,因为分支类 ref 可能被缓存数分钟,导致一次命令中读到不一致的仓库状态。推荐的解析流程:

owner/repo[#ref]
  -> resolve ref with git ls-remote
  -> commit SHA
  -> read https://raw.githubusercontent.com/{owner}/{repo}/{sha}/registry.json
  -> read includes and item files from the same SHA

这保证一条命令始终落在同一个一致的仓库快照上。完整的 40 位 commit SHA 本身稳定,可以直接使用;分支、标签与短 ref 则需要本机 Git 参与,先由 CLI 解析为 commit SHA。

github-ref.ts 正是这一流程的实现,几个可验证的细节:

  • 完整 SHA 判定使用 GITHUB_SHA_PATTERN = /^[a-fA-F0-9]{40}$/github-ref.ts),命中后直接小写返回,跳过网络解析;
  • 非完整 ref 通过 git ls-remote --symref 解析(15 秒超时),候选优先级由 getPreferredGitHubRefNames 决定:先查 refs/heads/{ref},再查 refs/tags/{ref}^{}refs/tags/{ref},最后才尝试裸 ref——即分支优先于标签;
  • 解析失败会给出带上下文的错误与建议(提示使用完整分支、标签或 SHA),Git 不存在时明确提示安装 Git;
  • 值得注意的是,该模块还带有一条认证回退路径resolveGitHubRefWithAuth,配合 github-auth.tsgithub-cli.ts),在匿名 ls-remote 失败时可选择鉴权解析。结合参考文档“私有仓库与 GitHub Enterprise 需要明确产品决策”的表述,可以推断这条路径是面向受限仓库预留的机制,而文档层面当前承诺的仍是公开 github.com 仓库。

6. 构建与验证

6.1 构建源码注册表

npx shadcn@latest build
npx shadcn@latest build registry.json --output public/r

第二条命令展示了显式指定入口文件与输出目录的用法。官方站点仓库的构建链路可参考 apps/v4/package.json 中的脚本定义:build 目标先执行 pnpm registry:build(串行构建 @shadcn/react@shadcn/helpersshadcn CLI 三个包,再运行 scripts/build-registry.mts 生成 public/r 下的构建产物),然后才执行 next build

6.2 用 CLI 检查构建结果

npx shadcn@latest list @acme
npx shadcn@latest search @acme -q "login"
npx shadcn@latest view @acme/login-form
npx shadcn@latest add @acme/login-form --dry-run
npx shadcn@latest registry validate ./registry.json

对 GitHub 注册表,直接使用 owner/repo 地址即可,无需先构建:

npx shadcn@latest list owner/repo
npx shadcn@latest search owner/repo -q "login"
npx shadcn@latest view owner/repo/item
npx shadcn@latest add owner/repo/item --dry-run
npx shadcn@latest registry validate owner/repo

这套命令覆盖了“构建 → 列表 → 搜索 → 查看详情 → 干跑安装 → 校验”的完整验证闭环;--dry-run 让安装行为可在不落盘的情况下被检查,registry validate 则支持本地文件与 GitHub 地址两种目标,是发布注册表前推荐的最后一步。

7. 在 shadcn/ui 代码库中实现注册表时的守则

参考文档最后给出了面向仓库贡献者的实现守则,这些约束在上述源码中都有对应体现:

  • 保持地址解析纯函数、可测试——address.ts 全文件无副作用,配套 address.test.tsparser.test.ts
  • 校验器不引入副作用,loader.ts 的校验函数全部只读取与抛错;
  • 保留官方 shadcn、namespace、url、file 四种既有方案的既有行为(即 resolveItemAddress 的优先级不可随意调整);
  • 为地址解析、源码加载、依赖解析、list、search、view、add 各路径补齐测试——packages/shadcn/src/registry/ 目录下几乎每个实现模块都带有同名测试文件;
  • 在出现多个真实 provider 之前,优先小型 source-reader 抽象,而不是插件系统。

8. 关键参考路径汇总

内容 路径
Registry 编写参考文档(本文主体) registry.md
地址解析实现 address.tsparser.ts
include 加载、路径重写与校验 loader.ts
item/注册表 Zod schema schema.ts
JSON Schema(可编辑同步) registry-item.json
GitHub ref 解析 github-ref.ts
官方根注册表真实示例 apps/v4/registry.json
官方构建脚本 apps/v4/package.json
shadcn 技能规则(配套阅读) SKILL.mdcli.md

适用前提与限制:本文所述构建命令均基于 npx shadcn@latest,以当前仓库中 CLI 包(packages/shadcn)的实际实现为准;GitHub 直连注册表当前仅覆盖公开 github.com 仓库,ref 解析依赖本机 Git 且对完整 40 位 SHA 免解析;include 树深度上限 32 层、重名与重复 include 均会直接导致构建失败,编写大型注册表时应提前规划目录结构以避免踩线。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
982
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384