Acme

Build Registry 完全指南

从零开始理解 shadcn/ui 组件注册系统

本文档面向初学者,帮助你理解 shadcn/ui 的组件注册和构建系统,以及如何复刻类似的系统。

📚 目录


什么是 Registry

Registry(注册表) 是 shadcn/ui 的核心机制,它是一个组件元数据的集合,记录了:

  • 组件的名称、类型、描述
  • 组件的文件路径
  • 组件的依赖关系(npm 包依赖、组件间依赖)
  • 组件的配置信息(Tailwind 配置、CSS 变量等)

简单来说,Registry 就像是一个组件商店的目录清单,告诉 CLI 工具:

  • 有哪些组件可以使用
  • 组件在哪里
  • 安装组件需要什么

整体架构

┌─────────────────────────────────────────────────────────────┐
│                      开发者工作区                              │
│  ┌──────────────────┐  ┌──────────────────┐                 │
│  │  创建组件文件      │  │  编写注册配置    │                 │
│  │  (手动)           │  │  (手动)         │                 │
│  └──────────────────┘  └──────────────────┘                 │
└─────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│                    Build Registry 脚本                       │
│                      (自动化流程)                             │
│  ┌──────────────────────────────────────────────────────┐   │
│  │ 1. buildRegistryIndex()   - 生成组件索引              │   │
│  │ 2. buildBlocksIndex()     - 生成 Blocks 索引          │   │
│  │ 3. buildRegistryJsonFile()- 生成 JSON 配置            │   │
│  │ 4. buildRegistry()        - 构建发布版本              │   │
│  │ 5. syncRegistry()         - 同步到 www 应用           │   │
│  └──────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│                      生成的产物                               │
│  ┌──────────────────┐  ┌──────────────────┐                 │
│  │  registry.json   │  │  __index__.tsx   │                 │
│  │  (元数据清单)     │  │  (动态导入索引)   │                 │
│  └──────────────────┘  └──────────────────┘                 │
│  ┌──────────────────┐  ┌──────────────────┐                 │
│  │ __blocks__.json  │  │  public/r/*      │                 │
│  │ (Blocks 列表)     │  │  (发布文件)       │                 │
│  └──────────────────┘  └──────────────────┘                 │
└─────────────────────────────────────────────────────────────┘

核心概念

Registry Item 类型

在 shadcn/ui 中,有多种类型的 Registry Item:

类型说明示例
registry:ui基础 UI 组件Button, Input, Card
registry:block完整的页面块/模板Login Form, Dashboard
registry:hookReact Hooksuse-toast, use-mobile
registry:lib工具函数utils.ts
registry:example示例代码Button Demo
registry:internal内部组件文档展示用
registry:page页面文件完整页面
registry:component组合组件Block 的子组件

Registry Item 结构

每个组件在注册表中的配置结构如下:

{
  name: "button",                          // 组件名称
  type: "registry:ui",                      // 类型
  description: "A button component",        // 描述
  dependencies: ["@radix-ui/react-slot"],   // npm 依赖
  registryDependencies: ["utils"],          // 其他组件依赖
  files: [                                  // 文件列表
    {
      path: "ui/button.tsx",               // 文件路径
      type: "registry:ui",                  // 文件类型
      target: ""                            // 可选:目标安装路径
    }
  ],
  tailwind: {                               // 可选:Tailwind 配置
    config: { /* ... */ }
  },
  cssVars: {                                // 可选:CSS 变量
    light: { /* ... */ },
    dark: { /* ... */ }
  }
}

文件组织结构

registry/
├── index.ts                    # 主入口,聚合所有注册配置
├── registry-ui.ts              # UI 组件注册配置
├── registry-blocks.ts          # Blocks 注册配置
├── registry-hooks.ts           # Hooks 注册配置
├── registry-lib.ts             # 工具函数注册配置
├── registry-examples.ts        # 示例注册配置
├── __index__.tsx               # 🤖 自动生成:组件动态导入索引
├── __blocks__.json             # 🤖 自动生成:Blocks 简化列表
└── new-york-v4/                # 实际组件文件目录
    ├── ui/                     # UI 组件
    │   ├── button.tsx
    │   ├── input.tsx
    │   └── ...
    ├── blocks/                 # Blocks
    │   ├── login-01/
    │   │   ├── page.tsx
    │   │   └── components/
    │   └── ...
    ├── hooks/                  # Hooks
    ├── lib/                    # 工具函数
    └── examples/               # 示例

