Plate 注册表的 shadcn 对齐(Parity)设计:上游优先契约、所有权边界与模板同步实战
【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate
本文以 Plate 仓库内的 shadcn-parity 技能文档(.agents/skills/shadcn-parity/SKILL.md)为主体,完整解析 Plate 与上游 shadcn 注册表(registry)之间的对齐契约:谁拥有契约、谁拥有内容、依赖与命名空间遵循哪些规则。结合仓库中的注册表构建入口、模板同步脚本与本地镜像工具源码,你将掌握 Plate 注册表从源码声明、构建输出到模板安装的全链路机制,并能在修改注册表或模板时做出符合上游契约的正确决策。
什么是 "shadcn parity":核心契约
shadcn-parity 技能文档定义的核心契约只有一句话:当需要 Plate 与 shadcn 对齐时,上游 shadcn 是注册表行为与模式的唯一事实来源(source of truth)。文档明确要求交付"逐源对齐"(source-by-source parity),而不是"inspired by shadcn"(受 shadcn 启发)。
文档给出了一条简洁的优先级规则,按顺序排列:
- 上游 schema 优先(upstream schema first)
- 上游 resolver 行为优先(upstream resolver behavior first)
- 上游文件/布局模式优先(upstream file/layout patterns first)
- 上游命名空间语义优先(upstream namespace semantics first)
- 只有当仓库存在真实约束时,才允许 Plate 自身偏离(Plate divergence only when the repo has a real constraint)
并且文档划出一条硬性红线:Plate 不 fork shadcn CLI。Plate 并不拥有自研的安装器,它拥有的只是围绕上游 shadcn 契约构建的注册表与模板同步层。一旦选择偏离上游,必须精确说明偏离原因("If you diverge, say exactly why")。
这条契约在仓库中有直接证据。Plate 注册表的数据类型完全来自上游 shadcn 包:registry.ts 第一行即
import type { Registry, RegistryItem } from 'shadcn/schema';也就是说,Plate 注册表条目在类型层面就绑定了上游Registry/RegistryItemschema,而不是 Plate 自定义的数据模型。此外,仓库在 registry-shadcn.json 中内嵌了一份完整的上游 shadcn 注册表快照(含button、sidebar、dashboard-01等条目及其dependencies、registryDependencies、cssVars字段),作为对齐比对时的参照物——这正是文档中 "check../shadcnfirst / compare Plate output against../shadcnbefore inventing a custom rule" 的落地形态。
所有权边界:shadcn 拥有契约,Plate 拥有内容与交付
技能文档把责任切成两半,这是理解整套机制的关键:
shadcn 拥有契约:
- registry item schema(条目 schema)
- 第三方注册表如
@acme/button的命名空间语义 - resolver 行为:普通条目、命名空间条目、URL、本地文件四种形态
- 本地文件 add 行为
components.json的 registry 语义
Plate 拥有内容与交付:
apps/www/src/registry/*下的注册表源码- 注册表构建逻辑(技能文档指向
apps/www/scripts/build-registry.mts) apps/www/public/r与apps/www/public/rd下的生成产物- 模板
components.json中配置的@plate命名空间 - 模板同步工具链:
- update-template.sh
- prepare-local-template-registry.mjs
文档用一句话总结边界:shadcn 决定注册表条目如何被解析和安装,Plate 决定注册表里装什么、本地模板同步如何向其供数。文档同时强调:Plate 的注册表构建是自定义的("Plate's registry build is custom"),但目标始终是在条目与 resolver 边界上保持上游对齐。
注册表数据模型:createPlateRegistry 的组装逻辑
Plate 注册表在 registry.ts 中组装。入口函数createPlateRegistry(registry.ts)返回一个标准上游Registry形状的对象:
export function createPlateRegistry(homepage = url): Registry { return { homepage, items: createPlateRegistryItems(), name: 'plate', }; }其中homepage在开发环境为http://localhost:3000,生产环境为https://platejs.org。条目列表由createPlateRegistryItems(registry.ts)拼装,按序合并:初始化条目registryInit、UI 组件、业务组件、blocks(每个 block 被自动注入@plate/plate-ui依赖)、lib、styles、hooks、examples,最后统一经过withEditorComponentTargets处理。
两类细节值得注意:
- 初始化条目(registry.ts):
plate(registry:lib类型,声明dependencies: ['platejs'])负责安装 Plate 主包;plate-ui(registry:style类型)携带 light/dark 两套brand颜色 CSS 变量,并依赖@plate/plate。这正体现了"依赖在源码中显式使用@plate/*"的规则。 - editor 组件的 target 重写(registry.ts):路径中含
components/editor/的文件,若未显式指定target,会被自动改写为@components/editor/前缀,让安装到模板的文件落在编辑器组件目录而非 UI 目录。这是典型的"Plate 特有动态行为放在构建逻辑里,而不是伪造注册表数据模型"——恰是技能文档 Registry Rules 的要求。
注册表条目覆盖registry:ui、registry:block、registry:style、registry:lib、registry:component、registry:page等上游类型,与内嵌的上游快照 registry-shadcn.json 中的条目形状保持一致。测试侧,registry.test.ts 直接对createPlateRegistry().items断言,保证条目集合稳定。
构建与产物:从源码到 public/r
技能文档把apps/www/scripts/build-registry.mts列为 Plate 注册表构建逻辑所在。从 apps/www/package.json 的脚本定义可以看到完整的构建与产物管线:
"build:registry": "tsx --tsconfig ./scripts/tsconfig.scripts.json scripts/build-registry.mts", "r": "concurrently \"pnpm build:registry\" \"pnpm build:tw\"", "rd": "NODE_ENV=development tsx ... scripts/build-registry.mts", "shadcn:build": "shadcn build public/r/registry.json", "shadcn:build:docs": "shadcn build public/r/registry-docs.json", "shadcn:dev": "shadcn build public/rd/registry.json --output public/rd"可以确认的产物结构:
apps/www/public/r/:正式生成的注册表目录,内含按条目命名的 JSON(如ai-kit.json、align-kit.json)以及 block 预览截图(*-light.png/*-dark.png)。当前快照中该目录含 300 余个 JSON 文件。apps/www/public/rd/:由shadcn:dev(开发模式)生成的镜像目录,是--local本地同步的默认数据源。
block 截图由 capture-registry.mts 用 Puppeteer 生成:它读取全部 block 名称,打开http://localhost:3333/view/<block>页面,分别以 light/dark 主题截取 1440x900、2 倍像素比的截图,写入public/r,已存在的截图会自动跳过。next.config.ts中还为/api/registry-source/[name]等路由声明了对./public/r/**/*的静态资源依赖,说明注册表产物同时服务于 API 分发。
依赖与命名空间规则
技能文档的 Registry Rules 是最实操的一部分,逐条解释如下:
- 优先裸名:当上游 shadcn 注册表已存在对应条目时,
registryDependencies使用button、command、popover这类裸名,而不是 Plate 侧的别名。 - 优先上游命名空间语法而非裸 URL:对非默认注册表,若命名空间已能覆盖该场景(如
@acme/button),就用命名空间,不要堆砌原始 URL。 - Plate 自依赖显式写
@plate/*:在注册表源码中,Plate 条目之间的依赖写为@plate/plate、@plate/plate-ui等形式(如上文registryInit中registryDependencies: ['@plate/plate'])。 - 公开生成物必须把自依赖改写为同基址 URL:
public/r下公开的条目 JSON 要把 Plate 自依赖重写为https://platejs.org/r/*.json这类同基址条目 URL,这样"按直接 URL 安装"时,shadcn CLI 能从同一注册表基址递归解析传递性 Plate 条目。 - 保留兼容性输入:旧的 localhost / 绝对 Plate 条目 URL 仍被接受,但仅作为本地文件同步适配器的兼容输入。
@shadcn/*仅作兼容输入:不得写入 Plate 注册表源码或生成产物。- 小条目原则:如果上游没有暴露某个独立小条目,就做一个 Plate 小条目,而不是为了"偷"上游一个大依赖里的一个内部文件而把整个大依赖拖进来。
- 不扩散兼容 hack:不要为了修一个数据问题去新增安装器逻辑——"Fix the registry data or the Plate sync tooling instead"(修注册表数据或同步工具)。
模板安装:shadcn CLI 仍是安装器
Template Rules 的第一条即"模板安装时,shadcn CLI 依然是安装器,Plate 只供数"。模板侧的入口配置在 templates/plate-template/components.json:
{ "$schema": "https://ui.shadcn.com/schema.json", "style": "new-york", "rsc": true, "tsx": true, "registries": { "@plate": "https://platejs.org/r/{name}.json" } }registries字段即 shadcn 的命名空间注册机制:@plate被定义为 Plate 条目的安装入口,{name}.json占位符由 shadcn CLI 在解析@plate/<item>时替换。模板规则还要求:templates/*/components.json必须与 shadcn 注册表语义对齐;生成产物出错时优先改注册表而不是打模板补丁;发明自定义规则前必须与../shadcn输出比对。
update-template.sh 同步流程
update-template.sh 是模板同步的核心脚本,支持basic与ai两种模式:
basic→ 更新templates/plate-template,安装@plate/editor-basicai→ 更新templates/plate-playground-template,安装@plate/editor-ai
registry 前缀的解析逻辑(update-template.sh)精确体现了三种安装形态:
if [[ -n "${TEMPLATE_REGISTRY_URL:-}" ]]; then REGISTRY_PREFIX="${TEMPLATE_REGISTRY_URL%/}" # 显式 URL 覆盖,如 http://127.0.0.1:3210/r elif [[ "$USE_LOCAL" == true ]]; then USE_LOCAL_FILES=true # --local:本地文件模式 REGISTRY_PREFIX="" else REGISTRY_PREFIX="@plate" # 默认:命名空间模式 figet_registry_item据此生成安装参数:本地文件模式直接输出<name>.json(相对文件名,交给 shadcn 的 local-file 流程);URL 前缀输出<prefix>/<name>.json;命名空间前缀输出@plate/<name>。
实际安装命令始终是上游 shadcn CLI:
pnpm dlx shadcn@latest add "$REGISTRY_NAME" -o安装后的补偿逻辑只处理"上游 CLI 的已知副作用",而非改变契约:normalize_relative_ts_imports用 perl 批量去掉本地文件安装重新引入的相对.ts/.tsx导入扩展名(update-template.sh);normalize_react_day_picker_api把calendar.tsx中的table/initialFocus等 API 名对齐到当前依赖版本(update-template.sh)。最后脚本固定仓库工具链版本(biome、typescript、eslint、ultracite 等),执行bun lint:fix与bun typecheck(可用TEMPLATE_SKIP_VERIFY=true跳过)。
--local 模式:JSON 镜像喂给上游本地文件安装
技能文档明确指出:--local使用本地文件模式而非 localhost;本地同步的本质是"从apps/www/public/rd准备一份 JSON 镜像",目的是喂给上游 shadcn 的本地文件安装流程,而不是替代它。
实现上,脚本用mktemp -d创建临时目录,调用 prepare-local-template-registry.mjs 把镜像源(默认apps/www/public/rd,可用TEMPLATE_LOCAL_REGISTRY_SOURCE覆盖)中的每个 JSON 拷贝到临时目录,并重写其中的registryDependencies。重写的核心函数toLocalDependency(prepare-local-template-registry.mjs)做两类转换:
if (dependency.startsWith('@plate/')) { return `${dependency.slice('@plate/'.length)}.json`; // @plate/ai-kit -> ai-kit.json } // 或把 localhost / 127.0.0.1 / platejs.org 的 *.json URL 收敛为 basename这正是"公开生成物用同基址 URL、本地同步用裸文件名"双轨制的落地:公开安装靠 URL 解析,本地安装靠镜像内的相对文件名解析,两者共享同一份rd数据。技能文档最后一条模板规则也呼应了这一点:若某个改动能让公开安装更干净但会破坏本地文件同步,必须明确指出来,并两侧一起修。
当前真实分歧:四条约束而非模式
技能文档的 "Current Divergences" 一节列出了 Plate 相对上游的现存分歧,并强调应把它们当作约束(constraints),而不是可以推广的模式(a pattern to spread):
- Plate 的注册表从
apps/www发布,而不是从上游 shadcn 的基础设施发布; - 本地模板同步使用准备好的 JSON 镜像 + 本地文件模式;
- 模板安装入口使用
components.json中的@plate注册表; - 旧的生成注册表产物中可能仍残留绝对 Plate 自 URL——只能作为兼容性输入看待,不是源码契约。
这四条与仓库事实一一对应:发布端是 apps/www 的shadcn:build管线;本地同步是上述rd镜像机制;入口是components.json的@plate命名空间;而prepare-local-template-registry.mjs对localhost/127.0.0.1/platejs.orgURL 的 basename 收敛,正是处理第 4 条"旧产物残留绝对 URL"的兼容层。
红旗清单:动手前的自检
技能文档最后给出五条"停下来重新评估"的红旗(Red Flags),适合作为任何注册表/模板改动前的检查表:
- 把 Plate 描述成 shadcn CLI 的 fork;
- 发明新的 Plate 专属注册表 schema;
- 用原始 URL 蔓延(raw URL sprawl)替代上游命名空间行为;
- 数据是错的,却靠加更多安装器逻辑来"解决"注册表问题;
- 不检查注册表源码是否是真正的问题源,就手工打模板补丁。
反过来,一份合格的 shadcn parity 改动应当满足:条目形状对齐上游RegistryItem;上游已有模型时直接复用而非另造 Plate 模型;命名用上游习惯与依赖结构;Plate 特有动态行为(如 editor 组件 target 重写)收敛在构建/同步工具中;且任何偏离都写明了原因。
小结
Plate 的 shadcn 对齐机制可以用三层结构概括:契约层由上游 shadcn 拥有——Registry/RegistryItemschema、resolver 行为、命名空间语义与components.json约定,Plate 通过import type { Registry } from 'shadcn/schema'与内嵌的上游快照 registry-shadcn.json 锚定;数据层由 Plate 拥有——apps/www/src/registry 下的条目源码经构建管线输出到public/r(开发镜像public/rd);交付层是@plate命名空间 + shadcn CLI 安装 + update-template.sh/prepare-local-template-registry.mjs 构成的模板同步链路。维护这套系统时,判断顺序始终是:先看上游怎么做的,再决定 Plate 是否需要、以及如何声明一条真实约束下的偏离。
【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考