shadcn/ui TanStack Start 单仓模板:用 pnpm Workspace + Turborepo 搭建多包 UI 工程
【免费下载链接】uiA set of beautifully-designed, accessible components and a code distribution platform. Works with your favorite frameworks. Open Source. Open Code.项目地址: https://gitcode.com/GitHub_Trending/ui/ui
shadcn/ui 官方仓库在templates/下提供了一批开箱即用的项目骨架,其中start-monorepo模板将 TanStack Start 应用与共享 UI 包组织成一个 pnpm + Turborepo 驱动的 monorepo:应用代码位于apps/web,组件库代码位于packages/ui,两者通过workspace:*协议与路径别名联动。读完本文,你将掌握该模板的完整目录结构、添加/引用 shadcn/ui 组件的标准流程,以及components.json、exports映射、Tailwind@source等关键配置在单仓中如何协同工作。
模板定位与目录结构
start-monorepo是一个面向 TanStack Start(Vite 生态的 React 全栈框架)的单仓模板,其核心设计目标只有一个:让 shadcn/ui 组件不再散落在应用包里,而是集中托管在独立的packages/ui包中,供工作区内多个应用复用。
从仓库实际文件看,模板采用经典的两级结构:
templates/start-monorepo/ ├── apps/ │ └── web/ # TanStack Start 应用(Vite + React 19) │ ├── src/routes/ # 路由文件(__root.tsx、index.tsx) │ ├── components.json # 应用侧 shadcn 配置 │ ├── tsconfig.json # 定义 @workspace/ui/* 路径映射 │ └── vite.config.ts ├── packages/ │ └── ui/ # @workspace/ui 共享包 │ ├── src/ │ │ ├── components/ # shadcn 组件落盘目录 │ │ ├── hooks/ │ │ ├── lib/ │ │ └── styles/globals.css │ ├── components.json # 包侧 shadcn 配置 │ └── package.json # 定义 exports 导出映射 ├── package.json # 根脚本(turbo build/dev/lint/...) ├── pnpm-workspace.yaml └── turbo.json工作区声明与运行环境约束
工作区成员由 pnpm-workspace.yaml 声明:
packages: - "apps/*" - "packages/*" allowBuilds: esbuild: true lightningcss: true unrs-resolver: true msw: falseapps/*与packages/*两个 glob 决定了哪些目录被视为独立包。allowBuilds则显式列出了允许执行安装后构建脚本的依赖(esbuild、lightningcss 等),这是较新 pnpm 版本收紧原生构建依赖时的白名单机制。
根 package.json 定义了运行前提与顶层命令:
packageManager: pnpm@10.33.4、engines.node >= 20:模板锁定 pnpm 10 且要求 Node 20 及以上;- 五个顶层脚本
build/dev/lint/format/typecheck全部委托给 Turborepo 调度(turbo build、turbo dev…); pnpm.onlyBuiltDependencies中额外声明了esbuild与lightningcss,与工作区配置呼应。
Turborepo 任务图
turbo.json 定义了跨包的任务编排规则,其中有几处值得注意:
"build": { "dependsOn": ["^build"], "inputs": ["$TURBO_DEFAULT$", ".env*"], "outputs": [".output/**"] }, "dev": { "cache": false, "persistent": true }build任务的dependsOn: ["^build"]表示构建web之前会先构建其上游依赖包(@workspace/ui),^是 Turborepo 的"依赖包优先"语法;dev任务标记为persistent: true且cache: false,因为 Vite dev server 是常驻进程,不适合缓存;outputs: [".output/**"]对应 TanStack Start 产物目录,命中缓存条件时可跳过重复构建。
添加组件:在应用目录下执行 shadcn CLI
模板 README 给出的核心操作是:在web应用根目录执行 shadcn CLI 的add命令,并用-c参数指定目标包:
pnpm dlx shadcn@latest add button -c apps/web这条命令的实际效果是:CLI 读取apps/web/components.json中的配置(尤其是aliases.ui指向@workspace/ui/components),将生成的组件文件落盘到packages/ui/src/components目录,而不是应用自己的src目录。这正是该模板"组件集中托管"设计的关键点——-c apps/web指定的是执行上下文所在的应用包,而组件物理位置由别名配置重定向到共享 UI 包。
UI 包的导出契约:exports映射
组件为什么能通过@workspace/ui/components/button这样的子路径被 import?答案在 packages/ui/package.json 的exports字段:
"exports": { "./globals.css": "./src/styles/globals.css", "./lib/*": "./src/lib/*.ts", "./components/*": "./src/components/*.tsx", "./hooks/*": "./src/hooks/*.ts" }四个子路径把src下的每类资产暴露为包级入口:CSS、工具函数、组件与 hooks 各自成体系。同时web应用通过依赖声明建立工作区关联:
"@workspace/ui": "workspace:*"workspace:*是 pnpm 的本地包引用协议,表示"始终解析到工作区内同名的@workspace/ui包",无需版本号、无需发布。
组件的两条引用路径:包名与路径别名
模板中组件其实有两条可达路径,理解它们的差异有助于排查导入问题:
包名路径(运行时/打包层):
@workspace/ui/components/button经由exports映射解析到packages/ui/src/components/button.tsx。README 给出的标准用法即为此路径:import { Button } from "@workspace/ui/components/button";TS 路径别名(类型检查层):apps/web/tsconfig.json 中额外声明了:
"paths": { "@/*": ["./src/*"], "@workspace/ui/*": ["../../packages/ui/src/*"] }这让 TypeScript 直接把
@workspace/ui/*映射到src源码文件,配合 vite.config.ts 中的resolve: { tsconfigPaths: true },Vite 在开发期也能按同一套别名解析模块。两条路径最终指向同一份源码,保证类型提示与运行时行为一致。
两份 components.json 的职责分工
模板存在两份 shadcn 配置文件,分工明确:
应用侧apps/web/components.json:
{ "style": "radix-nova", "rsc": false, "tsx": true, "tailwind": { "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" } }包侧packages/ui/components.json 结构相同,但tailwind.css指向包内相对路径src/styles/globals.css,aliases各项统一指向@workspace/ui/*。
关键配置项解读:
| 配置项 | 取值 | 作用 |
|---|---|---|
style | radix-nova | 指定组件的视觉/交互风格基线 |
rsc | false | 明确这是非 RSC 环境(TanStack Start 客户端路由应用),CLI 不会生成 React Server Component 相关代码 |
tailwind.css | 包内globals.css | 告诉 CLI 主题 CSS 变量的写入位置——注意应用侧写的是跨包相对路径,确保新组件的样式变量统一汇入 UI 包 |
tailwind.baseColor | neutral | 生成 CSS 变量时采用的基础色板 |
iconLibrary | lucide | 组件内图标统一使用 lucide 图标库 |
aliases.ui | @workspace/ui/components | 决定shadcn add的落盘目标,这是整个模板组件集中托管的枢纽 |
Tailwind 跨包扫描:@source指令
组件集中在packages/ui、页面代码在apps/web,Tailwind 如何同时识别两侧的工具类使用?答案在 packages/ui/src/styles/globals.css:
@import "tailwindcss"; @source "../../../apps/**/*.{ts,tsx}"; @source "../../../components/**/*.{ts,tsx}"; @source "../**/*.{ts,tsx}";Tailwind CSS v4 默认只扫描样式文件所在包内的源码;这里通过三条@source指令显式把扫描范围扩展到工作区应用目录(apps/**)与 UI 包自身(../**),保证无论从哪个包写的 className 都能被编译进最终的样式产物。
样式如何进入应用:globals.css?url引入
最后闭环的一环是 CSS 的分发。TanStack Start 的根路由 apps/web/src/routes/__root.tsx 做了这样的处理:
import appCss from "@workspace/ui/globals.css?url" export const Route = createRootRoute({ head: () => ({ links: [ { rel: "stylesheet", href: appCss, }, ], // ... }), }):?url后缀是 Vite 的资源导入约定:不把 CSS 内容内联进 JS,而是让 Vite 处理该样式文件并输出一张独立的样式资源 URL,再以<link rel="stylesheet">的形式注入根文档的<head>。这意味着:UI 包的单一globals.css就是全站唯一的样式入口,shadcn CLI 写入的主题变量、Tailwind 基础层与所有组件样式都通过这一条链路进入浏览器。
日常开发流程小结
在模板中完成一次"加组件 → 用组件"的完整流程如下:
- 在仓库根目录执行
pnpm install安装工作区依赖; - 执行
pnpm dlx shadcn@latest add button -c apps/web,组件落盘至packages/ui/src/components/; - 在任意路由或组件中
import { Button } from "@workspace/ui/components/button"; - 运行
pnpm dev(等价于turbo dev,由 apps/web/package.json 映射为vite dev --port 3000)启动应用,样式经根路由自动加载。
此外,根级还有lint/format/typecheck任务,Turborepo 会按依赖图在所有apps/*与packages/*成员上分别执行各自的eslint、prettier --write "**/*.{ts,tsx}"与tsc --noEmit脚本,无需逐包手工触发。
小结
start-monorepo模板的价值在于把三件事一次做对:用 pnpm workspace 声明包边界、用 Turborepo 编排构建与常驻进程、用components.json的aliases.ui加exports映射把 shadcn 组件固化为工作区共享资产。理解了 README 中那两行命令背后@workspace/ui/*别名、:?url样式导入与@source跨包扫描的完整链路,你就能在自有项目中复用同一套单仓组织方式,并针对自己的框架(Vite、Astro、React Router 等)调整对应的接入点。
【免费下载链接】uiA set of beautifully-designed, accessible components and a code distribution platform. Works with your favorite frameworks. Open Source. Open Code.项目地址: https://gitcode.com/GitHub_Trending/ui/ui
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考