构建流程详解

流程图

第 1 步:buildRegistryIndex()

目的: 生成一个可以在运行时动态加载组件的索引文件

过程:

// 输入:registry/index.ts 中的配置
const registry = {
  items: [
    { name: "button", files: [{ path: "ui/button.tsx" }] },
    // ...
  ]
}

// 输出:registry/__index__.tsx
export const Index = {
  "button": {
    name: "button",
    files: ["registry/new-york-v4/ui/button.tsx"],
    component: React.lazy(() => import("@/registry/new-york-v4/ui/button.tsx")),
    // ...
  }
}

作用:

  • 允许文档网站动态预览组件
  • 支持按需加载,提高性能

第 2 步:buildBlocksIndex()

目的: 生成一个精简的 Blocks 列表,用于快速查询

过程:

// 输入:所有 type: "registry:block" 的组件
const blocks = getAllBlocks(["registry:block"])

// 输出:registry/__blocks__.json
[
  {
    "name": "login-01",
    "description": "A simple login form",
    "categories": ["authentication", "login"]
  },
  // ...
]

作用:

  • 用于 Blocks 页面的快速列表展示
  • 减少前端加载的数据量

第 3 步:buildRegistryJsonFile()

目的: 生成完整的组件元数据清单

过程:

  1. 修正路径:将相对路径转换为绝对路径

    // 之前: "ui/button.tsx"
    // 之后: "registry/new-york-v4/ui/button.tsx"
  2. 生成 JSON:写入 registry.json

  3. 格式化:使用 Prettier 格式化

  4. 同步:复制到 www/public/r/styles/new-york-v4/registry.json

作用:

  • CLI 工具通过此文件知道如何下载和安装组件
  • 网站通过此文件展示组件列表和元数据

第 4 步:buildRegistry()

目的: 使用 shadcn CLI 构建可发布的组件文件

过程:

# 运行 shadcn CLI 的 build 命令
shadcn build registry.json --output ../www/public/r/styles/new-york-v4

输出结构:

www/public/r/styles/new-york-v4/
├── button.json              # 每个组件的独立元数据
├── input.json
├── login-01.json
└── ...

每个 JSON 文件包含:

  • 组件的完整代码(内联)
  • 依赖信息
  • 安装配置

作用:

  • 用户运行 npx shadcn@latest add button 时,CLI 从这里下载
  • 支持离线缓存和 CDN 分发

第 5 步:syncRegistry()

目的: 同步 v4 和 www 两个应用的 registry

过程:

  1. 复制 v4/public/r/registries.jsonwww/public/r/registries.json
  2. 运行 www 项目的 pnpm registry:build
  3. 复制 www/public/rv4/public/r

作用:

  • 保持两个应用的组件库同步
  • www 是旧版,v4 是新版,需要兼容

实践指南

如何创建一个组件

👨‍💻 人工操作部分

步骤 1:创建组件文件

# 在 registry/new-york-v4/ui/ 目录下创建组件文件
touch registry/new-york-v4/ui/my-component.tsx
// registry/new-york-v4/ui/my-component.tsx
import * as React from "react"
import { cn } from "@/lib/utils"

interface MyComponentProps extends React.HTMLAttributes<HTMLDivElement> {
  variant?: "default" | "primary"
}

function MyComponent({ className, variant = "default", ...props }: MyComponentProps) {
  return (
    <div
      className={cn("my-component", className)}
      data-variant={variant}
      {...props}
    />
  )
}

export { MyComponent }

步骤 2:在注册表中配置

编辑 registry/registry-ui.ts

export const ui: Registry["items"] = [
  // ... 其他组件
  {
    name: "my-component",                      // 组件名称(用户安装时使用)
    type: "registry:ui",                        // 类型
    description: "My custom component",         // 描述
    dependencies: [],                           // npm 依赖(如需要)
    registryDependencies: ["button", "card"],   // 依赖其他组件(如需要)
    files: [
      {
        path: "ui/my-component.tsx",           // 文件路径(相对于 new-york-v4/)
        type: "registry:ui",
      },
    ],
    // 可选:如果需要特殊的 Tailwind 配置
    tailwind: {
      config: {
        theme: {
          extend: {
            colors: {
              "my-color": "#ff0000"
            }
          }
        }
      }
    },
    // 可选:如果需要 CSS 变量
    cssVars: {
      light: {
        "my-var": "0 0% 100%"
      },
      dark: {
        "my-var": "0 0% 0%"
      }
    }
  },
]

