1. 这一篇要解决什么问题
前一篇把 web、admin、api 跑起来了,这一篇继续补前端样式基础设施。
我们的目标很明确:让 apps/web、apps/admin 和 packages/ui 一起用上 Tailwind CSS,并确认共享包里的类名在两个前端子站都能正常生效。
这里有个关键前提:@repo/ui 直接导出源码 TSX,我们选择的方式不是先构建成产物再给应用消费。
这就决定了最合适的接入方式不是在根目录做一套统一编译,而是让每个前端应用各自负责 Tailwind 编译,同时显式扫描共享包源码。
具体的做法如下:
11. 在当前项目下如何引入 tailwindcss,让多个子站和共享包可以一起使用?给我建议22. 执行你的建议,并基于 tailwindcss 创建一个组件用于验证是否生效
2. 先在 workspace 里统一 Tailwind 版本
既然这个仓库已经在 pnpm-workspace.yaml 里用 catalog 管理公共依赖版本,这一轮也继续沿用同一个做法。
在根目录的 pnpm-workspace.yaml 里补两项:
1catalog:2tailwindcss: ^4.1.153"@tailwindcss/postcss": ^4.1.15
这里放进 catalog 之后,后面两个前端子站都可以直接用 catalog: 引用版本,我们不用各写一份具体版本号。
然后分别把依赖补进两个应用:
1{2"devDependencies": {3"tailwindcss": "catalog:",4"@tailwindcss/postcss": "catalog:"5}6}
1{2"devDependencies": {3"tailwindcss": "catalog:",4"@tailwindcss/postcss": "catalog:"5}6}
这一步只让两个前端应用具备编译 Tailwind 的能力,packages/ui 依然保持源码导出,不需要自己单独跑一套 Tailwind 构建。
3. 在 web 和 admin 中接入 Tailwind v4
接下来改两个前端应用自己的编译入口。
先各自补一份 PostCSS 配置:
1export default {2plugins: {3'@tailwindcss/postcss': {},4},5}
1export default {2plugins: {3'@tailwindcss/postcss': {},4},5}
然后把两个应用的全局样式入口改成 Tailwind v4 的写法:
1@import "tailwindcss";2@import "@repo/ui/theme.css";
1@import "tailwindcss";2@import "@repo/ui/theme.css";
4. 共享主题
光把扫描路径接上还不够,还得让共享包里真的有 Tailwind 类名和共享主题变量,这样才能真正生效。
这一步在 packages/ui 里补两样东西就够了。
先加共享主题变量:
01@source "./**/*.{ts,tsx}";0203@theme {04--color-brand-50: #f3f7ff;05--color-brand-100: #dce8ff;06--color-brand-500: #4f7cff;07--color-brand-600: #315ee8;08--color-brand-700: #2749bb;09--color-surface-strong: rgba(15, 23, 42, 0.78);10--shadow-card: 0 24px 60px rgba(15, 23, 42, 0.18);11--radius-card: 1.5rem;12}
这里的 @source "./**/*.{ts,tsx}"; 是关键。
它会以 theme.css 自己所在的目录为基准,把 packages/ui/src 下面的 TS、TSX 文件显式加入 Tailwind 扫描范围。
这样 web 和 admin 只需要 @import "@repo/ui/theme.css";,就能顺带把 tailwind-demo.tsx 这类共享组件里的类名一起扫进去,不用在两个应用里重复写共享包扫描路径。
再加一个最小演示组件:
01type TailwindDemoProps = {02appName: string03}0405const features = [06'Shared Tailwind utilities',07'Shared theme tokens',08'Rendered from @repo/ui',09]1011export function TailwindDemo({ appName }: TailwindDemoProps) {12return (13<section className="w-full max-w-4xl rounded-card border border-white/10 bg-[linear-gradient(135deg,rgba(79,124,255,0.16),rgba(255,255,255,0.04))] p-6 shadow-card backdrop-blur md:p-8">14<div className="flex flex-col gap-6 lg:flex-row lg:items-end lg:justify-between">15<div className="space-y-4">16<span className="inline-flex w-fit items-center rounded-full border border-brand-500/20 bg-brand-50 px-3 py-1 text-xs font-semibold tracking-[0.24em] text-brand-700 uppercase">17shared ui18</span>19<div>20<h2 className="text-3xl font-semibold text-white md:text-4xl">21Tailwind runs in {appName} and still styles shared TSX.22</h2>23</div>24</div>25<div className="grid gap-3 sm:grid-cols-3">26{features.map((feature) => (27<div28key={feature}29className="rounded-2xl border border-white/10 bg-white/5 px-4 py-3 text-sm font-medium text-slate-100 shadow-sm shadow-black/10"30>31{feature}32</div>33))}34</div>35</div>36</section>37)38}
最后别忘了把样式文件也导出去:
1{2"exports": {3"./theme.css": "./src/theme.css",4"./*": "./src/*.tsx"5}6}
6. 让两个 Next 应用消费共享包
@repo/ui 现在既有 TSX,又有 theme.css,两个前端应用还需要再补一处配置:开启共享包转译。
1/** @type {import('next').NextConfig} */2const nextConfig = {3transpilePackages: ['@repo/ui'],4}56export default nextConfig
1/** @type {import('next').NextConfig} */2const nextConfig = {3transpilePackages: ['@repo/ui'],4}56export default nextConfig
然后在两个站点的首页把演示组件引入进来:
1import { TailwindDemo } from '@repo/ui/tailwind-demo'23export default function Home() {4return <TailwindDemo appName='web' />5}
1import { TailwindDemo } from '@repo/ui/tailwind-demo'23export default function Home() {4return <TailwindDemo appName='admin' />5}
这一步的验证意义很直接:TailwindDemo 里的类名都写在 packages/ui/src/tailwind-demo.tsx,只要两个站点都渲染正确,就说明共享包源码已经被应用自己的 Tailwind 编译流程覆盖到了。
7. 验证
建议按这个顺序检查:
1pnpm install2pnpm --filter @repo/ui check-types3pnpm --filter web check-types4pnpm --filter admin check-types5pnpm lint6pnpm build
然后分别启动两个前端:
1pnpm --filter web dev2pnpm --filter admin dev
接着打开:
http://localhost:3005http://localhost:3006
然后我们就可以验证:
- 两个站点都渲染出了共享演示组件
packages/ui里的 Tailwind 类名已经生效theme.css里的共享主题变量同时作用到了两个站点- 终端和浏览器控制台里没有 PostCSS、Tailwind、包转译相关报错
如果组件渲染出来了但没有样式,优先排查两处:
@source路径有没有写对- 共享组件里有没有大量动态拼接类名,导致 Tailwind 扫描不到
8. 总结
我们以 共享 UI 为例,建立了一条后面可以一直复用的规则:
- 共享包继续保持源码导出
- 每个前端应用各自负责 Tailwind 编译
- 共享 UI 的类名通过
@source被显式纳入扫描范围 - 共享设计 token 通过
theme.css被两个站点一起消费
后面继续往 packages/ui 里加真正的业务组件,基本就沿着这套接法往前推就够了。