shadcn/ui Vite Monorepo 模板实战:在 packages/ui 中集中管理组件并用 Turborepo 驱动开发
本文基于 shadcn/ui 仓库中的 vite-monorepo 模板,讲解如何在 Vite + Turborepo 的 monorepo 结构中接入 shadcn/ui:理解 apps/web(应用)与 packages/ui(组件包)的职责划分,掌握通过 shadcn add 命令把组件写入共享包的操作细节,以及包导出映射、Tailwind 内容扫描等让“跨包导入组件 + 样式”真正生效的底层机制。读完你可以直接复制该模板起步,并在多包工程里安全地增删、复用 shadcn/ui 组件。
模板定位与目录结构
仓库中的该模板描述非常直接:这是一个带 shadcn/ui 的 Vite monorepo 模板(见 README)。其目录划分为两层:
apps/web/:实际的 Web 应用,入口为 App.tsx,使用 Vite 开发/构建;packages/ui/:名为@workspace/ui的共享 UI 包,shadcn/ui 组件的“落点”,源码位于packages/ui/src/components下。
从模板根目录的 package.json 与 pnpm-workspace.yaml 可以确认工作区约定:
{
"scripts": {
"build": "turbo build",
"dev": "turbo dev",
"lint": "turbo lint",
"format": "turbo format",
"typecheck": "turbo typecheck"
},
"devDependencies": {
"turbo": "^2.9.18",
"typescript": "~6"
},
"packageManager": "pnpm@10.33.4",
"engines": { "node": ">=20" }
}
# pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"
由此可提炼出适用前提:包管理器必须使用 pnpm(packageManager 字段锁定 pnpm 10.x),Node 版本需 >=20;所有顶层脚本都经由 turbo.json 定义的 Turborepo 任务编排,其中 build 声明了 dependsOn: ["^build"] 与 outputs: ["dist/**"] 缓存,dev 则标记为 cache: false, persistent: true(长驻进程、不参与缓存)。根目录 tsconfig.json 统一了 target: ES2022、moduleResolution: bundler、strict: true 的编译基线。
添加组件:把 shadcn/ui 组件写进共享包
模板 README 给出的核心操作是在 web 应用根目录执行:
pnpm dlx shadcn@latest add button -c apps/web
这条命令有几个要点:
-c apps/web(--cwd)显式指定了 shadcn CLI 的工作目录为 monorepo 根下的apps/web。CLI 会在该目录读取 components.json,这是 shadcn/ui 的“项目配置”,决定了组件写到哪、如何导入。- 执行后组件不会落在
apps/web本地,而是被放置到共享包目录packages/ui/src/components(README 原文如此)。
为什么组件会“跨目录”落盘?关键在于两份 components.json 的别名配置。应用侧 apps/web/components.json 中:
{
"style": "radix-nova",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "../../packages/ui/src/styles/globals.css",
"baseColor": "neutral",
"cssVariables": true
},
"iconLibrary": "lucide",
"aliases": {
"components": "@/components",
"hooks": "@/hooks",
"lib": "@/lib",
"utils": "@workspace/ui/lib/utils",
"ui": "@workspace/ui/components"
}
}
注意 tailwind.css 指向了 ../../packages/ui/src/styles/globals.css——即共享包中的全局样式;而 utils、ui 等别名直接指向 @workspace/ui 包的导出路径。共享包侧 packages/ui/components.json 则是把 components、hooks、lib、utils 全部映射到 @workspace/ui/... 的包级路径。两份配置互相呼应,从源码结构看,这正是 CLI “在 web 应用里运行、却把文件写进 packages/ui”这一行为背后的寻址依据。
此外,style 为 radix-nova、rsc 为 false、cssVariables 为 true,表明该模板生成的是基于 Radix 原语(nova 风格)的非 RSC 纯 Web 组件,主题通过 CSS 变量实现,这也是后文按 d 键切换深色模式得以工作的基础。
使用组件:通过包导出映射跨包导入
README 的第二部分是消费方式——从 ui 包按“文件级”子路径导入:
import { Button } from "@workspace/ui/components/button";
模板的 App.tsx 已经内置了这条示范链路:
import { Button } from "@workspace/ui/components/button"
export function App() {
return (
<div className="flex min-h-svh p-6">
<div className="flex max-w-md min-w-0 flex-col gap-4 text-sm leading-loose">
<div>
<h1 className="font-medium">Project ready!</h1>
<p>You may now add components and start building.</p>
<p>We've already added the button component for you.</p>
<Button className="mt-2">Button</Button>
</div>
<div className="text-muted-foreground font-mono text-xs">
(Press <kbd>d</kbd> to toggle dark mode)
</div>
</div>
</div>
)
}
页面同时提示“已为你添加好 button 组件”,并支持按键 d 切换深色模式(对应 theme-provider.tsx 提供的主题切换能力)。
子路径导出是跨包导入能生效的关键
@workspace/ui/components/button 这类“按文件导出”的写法,依赖 packages/ui/package.json 中的 exports 映射:
"exports": {
"./globals.css": "./src/styles/globals.css",
"./lib/*": "./src/lib/*.ts",
"./components/*": "./src/components/*.tsx",
"./hooks/*": "./src/hooks/*.ts"
}
这意味着 @workspace/ui/components/button 实际解析到 src/components/button.tsx,@workspace/ui/lib/utils 解析到 src/lib/utils.ts,全局样式则以 @workspace/ui/globals.css 暴露。应用侧 apps/web/package.json 通过 "@workspace/ui": "workspace:*" 以 pnpm workspace 协议依赖该私有包("private": true),无需构建发布即可完成本地联调。共享包依赖中包含 react、react-dom 与 zod,与 web 应用保持一致的 React 19 版本区间,避免双 React 实例问题。
让样式真正生效:Vite 别名与 Tailwind 内容扫描
跨包使用 shadcn/ui 组件最容易踩的坑是“组件能跑但样式缺失/类名被树摇掉”。该模板用两处配置解决了它:
- Vite 别名:vite.config.ts 中仅配置了
@→apps/web/src的本地别名,并启用@tailwindcss/vite插件。跨包引用全部走 npm 包名@workspace/ui,不依赖别名,边界清晰。 - Tailwind 的
@source声明:共享包的 globals.css 开头显式声明了扫描范围:
@import "tailwindcss";
@source "../../../apps/**/*.{ts,tsx}";
@source "../../../components/**/*.{ts,tsx}";
@source "../**/*.{ts,tsx}";
从源码结构看,第一段 @source 指向上层 apps/ 目录,让 Tailwind 能够收集应用代码中使用的工具类;cssVariables: true 的主题变量定义在同一份 globals.css 中,应用侧 components.json 又通过 tailwind.css 字段指向它,从而形成“组件与样式同包存放、应用侧按包路径引用”的闭环。
日常开发与构建流程
结合两份 package.json 与 turbo 任务定义,模板给出的完整工作流是:
| 场景 | 位置 | 命令 | 说明 |
|---|---|---|---|
| 启动开发 | 仓库根目录 | pnpm dev |
经 turbo 转发到 apps/web 的 vite(persistent 任务) |
| 添加 shadcn/ui 组件 | 仓库根目录或 apps/web |
pnpm dlx shadcn@latest add <组件名> -c apps/web |
组件写入 packages/ui/src/components |
| 生产构建 | 仓库根目录 | pnpm build |
turbo 按 ^build 拓扑排序执行,apps/web 走 tsc -b && vite build |
| 类型检查 | 仓库根目录 | pnpm typecheck |
两个包各自 tsc --noEmit |
| Lint / 格式化 | 仓库根目录 | pnpm lint / pnpm format |
eslint 与 prettier(含 tailwindcss 插件) |
apps/web 的 build 脚本为 tsc -b && vite build,即先由 TypeScript project references(tsconfig.json、tsconfig.app.json、tsconfig.node.json)完成类型级构建检查,再交给 Vite 打包;@workspace/ui 包本身没有 build 脚本(它作为源码包直接被应用消费,而非先产出 dist),这与 turbo.json 中 build 依赖 ^build 的声明相符——上游包无 build 任务时不会阻塞下游。
小结与扩展方向
这套模板把 shadcn/ui 的“组件代码即资产”理念搬进了标准 monorepo:组件源码统一沉淀在 packages/ui/src/components,通过 exports 子路径映射对外提供文件级导入;应用侧 components.json 负责把 CLI 的写入路径、别名与 Tailwind 入口指回共享包。后续可以在此骨架上:更换 baseColor 调整基础色板、在 @workspace/ui/components 中继续累积业务组件,并借助 pnpm typecheck / pnpm lint 在 turbo 的缓存下获得快速的多包校验。
模板相关文件的完整清单可参考 README、pnpm-workspace.yaml、turbo.json 与 packages/ui/package.json。
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