步骤 3:运行构建命令

pnpm registry:build

🤖 自动化完成的部分

脚本会自动:

  1. ✅ 读取你的配置
  2. ✅ 生成 registry/__index__.tsx,添加动态导入
  3. ✅ 生成 registry.json,包含完整元数据
  4. ✅ 生成 public/r/styles/new-york-v4/my-component.json
  5. ✅ 同步到其他目录

📦 用户如何使用

npx shadcn@latest add my-component

CLI 会:

  1. 读取 public/r/styles/new-york-v4/my-component.json
  2. 检查并安装 npm 依赖
  3. 安装 registryDependencies(button, card
  4. 复制组件代码到用户项目
  5. 更新 Tailwind 配置(如果需要)

如何创建一个 Block

Block 是完整的页面模板或功能块,通常包含多个文件。

👨‍💻 人工操作部分

步骤 1:创建 Block 目录和文件

# 创建 Block 目录
mkdir -p registry/new-york-v4/blocks/my-dashboard/{components,lib}

# 创建主页面
touch registry/new-york-v4/blocks/my-dashboard/page.tsx

# 创建组件
touch registry/new-york-v4/blocks/my-dashboard/components/sidebar.tsx
touch registry/new-york-v4/blocks/my-dashboard/components/header.tsx

# 可选:创建数据文件
touch registry/new-york-v4/blocks/my-dashboard/data.json

文件结构:

blocks/my-dashboard/
├── page.tsx                 # 主页面文件
├── components/
│   ├── sidebar.tsx         # 子组件
│   └── header.tsx          # 子组件
└── data.json               # 可选:数据文件

步骤 2:编写代码

// page.tsx
import { Sidebar } from "./components/sidebar"
import { Header } from "./components/header"

export default function Page() {
  return (
    <div className="flex h-screen">
      <Sidebar />
      <div className="flex-1">
        <Header />
        <main className="p-6">
          {/* 内容 */}
        </main>
      </div>
    </div>
  )
}
// components/sidebar.tsx
export function Sidebar() {
  return <aside className="w-64 border-r">Sidebar</aside>
}

步骤 3:在注册表中配置

编辑 registry/registry-blocks.ts

export const blocks: Registry["items"] = [
  // ... 其他 blocks
  {
    name: "my-dashboard",
    type: "registry:block",
    description: "A dashboard with sidebar and header",

    // npm 依赖
    dependencies: [
      "@tanstack/react-table",
      "recharts",
    ],

    // 组件依赖(会自动安装这些组件)
    registryDependencies: [
      "sidebar",
      "card",
      "button",
      "table",
    ],

    // 文件列表
    files: [
      {
        path: "blocks/my-dashboard/page.tsx",
        type: "registry:page",                    // 标记为页面文件
        target: "app/dashboard/page.tsx",         // 安装到用户项目的路径
      },
      {
        path: "blocks/my-dashboard/data.json",
        type: "registry:file",                    // 普通文件
        target: "app/dashboard/data.json",
      },
      {
        path: "blocks/my-dashboard/components/sidebar.tsx",
        type: "registry:component",               // 子组件
      },
      {
        path: "blocks/my-dashboard/components/header.tsx",
        type: "registry:component",
      },
    ],

    // 分类(用于筛选和展示)
    categories: ["dashboard", "admin"],

    // 元数据(用于文档展示)
    meta: {
      iframeHeight: "800px",                     // 预览高度
      container: "w-full min-h-screen",          // 容器样式
    },
  },
]

步骤 4:运行构建

pnpm registry:build

🤖 自动化完成的部分

脚本会自动:

  1. ✅ 将 Block 添加到 __index__.tsx
  2. ✅ 将 Block 添加到 __blocks__.json(用于列表展示)
  3. ✅ 生成 my-dashboard.json,包含所有文件的内联代码
  4. ✅ 处理依赖关系

📦 用户如何使用

npx shadcn@latest add my-dashboard

CLI 会:

  1. 安装 npm 依赖:@tanstack/react-table, recharts
  2. 安装组件依赖:sidebar, card, button, table
  3. 复制文件到指定位置:
    • page.tsxapp/dashboard/page.tsx
    • data.jsonapp/dashboard/data.json
    • sidebar.tsxcomponents/sidebar.tsx
    • header.tsxcomponents/header.tsx

人工操作 vs 自动化

🙋 必须人工完成的工作

任务说明
编写组件代码实际的 React 组件逻辑、样式、功能
设计组件 APIProps、事件、接口设计
编写注册配置registry-*.ts 中添加组件元数据
确定依赖关系决定需要哪些 npm 包和其他组件
设置文件路径决定组件的目录结构和安装位置
编写测试和文档可选但推荐
配置 Tailwind/CSS如果需要特殊配置

🤖 脚本自动完成的工作

任务脚本说明
生成动态索引buildRegistryIndex()创建 __index__.tsx
生成 Blocks 列表buildBlocksIndex()创建 __blocks__.json
生成 JSON 配置buildRegistryJsonFile()创建 registry.json
构建发布文件buildRegistry()为每个组件生成独立 JSON
同步多个应用syncRegistry()在 v4 和 www 之间同步
格式化代码Prettier自动格式化生成的文件
路径转换自动相对路径 → 绝对路径
内联代码shadcn CLI将组件代码嵌入 JSON

典型工作流程

👨‍💻 人工:编写组件代码

👨‍💻 人工:编写注册配置

🤖 运行:pnpm registry:build

🤖 自动:生成所有元数据和索引

✅ 完成:组件可以被安装使用

工作流程图

完整的开发到发布流程


快速参考

常用命令

# 开发模式(启动开发服务器)
pnpm dev

# 构建 registry(添加/修改组件后运行)
pnpm registry:build

# 捕获 Blocks 截图(用于文档展示)
pnpm registry:capture

# 验证 registry 配置
pnpm validate:registries

# 格式化代码
pnpm format:write

关键文件速查

文件作用编辑频率
registry/registry-ui.tsUI 组件注册配置⭐⭐⭐ 经常
registry/registry-blocks.tsBlocks 注册配置⭐⭐⭐ 经常
registry/index.ts注册表入口⭐ 很少
scripts/build-registry.mts构建脚本⭐ 很少
registry/__index__.tsx🤖 自动生成❌ 不要编辑
registry.json🤖 自动生成❌ 不要编辑

常见问题

Q: 我创建了组件但 CLI 找不到?

  • A: 确保运行了 pnpm registry:build
  • A: 检查 registry-*.ts 中的配置是否正确

Q: 组件安装后路径不对?

  • A: 检查 files[].target 配置
  • A: Block 的 page.tsx 应设置 target

Q: 依赖没有自动安装?

  • A: 检查 dependenciesregistryDependencies 配置
  • A: npm 包依赖用 dependencies
  • A: 其他组件依赖用 registryDependencies

Q: 如何调试构建过程?

  • A: 查看脚本输出的日志
  • A: 检查生成的 registry.json 文件
  • A: 查看 public/r/styles/new-york-v4/*.json 文件

总结

核心要点

  1. Registry 是组件元数据的集合,不是组件本身
  2. 手动部分:编写代码和配置
  3. 自动部分:运行脚本生成所有元数据
  4. 两层结构
    • 开发层:registry/new-york-v4/ - 组件源码
    • 配置层:registry/registry-*.ts - 元数据配置
  5. 三个产物
    • __index__.tsx - 运行时索引
    • registry.json - 完整配置
    • public/r/*.json - 发布文件

复刻要点

如果你要创建类似的系统,需要:

  1. 定义 Schema:组件元数据的数据结构
  2. 组织文件结构:组件代码 + 配置分离
  3. 编写构建脚本
    • 读取配置
    • 生成索引
    • 生成 JSON
    • 内联代码(可选)
  4. 创建 CLI 工具:读取 JSON 并安装组件
  5. 文档系统:展示组件列表和预览

下一步

  • 📖 阅读 shadcn CLI 源码理解安装逻辑
  • 🔧 尝试创建自己的组件和 Block
  • 🎨 自定义构建流程以满足需求
  • 📦 发布到 npm 或私有 registry

文档版本: v1.0 最后更新: 2025-10-23 作者: AI Assistant 项目: shadcn/ui Build Registry 系统解析