用 Claude Code 和 pnpm workspace 搭建实用 Monorepo
用 Claude Code 配合 pnpm workspace 设计小型 monorepo,覆盖依赖、filter、CI 和常见坑。
pnpm workspace 解决的是“不要到处复制代码”
我是 claudecode-lab.com 的 Masa。
一个产品刚开始通常只有一个应用,但很快会长出后台、公共 UI、配置、脚本、邮件任务和测试工具。真正的信号不是“文件多”,而是同一个按钮文案、API 路径、环境变量名、Zod schema 被复制到多个目录。复制一次很快,半年后改一次就很慢。
pnpm workspace 是把多个 package 放在同一个 Git 仓库里管理的方式。官方 pnpm Workspace 文档说明,workspace 需要在根目录放置 pnpm-workspace.yaml,用它把多个项目联合成一个工作区。对小团队来说,这比一开始就拆成很多仓库更容易维护。
Claude Code 的价值不是“帮我生成一个 monorepo”,而是持续检查边界。它可以帮你找出内部依赖没有写 workspace:*、apps/* 被 packages/* 反向引用、CI 没有用 filter 导致每次都跑全量这些问题。先把基础搭好,再让 Claude Code 做审查,效果比让它一次性重构整个仓库稳定得多。
这篇文章以 pnpm 11.5.0 为例。更完整的背景可以参考Claude Code monorepo 管理和依赖管理指南。
目标结构: 4 个 package 就足够开始
我建议从两个应用加两个共享 package 开始。边界简单,Claude Code 也容易理解。
flowchart LR
web["apps/web\n@acme/web"] --> ui["packages/ui\n@acme/ui"]
web --> config["packages/config\n@acme/config"]
admin["apps/admin\n@acme/admin"] --> ui
admin --> config
acme-workspace/
apps/web/src/main.ts
apps/web/package.json
apps/admin/src/main.ts
apps/admin/package.json
packages/config/src/index.ts
packages/config/package.json
packages/ui/src/index.ts
packages/ui/package.json
pnpm-workspace.yaml
package.json
.npmrc
CLAUDE.md
三个常见用例很适合这个结构。第一,把通用显示逻辑、标签和轻量 UI helper 放进 packages/ui,让 Web 和后台共用。第二,把产品名、公开 URL、feature flag 名称放进 packages/config,避免两个应用配置不一致。第三,后续添加 packages/contracts,把 API 类型或 Zod schema 同时给前端和服务端使用。
不要一开始就创建 packages/common 或 packages/everything。共享代码应该表示“从任何调用方看语义都一样”,不是把不好归类的逻辑丢进去。给 Claude Code 的指令也要明确,比如“只抽出重复的 UI helper,业务判断留在 app 内”。
最小配置可以直接复制
根目录先放 pnpm-workspace.yaml。官方 pnpm-workspace.yaml 文档说明,packages 可以包含目录,也可以用 ! 排除目录。
packages:
- "apps/*"
- "packages/*"
catalog:
typescript: ^5.8.3
根 package.json 只负责调度各 package。
{
"name": "acme-workspace",
"private": true,
"packageManager": "pnpm@11.5.0",
"scripts": {
"check:web": "pnpm --filter @acme/web build",
"build": "pnpm -r --sort --if-present build",
"test": "pnpm -r --if-present test",
"changed:test": "pnpm --filter \"...[origin/main]\" --if-present test"
},
"devDependencies": {
"typescript": "catalog:"
}
}
tsconfig.base.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"skipLibCheck": true,
"noEmit": true
}
}
.npmrc 推荐从严格模式开始,尤其是库 package 较多时。
link-workspace-packages=false
save-workspace-protocol=rolling
shared-workspace-lockfile=true
strict-peer-dependencies=true
auto-install-peers=false
关键点是 workspace:*。pnpm 文档说明,使用 workspace: 协议时,pnpm 不会把依赖解析到本地 workspace 之外。这样可以避免内部包名和 registry 上的包名冲突。
packages/config/package.json:
{
"name": "@acme/config",
"version": "0.1.0",
"private": true,
"type": "module",
"exports": {
".": {
"types": "./src/index.ts",
"import": "./src/index.ts"
}
}
}
packages/config/src/index.ts:
export const appConfig = {
productName: "Acme Workspace",
supportEmail: "support@example.com",
publicSiteUrl: "https://example.com"
} as const;
packages/ui 通过 workspace 协议引用 config。
{
"name": "@acme/ui",
"version": "0.1.0",
"private": true,
"type": "module",
"dependencies": {
"@acme/config": "workspace:*"
},
"exports": {
".": {
"types": "./src/index.ts",
"import": "./src/index.ts"
}
}
}
import { appConfig } from "@acme/config";
export function renderPrimaryButton(label: string): string {
return `[${appConfig.productName}] ${label}`;
}
应用侧只声明自己需要的内部依赖。
{
"name": "@acme/web",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"build": "tsc -p tsconfig.json"
},
"dependencies": {
"@acme/config": "workspace:*",
"@acme/ui": "workspace:*"
}
}
apps/web/tsconfig.json:
{
"extends": "../../tsconfig.base.json",
"include": ["src"]
}
import { appConfig } from "@acme/config";
import { renderPrimaryButton } from "@acme/ui";
console.log(appConfig.publicSiteUrl);
console.log(renderPrimaryButton("Start trial"));
执行:
corepack pnpm install
corepack pnpm --filter @acme/web build
corepack pnpm -r --sort --if-present build
先让 Claude Code 理解 package 边界
Claude Code 官方有一页monorepos and large codebases。重点是: 大仓库里不要让无关文件占满上下文。pnpm workspace 也是一样。根目录可以启动 Claude Code,但任务必须说明范围。
根 CLAUDE.md 只写全局规则:
# Acme Workspace
This repository is a pnpm workspace.
Packages:
- apps/web: customer-facing TypeScript app
- apps/admin: internal admin app
- packages/ui: shared UI helpers
- packages/config: shared runtime constants
Rules:
- Use pnpm, not npm or yarn.
- Add internal dependencies with workspace:*.
- Run focused commands with pnpm --filter before full workspace commands.
- Do not move business logic into packages/ui.
如果某个 package 有独立规则,就放在那个目录的 CLAUDE.md。Claude Code 的 memory 文档说明,CLAUDE.md 是持久指令,不是临时提示。因此它应该短、具体、能长期维护。
claude -p "
Inspect this pnpm workspace. Do not edit files yet.
List the package graph, scripts, and risky dependency directions.
Then propose the smallest change needed to share UI helpers between apps/web and apps/admin.
"
这个提示先审查,再提案,能减少 Claude Code 过度抽象的概率。
filter 是日常开发和 CI 的核心
pnpm Filtering 用来限制命令作用的 package 范围。
pnpm --filter @acme/web build
pnpm --filter @acme/web... build
pnpm --filter ...@acme/ui test
pnpm --filter "...[origin/main]" --if-present test
最容易错的是 ... 的方向。@acme/web... 表示 web 加它依赖的包。...@acme/ui 表示 ui 加依赖 ui 的包。修改 UI 后只跑 @acme/ui...,很可能漏掉 web/admin 的测试。
CI 可以只跑受影响 package:
name: workspace-check
on:
pull_request:
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: corepack enable
- run: corepack prepare pnpm@11.5.0 --activate
- run: pnpm install --frozen-lockfile
- run: pnpm --filter "...[origin/main]" --if-present test
- run: pnpm --filter "...[origin/main]" --if-present build
常见坑和修复方式
第一个坑是内部包不用 workspace:*。
{
"dependencies": {
"@acme/ui": "^0.1.0"
}
}
应该改成:
{
"dependencies": {
"@acme/ui": "workspace:*"
}
}
第二个坑是共享包变成业务垃圾桶。packages/ui 不应该发 API 请求,也不应该判断 billing 计划。第三个坑是循环依赖,例如 packages/ui import apps/web。这类问题会破坏构建顺序,也让 Claude Code 难以判断修改影响。
我会定期让 Claude Code 做一次边界审查:
claude -p "
Check this workspace for circular dependencies and misplaced imports.
Focus on packages/* importing from apps/*, duplicated config values,
and dependencies that should be workspace:*.
Return findings with file paths and minimal fixes.
"
如果要发布内部库,再加入 Changesets:
pnpm add -Dw @changesets/cli
pnpm changeset init
pnpm changeset
pnpm changeset version
pnpm -r publish --access public
pnpm 文档也提示,workspace 内 package 的版本管理通常交给 Changesets 或 Rush。不是所有 private app 都需要发布流程,apps/* 通常应该保持 private: true。
总结和验证结果
pnpm workspace 的核心不是复杂工具链,而是把共享 UI、配置、类型和测试从“复制文件”变成明确依赖。Claude Code 最适合做边界审查、最小改动提案和 CI 失败分析。
下一步可以阅读 CLAUDE.md 最佳实践 和 Claude Code 测试策略。如果需要团队导入或规则设计,可以从 Claude Code training 开始。
本文示例按 Windows、Node.js 22、Corepack、pnpm 11.5.0 检查。实际使用中,workspace:* 缺失和 filter 方向写反是最常见的问题。把“先让 Claude Code 输出 package graph,再修改文件”作为固定步骤,可以明显减少无用抽象和循环依赖。
免费 PDF: Claude Code 速查表
输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。
我们会妥善保护你的信息,不发送垃圾邮件。
把 Claude Code 变成真正能带来结果的工作流
先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。
关于作者
Masa
专注 Claude Code 实务流程、团队导入和内容转化的工程师。
相关文章
从Obsidian到CLAUDE.md的Claude Code流程:不再反复解释上下文
把 Obsidian 工作笔记整理成 CLAUDE.md 运行说明,让 Claude Code 每次都带着正确上下文开始。
Claude Code 收入 CTA 路由:从文章分流到 PDF、Gumroad 与咨询
用 Claude Code 按读者意图把文章流量分到免费 PDF、Gumroad 教材或咨询入口。
Claude Code 团队交接规则: 把审查证据、权限、回滚和收入路径一起交付
面向团队的 Claude Code 交接格式: 证据、权限、回滚、免费 PDF、Gumroad 与咨询路径都要可审查。