进阶指南
多模板项目
Monorepo 场景配置、pnpm workspace 最佳实践、共享组件和样式、类型共享与多包构建策略
在 Monorepo 中管理多个模板包,复用组件、样式和类型,同时保持各包独立构建。
Monorepo 场景
场景 1:多机器人共享模板
workspace/
├── packages/
│ ├── shared/ # 共享包
│ │ ├── templates/ # 共享模板
│ │ ├── components/ # 通用组件
│ │ ├── styles/ # 共享样式
│ │ └── types/ # 类型定义
│ ├── bot-a/ # 机器人 A
│ │ └── karin.template.ts
│ └── bot-b/ # 机器人 B
│ └── karin.template.ts
├── pnpm-workspace.yaml
└── package.json场景 2:按功能模块拆分
workspace/
├── packages/
│ ├── core/ # 核心模板
│ ├── games/ # 游戏相关
│ ├── social/ # 社交功能
│ └── admin/ # 管理后台
└── pnpm-workspace.yaml场景 3:多租户 SaaS
workspace/
├── packages/
│ ├── base/ # 基础模板
│ ├── tenant-a/ # 租户 A 定制
│ └── tenant-b/ # 租户 B 定制
└── pnpm-workspace.yamlpnpm Workspace 配置
初始化 Workspace
packages:
- 'packages/*'
- 'apps/*'根 package.json
{
"name": "karin-templates-workspace",
"private": true,
"scripts": {
"dev": "pnpm --filter \"./packages/*\" dev",
"build": "pnpm --filter \"./packages/*\" build",
"template:bot-a": "pnpm --filter bot-a template",
"template:bot-b": "pnpm --filter bot-b template"
},
"devDependencies": {
"@karinjs/template-react": "workspace:*",
"typescript": "^5.0.0"
}
}共享模板配置
共享包结构
packages/shared/
├── package.json
├── templates/
│ ├── common/
│ │ └── card/
│ │ └── index.tsx
│ └── style.css
├── components/
│ ├── Avatar.tsx
│ └── Badge.tsx
├── styles/
│ ├── tokens.css
│ └── utilities.css
└── types/
└── index.ts共享包 package.json
{
"name": "@workspace/shared",
"version": "1.0.0",
"type": "module",
"exports": {
".": "./index.ts",
"./components": "./components/index.ts",
"./types": "./types/index.ts",
"./styles/*": "./styles/*"
},
"dependencies": {
"@karinjs/template-react": "workspace:*",
"@heroui/react": "^3.0.0",
"react": "^18.0.0"
}
}消费包配置
export default ({
: {
: '../../shared/templates', // 引用共享模板
: '../../shared/assets'
},
: {
: 5181 // 避免端口冲突
},
: ['../../shared/styles/tokens.css', '../../shared/styles/utilities.css']
})类型引用
import { } from '@karinjs/template-react'
import type { CommonCardData } from '@workspace/shared/types'
const = (import.meta.)
// 类型安全调用
async function (: CommonCardData) {
await ('common/card', )
}共享组件
定义共享组件
import React from 'react'
export interface AvatarProps {
: string
: string
?: number
}
export const : React.<AvatarProps> = ({ , , = 48 }) => (
< ={} ={} ="rounded-full object-cover" ={{ : , : }} />
)export { Avatar } from './Avatar'
export { Badge } from './Badge'
export { Card } from './Card'在模板中使用
import { , type } from '@karinjs/template-react'
import { , } from '@workspace/shared/components'
interface ProfileData {
: string
: string
: number
}
const = ({ }: <ProfileData>) => (
< ="flex items-center gap-4 p-6 bg-surface rounded-xl">
< ={.} ={.} ={64} />
<>
< ="text-xl font-bold">{.}</>
<>Lv.{.}</>
</>
</>
)
export default ({
: '用户资料',
:
})组件库包
packages/components/
├── package.json
├── src/
│ ├── Avatar/
│ │ ├── index.tsx
│ │ └── Avatar.stories.tsx
│ ├── Badge/
│ │ ├── index.tsx
│ │ └── Badge.stories.tsx
│ └── index.ts
└── tsconfig.json{
"name": "@workspace/components",
"version": "1.0.0",
"type": "module",
"exports": {
".": "./src/index.ts",
"./*": "./src/*/index.tsx"
},
"scripts": {
"storybook": "storybook dev -p 6006"
},
"devDependencies": {
"@storybook/react": "^7.0.0",
"@storybook/react-vite": "^7.0.0"
}
}共享样式
样式包结构
packages/styles/
├── package.json
├── tokens.css # 设计 token
├── base.css # 基础样式
├── utilities.css # 工具类
└── animations.css # 动画设计 token
@layer tokens {
:root {
/* 颜色 */
--color-brand: oklch(0.6 0.2 260);
--color-success: oklch(0.7 0.15 145);
--color-warning: oklch(0.75 0.15 85);
--color-error: oklch(0.6 0.2 25);
/* 间距 */
--spacing-xs: 0.25rem;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 1.5rem;
--spacing-xl: 2rem;
/* 圆角 */
--radius-sm: 0.25rem;
--radius-md: 0.5rem;
--radius-lg: 1rem;
/* 阴影 */
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
}
}工具类
@layer utilities {
.text-brand {
color: var(--color-brand);
}
.bg-brand {
background-color: var(--color-brand);
}
.gap-xs {
gap: var(--spacing-xs);
}
.gap-sm {
gap: var(--spacing-sm);
}
.gap-md {
gap: var(--spacing-md);
}
.rounded-custom {
border-radius: var(--radius-md);
}
.shadow-custom {
box-shadow: var(--shadow-md);
}
}在模板中引用
@import 'tailwindcss';
@import '@karinjs/template-react/styles';
@import '@workspace/styles/tokens.css';
@import '@workspace/styles/utilities.css';
@import '@workspace/styles/animations.css';
@source '../../../shared/templates';类型共享
类型包结构
packages/types/
├── package.json
├── src/
│ ├── template.ts # 模板数据类型
│ ├── api.ts # API 类型
│ └── index.ts
└── tsconfig.json定义类型
/** 用户资料数据 */
export interface UserProfile {
: string
: string
: string
: number
: number
}
/** 排行榜数据 */
export interface Leaderboard {
: string
: <{
: number
: UserProfile
: number
}>
}
/** 卡片基础数据 */
export interface CardData {
: string
?: string
?: string
}类型增强
import type { UserProfile, Leaderboard } from './template'
declare module '@karinjs/template-react/registry-types' {
interface ProjectRegistry {
'user/profile': import('@karinjs/template-react').TemplateDef<UserProfile>
'user/leaderboard': import('@karinjs/template-react').TemplateDef<Leaderboard>
}
}在模板中使用
import { , type } from '@karinjs/template-react'
type = {
: string
: number
}
const = ({ }: <>) => (
<>
<>{.}</>
<>Level {.}</>
</>
)
export default ({
:
})多包构建策略
并行构建
{
"scripts": {
"build": "pnpm --parallel --filter \"./packages/*\" build"
}
}依赖顺序构建
{
"scripts": {
"build": "pnpm --filter \"@workspace/types\" build && pnpm --filter \"@workspace/components\" build && pnpm --filter \"./packages/bot-*\" build"
}
}Turborepo 集成
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", "lib/**"]
},
"dev": {
"cache": false,
"persistent": true
},
"template": {
"cache": false
}
}
}{
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev --parallel",
"template": "turbo run template --filter bot-a"
}
}独立构建配置
每个包使用独立的构建插件:
import { } from 'vite'
import { } from '@karinjs/template-react/plugin'
export default ({
: [()],
: {
: 'lib',
: {
: 'src/index.ts',
: ['es']
}
}
})开发工作流
同时启动多个面板
{
"scripts": {
"dev:all": "concurrently \"pnpm --filter bot-a template\" \"pnpm --filter bot-b template\"",
"dev:bot-a": "pnpm --filter bot-a template",
"dev:bot-b": "pnpm --filter bot-b template"
},
"devDependencies": {
"concurrently": "^8.0.0"
}
}监听共享包变更
export default ({
: {
: {
: {
// 监听共享包变更
: ['!**/node_modules/@workspace/**']
}
}
}
})热重载共享组件
使用 pnpm link 或 workspace 协议自动热重载:
{
"dependencies": {
"@workspace/shared": "workspace:*",
"@workspace/components": "workspace:*",
"@workspace/types": "workspace:*"
}
}版本管理
Changesets
pnpm add -D @changesets/cli
pnpm changeset init{
'changelog': '@changesets/cli/changelog',
'commit': false,
'linked': [['@workspace/shared', '@workspace/components', '@workspace/types']],
'access': 'public',
'baseBranch': 'main'
}发布流程:
# 1. 记录变更
pnpm changeset
# 2. 提升版本
pnpm changeset version
# 3. 发布
pnpm changeset publish固定版本
所有包使用相同版本:
{
"version": "1.0.0",
"workspaces": {
"packages": ["packages/*"]
}
}pnpm version patch --workspace实际案例
案例 1:多机器人共享基础模板
workspace/
├── packages/
│ ├── base/ # 基础模板包
│ │ ├── templates/
│ │ │ ├── user/
│ │ │ └── common/
│ │ └── package.json
│ ├── bot-genshin/ # 原神机器人
│ │ ├── ktr/template/
│ │ │ └── genshin/ # 原神特有模板
│ │ └── karin.template.ts
│ └── bot-honkai/ # 崩坏机器人
│ ├── ktr/template/
│ │ └── honkai/ # 崩坏特有模板
│ └── karin.template.tsexport default ({
: {
: 'ktr/template' // 仅扫描本地模板
},
: { : 5181 },
: [
'../../base/templates/style.css' // 引入基础样式
]
})在渲染器中同时加载两个注册表:
import { , } from '@karinjs/template-react'
async function () {
// 加载基础模板(返回路由映射本身)
const = await ({ : '../../base' })
// 加载本地模板
const = await ({ : '.' })
// 合并注册表
const = { ..., ... }
return (, {
: 'lib/style.css', // 打包产物里的 CSS(产物目录随各自打包配置)
: 'temp/html'
})
}
export const = await ()案例 2:组件库 + 多主题
workspace/
├── packages/
│ ├── ui/ # 组件库
│ │ ├── src/
│ │ │ ├── Avatar/
│ │ │ ├── Card/
│ │ │ └── index.ts
│ │ └── package.json
│ ├── themes/ # 主题包
│ │ ├── default.css
│ │ ├── dark.css
│ │ └── package.json
│ ├── templates-light/ # 浅色主题模板
│ │ └── karin.template.ts
│ └── templates-dark/ # 深色主题模板
│ └── karin.template.ts@import 'tailwindcss';
@import '@karinjs/template-react/styles';
@import '@workspace/themes/default.css';
@source '../../ui/src';案例 3:功能模块化
workspace/
├── packages/
│ ├── core/ # 核心功能
│ │ └── templates/
│ │ ├── help/
│ │ └── status/
│ ├── games/ # 游戏模块
│ │ └── templates/
│ │ ├── gacha/
│ │ └── inventory/
│ ├── social/ # 社交模块
│ │ └── templates/
│ │ ├── profile/
│ │ └── leaderboard/
│ └── main/ # 主应用
│ └── karin.template.tsexport default ({
: {
: 'ktr/template'
},
: ['../core/templates/style.css', '../games/templates/style.css', '../social/templates/style.css']
})最佳实践
1. 目录组织
推荐:按功能分包
✅ packages/ui, packages/themes, packages/templates
避免:过度拆分
❌ packages/avatar, packages/badge, packages/card2. 依赖管理
使用 workspace 协议避免版本冲突:
{
"dependencies": {
"@workspace/shared": "workspace:*" // 始终使用最新
}
}3. 构建缓存
利用 Turborepo 或 Nx 缓存构建结果:
{
"pipeline": {
"build": {
"outputs": ["dist/**", "lib/**"],
"dependsOn": ["^build"]
}
}
}4. 类型检查
根目录统一类型检查:
{
"references": [{ "path": "./packages/shared" }, { "path": "./packages/bot-a" }, { "path": "./packages/bot-b" }]
}pnpm tsc --build5. 测试覆盖
共享包必须有测试:
{
"scripts": {
"test": "vitest",
"test:ci": "vitest run --coverage"
}
}6. 文档
共享包提供 README:
# @workspace/shared
共享模板和组件库。
## 使用
\`\`\`ts
import { Avatar } from '@workspace/shared/components'
\`\`\`