组件库做到后面,很容易长出一种奇怪的 API。
一个组件有几十个 props,颜色能换,圆角能换,图标能换,某一块 DOM 也能通过插槽替换。维护者努力给每种需求留入口,使用者却还是会在某个瞬间发现,自己真正想改的是组件的骨架。
DOM 顺序要变,交互节奏要变,移动端策略也不一样。这时候继续加一个 variant 或 renderSomething,双方都知道只是在绕开那个不能碰的黑盒。
我做 Sticker UI 时,不想把这条路走到底。它同时提供 npm 包和 source-first registry,不是因为我拿不准怎么发布,而是「稳定依赖」与「完全拥有源码」本来就是两种合理需求。
source-first 不是把代码块放进 README
复制粘贴一段示例,当然也能把源码拿走。但它没有依赖描述,没有稳定入口,也很难知道这段代码对应上游哪个版本。
Sticker UI 的 source-first 路径使用兼容 shadcn 的 registry。使用者安装某个组件以后,拿到的是进入自己仓库的 React 与 Tailwind 源码。它可以参与本地评审、调试和重构,也可以直接接入项目自己的 tokens、状态管理和无障碍约定。
从那一刻起,这份组件不再隔着 node_modules 被使用。团队可以删除不需要的分支,改变 DOM,重写交互,不必等上游为了一个非常具体的产品需求新增开关。
控制权回来以后,维护责任也一起回来了。
这点不能藏。source-first 不是免费的自由,使用者要维护自己的改动,并决定以后是否吸收上游更新。它适合那些组件已经进入产品核心、改造深度大于升级便利的场景,不是所有项目都应该默认选择的答案。
npm 没有过时,它只是解决另一种问题
很多团队根本不想拥有每个按钮和弹窗的源码。他们更需要统一升级、较少的本地代码,以及一个清楚的版本边界。对这些项目,npm 包仍然是更省心的路径。
所以 Sticker UI 没打算用 registry 消灭 npm。两种交付方式共享组件实现和设计 tokens,面向的却是不同选择。
- npm 适合希望跟随版本获得修复和更新的项目。
- registry 适合需要深度定制、愿意长期拥有源码的项目。
真正难的不是把同一个组件提供两个下载按钮,而是阻止它们慢慢变成两个产品。npm 版本已经更新,registry 还停在旧结构;预览里展示一个 API,复制到本地的代码却是另一个 API。这种漂移比只维护一种交付方式更隐蔽。
双路径成立的前提,是生成与一致性检查成为发布流程的一部分。
设计系统也要能够跟着源码搬家
Sticker UI 使用 React、Tailwind CSS 4 和一组可复用 tokens。npm 包提供组件与 tokens,不预先塞进一整份难以追踪的组件 CSS。使用者通过 Tailwind 的 @source 让构建工具生成实际用到的工具类,也可以在应用里覆盖 tokens。
registry 版本沿用同一套类名和 tokens,源码进入项目以后不需要再翻译成另一套主题协议。这样「从 npm 使用」和「把源码拿走」才像同一个设计系统的两种入口,而不是两个长得相似的实现。
复合组件仍然需要稳定公共入口,例如 Dialog.Content、Select.Item。source-first 允许拿到源码,并不等于 npm 路径可以鼓励用户从内部目录随便导入。拥有源码的自由和依赖公共 API 的稳定,是两份不同契约。
预览和文档必须真的跑在公开 API 上
组件库很容易拥有一套看起来很完整、实际已经落后的文档。预览页面还在用旧 props,类型定义已经换了;registry 安装命令能执行,放进干净项目却缺依赖。
我更愿意把组件导出、类型、预览示例和 registry 生成放进同一条验证链。公开组件都能从包入口导入,复合组件子项完整,预览真的调用当前 API,registry 带齐依赖与 tokens,并且能够在干净项目安装。npm tarball 和 registry 也要指向同一个版本来源。
这些检查自动化以后,两种交付路径才不会变成双倍人工核对。否则 source-first 看起来给了用户更多控制,维护者自己却先失去了控制。
Beta 不是谦虚词,而是版本承诺
Sticker UI 目前使用明确的 beta 渠道,因为组件目录、命名和组合方式仍在演进。视觉已经能用,不代表公共 API 与升级路径已经稳定。
我宁愿把这种不确定公开写出来,也不想为了显得成熟,先发一个 1.0 再让使用者承担破坏性变化。source-first 确实让用户更容易修改代码,但它不能替代上游对版本边界的说明。
组件库不需要替用户做完所有决定。npm 可以给出省心的默认路径,registry 则在默认路径不够用时,给出一个正式的出口。
我不想再造一个只能站在外面不断传参数的黑盒。
但我也不想假装所有人都愿意接管源码。
这两种人都真实存在。一个诚实的组件库,应该允许他们选择自己愿意承担哪一份复杂度。