117k

Monorepo

在 monorepo 中使用 shadcn/ui 组件和 CLI。

直到现在,在 monorepo 中使用 shadcn/ui 还是有点麻烦。你可以使用 CLI 添加组件,但你必须管理组件安装的位置,并手动修正导入路径。

通过 CLI 中新增的 monorepo 支持,我们让在 monorepo 中使用 shadcn/ui 变得轻松许多。

CLI 现在能够理解 monorepo 结构,会将组件、依赖和注册依赖安装到正确的路径,并帮你处理导入。

快速入门

创建一个新的 monorepo 项目

要创建一个新的 monorepo 项目,运行 init 命令并带上 --monorepo 标志。

pnpm dlx shadcn@latest init --monorepo

然后选择你想使用的模板。

? Select a template ›
   Next.js
    Vite
    TanStack Start
    React Router
    Astro

这将创建一个包含两个工作区 webui 的 monorepo 项目,并使用 Turborepo 作为构建系统。

一切都已为你设置好,所以你可以开始向项目添加组件了。

注意:该 monorepo 使用 React 19 和 Tailwind CSS v4。

向项目添加组件

要向项目添加组件,请在你的应用路径下运行 add 命令。

cd apps/web
pnpm dlx shadcn@latest add [COMPONENT]

CLI 会自动判断你添加的组件类型,并将正确的文件安装到正确的路径。

例如,如果你运行 npx shadcn@latest add button,CLI 会将按钮组件安装到 packages/ui 下,并更新 apps/web 中组件的导入路径。

如果你运行 npx shadcn@latest add login-01,CLI 会将 buttonlabelinputcard 组件安装到 packages/ui 中,而 login-form 组件则安装到 apps/web/components

导入组件

你可以从 @workspace/ui 包中导入组件,写法如下:

import { Button } from "@workspace/ui/components/button"

你也可以从 @workspace/ui 包中导入 hooks 和工具函数。

import { useTheme } from "@workspace/ui/hooks/use-theme"
import { cn } from "@workspace/ui/lib/utils"

文件结构

当你创建一个新的 monorepo 项目时,CLI 会创建如下文件结构:

apps
└── web         # 你的应用代码放这里。
    ├── app
    │   └── page.tsx
    ├── components
    │   └── login-form.tsx
    ├── components.json
    └── package.json
packages
└── ui          # 你的组件和依赖安装于此。
    ├── src
    │   ├── components
    │   │   └── button.tsx
    │   ├── hooks
    │   ├── lib
    │   │   └── utils.ts
    │   └── styles
    │       └── globals.css
    ├── components.json
    └── package.json
package.json
turbo.json

要求

  1. 每个工作区都必须有一个 components.json 文件。package.json 文件告诉 npm 如何安装依赖,而 components.json 文件则告诉 CLI 如何以及在哪里安装组件。

  2. components.json 文件必须正确定义该工作区的别名。这会告诉 CLI 如何导入组件、hooks、工具等。

apps/web/components.json
{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "radix-nova",
  "rsc": true,
  "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"
  }
}
packages/ui/components.json
{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "radix-nova",
  "rsc": true,
  "tsx": true,
  "tailwind": {
    "config": "",
    "css": "src/styles/globals.css",
    "baseColor": "neutral",
    "cssVariables": true
  },
  "iconLibrary": "lucide",
  "aliases": {
    "components": "@workspace/ui/components",
    "utils": "@workspace/ui/lib/utils",
    "hooks": "@workspace/ui/hooks",
    "lib": "@workspace/ui/lib",
    "ui": "@workspace/ui/components"
  }
}
  1. 确保两个 components.json 文件中的 styleiconLibrarybaseColor 保持一致。

  2. 对于 Tailwind CSS v4,components.json 中的 tailwind 配置需留空。

遵循这些要求后,CLI 就能够将 ui 组件、blocks、libs 和 hooks 安装到正确的路径,并为你处理导入。

使用 package.json#imports

对于使用包导入且不依赖 tsconfig.json paths 的 monorepo,请使用:

  • 为每个工作区内的文件使用本地 #... 别名
  • 使用工作区包的 exports 来处理共享导入,例如 @workspace/ui/components

例如,应用工作区可以使用本地包导入:

apps/web/package.json
{
  "name": "web",
  "private": true,
  "type": "module",
  "imports": {
    "#components/*": "./src/components/*.tsx",
    "#lib/*": "./src/lib/*.ts",
    "#hooks/*": "./src/hooks/*.ts"
  },
  "dependencies": {
    "@workspace/ui": "workspace:*"
  }
}
apps/web/components.json
{
  "aliases": {
    "components": "#components",
    "ui": "@workspace/ui/components",
    "lib": "#lib",
    "hooks": "#hooks",
    "utils": "@workspace/ui/lib/utils"
  }
}

而共享的 UI 包可以通过 exports 暴露其安装目标:

packages/ui/package.json
{
  "name": "@workspace/ui",
  "private": true,
  "type": "module",
  "imports": {
    "#components/*": "./src/components/*.tsx",
    "#lib/*": "./src/lib/*.ts",
    "#hooks/*": "./src/hooks/*.ts"
  },
  "exports": {
    "./globals.css": "./src/styles/globals.css",
    "./components/*": "./src/components/*.tsx",
    "./lib/*": "./src/lib/*.ts",
    "./hooks/*": "./src/hooks/*.ts"
  }
}
packages/ui/components.json
{
  "aliases": {
    "components": "#components",
    "ui": "#components",
    "lib": "#lib",
    "hooks": "#hooks",
    "utils": "#lib/utils"
  }
}

在这个设置中:

  • 从应用添加到共享 UI 包的文件会通过 @workspace/ui/... 路由
  • packages/ui 内添加的文件使用包本地的 #... 别名
  • 共享包必须导出任何被另一个工作区引用的路径

关于框架特定的包导入配置,请参阅 package imports 指南