shadcn/ui v4 生成式 styles 目录解析:registry:build 如何产出可运行的样式组合
apps/v4/styles/ 是 shadcn/ui v4 注册表构建流水线的自动生成输出目录:它存放每个 base(base、radix)与 style token(nova、sera、vega…)交叉组合编译后的组件源码,供文档站直接导入。由于整个目录被 gitignore(仅 styles/README.md 被提交),任何新克隆的仓库都需要先运行一次生成命令,才能启动开发服务器。读完全文,你将掌握这条“源码 base × 样式 token → 组合 styles”的生成链路、快速定向构建与完整构建的差异,以及当 styles 缺失时开发服务器如何提前报错。
styles 目录的定位:自动生成的产物,而非手写源码
apps/v4/styles/README.md 开门见山地说明了该目录的三重属性:
- 内容来源:存放 v4 注册表生成的样式(generated styles);
- 生命周期:由注册表构建过程(registry build process)自动产出;
- 版本控制:目录被 gitignore,仓库中只跟踪 README 本身。
这一点在 apps/v4/.gitignore 中有直接印证:
# generated style sources (rebuilt by registry:build, or targeted via
# `pnpm registry:build --style all`). See styles/README.md.
/styles/*
!/styles/README.md
也就是说,styles/<style>/ui/ 下的所有组件文件都不入库,每次构建时重新编译、覆盖。这带来一个明确的开发者约定:不要手工编辑 styles/ 下的任何生成文件——改了也会在下次构建时被覆盖。真正的“事实来源”(source of truth)是手写的 base 注册表与样式 token 文件,registry/README.md 把它们归纳为:
registry/bases/base/、registry/bases/radix/—— 两个手写的 base 注册表(Base UI 与 Radix),各自包含registry.ts以及ui/、lib/、hooks/、blocks/等子目录;registry/styles/style-*.css—— 样式 token 文件(nova、sera、vega…),每个文件定义一种 style 的设计 token;registry/new-york-v4/—— 遗留(legacy)源码注册表,它的registry.ts与组件文件是直接手写并提交的,不参与组合生成。
重新生成 styles:快速定向构建 vs 完整构建
styles README 给出的核心操作只有一条命令——快速定向构建:
pnpm --filter=v4 registry:build --style all
--filter=v4 将脚本定位到 apps/v4/package.json 中的 registry:build 脚本,其真实展开是:
"registry:build": "pnpm --filter=@shadcn/react build && pnpm --filter=@shadcn/helpers build && pnpm --filter=shadcn build && bun run ./scripts/build-registry.mts"
即先依次构建三个 workspace 依赖包(@shadcn/react、@shadcn/helpers、shadcn),再由 bun 执行真正的构建脚本 apps/v4/scripts/build-registry.mts。参数会原样透传给该脚本:
- 定向构建(
--style all、--style base-nova等):只重建本地styles/<style>/ui(及其ui-rtl),适合改动手写 base 组件后的快速本地迭代; - 完整构建(不带任何参数):在定向构建产物之外,还会重新生成所有运行时索引,并导出可安装的注册表 JSON 到
public/r/styles/<style>(README 原话:“A fullpnpm --filter=v4 registry:buildalso regenerates them, along with the installable registry JSON underpublic/r/styles”)。生产部署与提交前都应跑完整构建。
两种构建的差异在 scripts/build-registry.mts 中写得很明确:定向构建会设置 shouldFormatOutput = false,跳过对生成输出的 prettier 格式化以换取速度(因此可能留下未格式化文件与较大的 git diff);而完整构建会重新格式化一切,把所有产物恢复到规范状态。这也是 registry/README.md 强调“提交前跑一次完整构建”的原因。
一次 --style 定向构建到底做了什么
从 build-registry.mts 的实现看,--style <style|all> 走的是 runTargetedStyleBuild 路径,其执行顺序是:
- 参数校验:
assertKnownTarget会检查目标是否为已知 style id(如base-nova、radix-nova、base-sera、new-york-v4等)。这里有一个易踩的坑:--style new-york-v4会被明确拒绝,因为它属于遗留源码注册表而非生成组合,正确做法是改用--registry new-york-v4。未知目标则直接抛出错误并列出全部合法 id。 - 只加载相关 base:
buildBases接受targetStyleNames过滤,定向构建base-nova时不会把其他 base 的源文件全部读进内存(源码注释见 L954-L964)。 - 组合生成临时注册表:每个 base × style 组合先在
registry/<style>/(临时目录,构建后清理)中落盘一份组合 registry,其中.ts/.tsx文件会经过transformStyle做样式 token 替换,并用createStyleMap(解析自registry/styles/style-<style>.css)做映射。 - 拷贝到持久目录:
copyUIToStyles将registry/<style>/ui同步到styles/<style>/ui,同步过程中重写导入路径(如@/registry/<style>/ui/→@/styles/<style>/ui/、@/registry/<style>/lib/→@/lib/),并对.tsx文件应用 icon 占位符替换,最后格式化落盘。 - 生成 RTL 变体:
buildRtlStyles只为base-nova、radix-nova、aria-nova三个组合生成styles/<style>/ui-rtl(判定函数shouldGenerateRtlStyles,见 L371-L373),其余组合的ui-rtl会被清理删除。 - 清理临时文件:
registry/<style>/目录与registry-<style>.json被 rimraf 移除,仓库中不会残留。
生成组合的集合由 STYLE_COMBINATIONS 定义:BASES(base、radix 等)与 registry/styles.tsx 中 STYLES 列表的笛卡尔积,命名为 ${base}-${style}。当前 STYLES 列表共 8 个 token:vega(干净中性)、nova(收紧留白)、maia(圆润宽间距)、lyra(方正、适合等宽字体)、mira(紧凑界面)、luma(柔和发光感)、sera(编辑排版感)、rhea(类 luma 但紧凑)。每个 base × 每个 token 就是一个可生成的 style 目录,例如 styles/base-nova/ui/button.tsx。
此外,定向构建还依赖一份位于 node_modules/.cache/build-registry/ 的转换缓存(TRANSFORM_CACHE_ROOT):以“转换实现 + 手写 registry + 样式 CSS + 源文件内容”的哈希为键,命中即直接读取上次已格式化的转换结果,避免重复 transform,这是 --style all 能做到“fast targeted build”的关键。
为什么必须先生成:开发服务器的 fail-fast 守卫
styles 缺失并非只会让页面白屏——Next.js 会在启动阶段直接拦截。apps/v4/next.config.mjs 中有一段针对开发环境的专项检查:
- 解析被跟踪的组件映射文件
registry/__components__.tsx中所有@/styles/<name>/的引用(该映射本身入库,而它引用的 styles 不入库); - 逐一检查本地
styles/<name>/ui是否存在; - 一旦发现缺失,启动即抛错:
Generated styles are missing or stale (base-nova, ...). Run `pnpm --filter=v4 registry:build --style all` once, then restart the dev server.
注释解释了动机:如果未生成就启动,Turbopack 会在编译 /docs 时遇到数百个 module-not-found 错误,开发服务器直接卡死;提前失败并给出操作指令是更好的体验。这条错误信息本身就是 styles README 中那条命令的实际出处——两者互相印证。
与可安装注册表 JSON 的关系
完整构建还负责把每个 style 导出为网站与 CLI 可安装的消费端产物:
buildRegistryJsonFile将组合 registry 序列化为public/r/styles/<style>/registry.json(遗留 style 会额外注入共享 font items,保证 CLI 能输出font-*.json);- 随后
buildRegistry调用packages/shadcn的 CLI(node ../../packages/shadcn/dist/index.js build registry-<style>.json --output public/r/styles/<style>)完成逐条拆分导出; - 这些 JSON 目录同样是 gitignore 的(见 .gitignore),仅
index.json与冻结的 v3 样式default/、new-york/例外入库。
对本地开发者而言,这两类产物的分工可以概括为:styles/ 服务于文档站自身的渲染(代码被文档应用直接 import,next.config.mjs 的 outputFileTracingIncludes 也把 ./styles/**/* 纳入打包追踪);public/r/styles/ 服务于对外分发(shadcn add 等安装链路从这些 JSON 拉取代码)。
实用要点小结
- 新克隆仓库后的第一步:
pnpm --filter=v4 registry:build --style all,然后(如刚启动过失败)重启 dev server; - 日常迭代:改动手写 base 组件后跑
--style <name>或--style all;改 registry/块元数据跑--indexes;改 CLI 安装内容跑--registry <name>;标志可组合使用; - 提交与部署前:跑一次不带参数的完整
registry:build,它会重新格式化所有生成物并导出public/r/styles; - 红线:
styles/下的一切都是生成物,不要提交、不要手改;--style new-york-v4是非法目标,请用--registry new-york-v4。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00