关于 shared-monorepo-pnpm-workspaces
面向 JavaScript 和 TypeScript monorepo 的 pnpm 工作区管理指南,涵盖包链接、依赖目录、定向命令、共享配置、发布与 CI 实践。
主要功能
- 01使用 pnpm-workspace.yaml 配置工作区
- 02通过 workspace:* 链接内部包
- 03用 catalog: 统一依赖版本
- 04使用 --filter 定向运行命令
- 05发布与 CI 配置指南
适用场景
- 01搭建 pnpm monorepo
- 02在工作区包之间共享依赖版本
- 03仅对变更的包运行命令
- 04从一个仓库发布相关包
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ agents-inc/ skills --skill shared-monorepo-pnpm-workspaces安装后在 Claude Code 中输入/shared-monorepo-pnpm-workspaces确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
metadata.yamlreference.mdexamples/ci.mdcore.mdpackages.mdpublishing.mdscripts.md详细介绍
用于 Monorepo 管理的 pnpm 工作区
**快速指南:**使用 pnpm 10.x 工作区管理 monorepo。
pnpm-workspace.yaml用于定义工作区包。使用workspace:*协议链接内部包。使用catalog:协议同步依赖版本。使用--filter执行定向命令。在各个包之间共享tsconfig和工具配置。使用 Changesets 管理版本和发布。默认严格隔离依赖。
<critical_requirements>
重要:使用此技能前
所有代码都必须遵循 CLAUDE.md 中的项目规范(kebab-case、命名导出、导入顺序、
import type、命名常量)
(所有内部包依赖都必须使用 workspace:* 协议,绝不能硬编码版本)
(CI 中必须使用 --frozen-lockfile,pnpm 会在 CI 环境中默认启用此选项)
(必须在仓库根目录的 pnpm-workspace.yaml 中定义工作区包)
(pnpm 专属设置必须放在 pnpm-workspace.yaml 中,不能放在 .npmrc 中(pnpm v10 的变更))
(在 3 个或更多包之间共享依赖版本时,必须使用 catalog: 协议)
(仅有特定包发生更改时,必须使用 --filter 执行定向命令,而不是使用 pnpm -r)
</critical_requirements>
**自动检测:**pnpm-workspace.yaml、pnpm 工作区、工作区协议、workspace:*、catalog:、pnpm filter、pnpm recursive、pnpm monorepo、pnpm-lock.yaml、.npmrc pnpm、pnpm catalogs、pnpm publish
适用场景:
- 使用 pnpm 工作区搭建 monorepo
- 配置
pnpm-workspace.yaml以发现工作区包 - 使用
workspace:*协议链接内部包 - 使用
catalog:协议同步依赖版本 - 使用
--filter或-r在工作区中运行脚本 - 从 pnpm 工作区发布包
- 使用 pnpm 缓存设置 CI/CD 流水线
- 在工作区包之间共享 TypeScript、ESLint 或 Prettier 配置
- 从 npm/yarn 工作区迁移到 pnpm
不适用场景:
- 没有共享代码的单包项目
- 使用 Bun 或 Yarn 作为包管理器的项目
- 仅需要兼容 npm 的项目(例如 npm 工作区)
- 任务编排逻辑(请在 pnpm 之上使用专用任务运行器)
涵盖的关键模式:
pnpm-workspace.yaml配置和工作区包发现- 工作区协议(
workspace:*、workspace:^、workspace:~) - 用于同步依赖版本的目录
- 过滤命令(
--filter、-F、glob 模式、依赖选择器) - 在工作区中运行脚本(
-r、--parallel、--workspace-concurrency) pnpm-workspace.yaml中的设置(v10:设置从.npmrc移至此文件)- 使用
publishConfig和 changesets 发布 - 使用 GitHub Actions、缓存和
--frozen-lockfile的 CI/CD - 共享 TypeScript 配置模式
- Monorepo 目录结构规范
详细资源:
- 核心配置 -- pnpm-workspace.yaml、.npmrc、目录结构、设置
- 共享包 -- 工作区协议、目录、TypeScript/ESLint 配置
- 脚本与过滤 -- --filter、递归执行、依赖管理
- 发布与版本管理 -- changesets、publishConfig、Docker
- CI/CD 流水线 -- GitHub Actions、自动化发布
- 快速命令参考 -- 精简速查表
## 设计理念
pnpm 工作区通过内容寻址存储实现严格、高效的单体仓库管理。与 npm/yarn 不同,pnpm 创建的 node_modules 并非扁平结构,包只能访问自己声明的依赖项——这种严格性能够及早发现缺失的依赖声明。工作区协议(workspace:*)可确保内部包始终在本地链接,而目录(catalog:)则集中管理版本,避免版本不一致。
核心原则:
- 默认严格——包无法访问未声明的依赖项
- 节省磁盘空间——内容寻址存储可在各项目间共享相同文件
- 安全优先——v10 默认阻止生命周期脚本,以防范供应链攻击
- 单一锁文件——所有包共用工作区根目录中的一个
pnpm-lock.yaml
适用场景:
- 包含多个应用和共享包的单体仓库
- 需要严格隔离依赖项的项目
- 希望节省磁盘空间来存储依赖项的团队
- 在一个仓库中发布多个相关 npm 包
不适用场景:
- 单包项目(没有工作区带来的好处)
- 深度依赖 Yarn PnP 或 Berry 特性的项目
- 仅提供 npm 的环境(例如某些 CI/CD 环境或受限的企业环境)
核心模式
模式 1:工作区配置(pnpm-workspace.yaml)
仓库根目录中的 pnpm-workspace.yaml 文件用于定义包含工作区包的目录。每个 pnpm 工作区都必须包含此文件。
# pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"
在 pnpm v10 中,设置项从 .npmrc 移到了此文件中:
# pnpm-workspace.yaml (v10+)
packages:
- "apps/*"
- "packages/*"
linkWorkspacePackages: true
saveWorkspaceProtocol: rolling
disallowWorkspaceCycles: true
优点: 工作区定义和设置的单一事实来源
完整的工作区设置、目录结构和所有设置项,请参阅 examples/core.md。
模式 2:工作区协议(workspace:*)
工作区协议可确保内部包始终解析为本地工作区版本,而不会从注册表获取。
| 协议 | 开发期间 | 执行 pnpm publish 后 |
|---|---|---|
workspace:* | 链接到本地包 | 替换为精确版本(例如 1.5.0) |
workspace:^ | 链接到本地包 | 替换为插入符范围(例如 ^1.5.0) |
workspace:~ | 链接到本地包 | 替换为波浪号范围(例如 ~1.5.0) |
{
"dependencies": {
"@repo/ui": "workspace:*",
"@repo/types": "workspace:*"
}
}
优点: 确保本地链接,pnpm 会拒绝从外部解析;发布时进行版本转换,以确保符合正确的 semver 版本格式
{
"dependencies": {
"@repo/ui": "^1.0.0"
}
}
缺点: 硬编码版本可能会从 npm 注册表安装,而不是使用本地工作区;不同包之间的版本可能不匹配
有关协议变体、别名和内部包设置,请参阅 examples/packages.md。
模式 3:使用目录同步版本
目录在 pnpm-workspace.yaml 中定义一次依赖项版本,并通过 catalog: 在所有包中引用。
# pnpm-workspace.yaml
catalog:
react: ^19.0.0
react-dom: ^19.0.0
typescript: ^5.7.0
{
"dependencies": {
"react": "catalog:",
"react-dom": "catalog:"
}
}
优点: 版本的单一事实来源,只需更新一行即可更新所有包,并避免合并冲突
命名目录支持版本迁移:
catalogs:
react18:
react: ^18.3.1
react19:
react: ^19.0.0
有关命名目录、严格强制执行和完整示例,请参阅 examples/packages.md。
模式 4:筛选命令
--filter(或 -F)可将命令限制在指定包中运行,而不是在整个工作区中运行。
# Exact package
pnpm --filter @repo/web-app build
# Package and ALL its dependencies
pnpm --filter "web-app..." build
# Changed packages since main
pnpm --filter "...[origin/main]" test
# Exclude a package
pnpm --filter "!@repo/docs" build
优点: 针对性执行可节省 CI 时间,基于依赖关系的筛选可确保正确的构建顺序
# BAD: Running everything when only one package changed
pnpm -r build
缺点: 重建所有包会浪费 CI 时间,且无法检测变更
有关所有筛选变体、针对 CI 优化的模式和依赖关系图探索,请参阅 examples/scripts.md。
模式 5:跨工作区运行脚本
# Topological order (respects dependency graph) -- use for build
pnpm -r build
# Parallel (ignores dependency graph) -- use for test, lint
pnpm -r --parallel test
# Controlled concurrency
pnpm -r --workspace-concurrency 4 build
# BAD: Building in parallel when packages depend on each other
pnpm -r --parallel build
**不好的原因:**依赖项尚未完成时,软件包可能已开始构建,导致模块缺失错误
有关依赖项管理和添加依赖项,请参阅 examples/scripts.md。
模式 6:共享 TypeScript 配置
使用配置包在所有工作区软件包之间共享 TypeScript 编译器选项。
{
"name": "@repo/config-typescript",
"private": true,
"exports": {
"./base": "./tsconfig.base.json",
"./react": "./tsconfig.react.json",
"./node": "./tsconfig.node.json"
}
}
使用者用法:
{
"extends": "@repo/config-typescript/react",
"include": ["src/**/*.ts", "src/**/*.tsx"]
}
**好的原因:**TypeScript 设置只有一个真实来源,变更会传播到所有软件包
有关完整的 base/react/node 配置和 ESLint 共享,请参阅 examples/packages.md。
模式 7:从工作区发布
使用 publishConfig 控制发布内容,并使用 changesets 管理版本。
{
"name": "@repo/ui",
"main": "./src/index.ts",
"publishConfig": {
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"access": "public"
}
}
**好的原因:**开发期间使用源代码(HMR 更快),发布时使用构建输出
pnpm changeset # Create changeset
pnpm changeset version # Bump versions + changelogs
pnpm publish -r # Publish to npm
有关 changesets 配置、Docker 部署和迁移,请参阅 examples/publishing.md。
模式 8:使用 GitHub Actions 实现 CI/CD
- uses: pnpm/action-setup@v4
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: 22
cache: "pnpm"
- run: pnpm install
- run: pnpm --filter "...[origin/main]" build
- run: pnpm --filter "...[origin/main]" test
好的原因:pnpm/action-setup@v4 负责安装,cache: "pnpm" 缓存存储目录,CI 中会自动启用 --frozen-lockfile,基于变更的筛选只会构建受影响的软件包
有关完整工作流(包括使用 changesets 自动发布),请参阅 examples/ci.md。
性能优化
安装性能:
- pnpm 使用内容寻址存储——相同文件在磁盘上只存储一次
- 通过硬链接,热安装速度最多比 npm/yarn 快 2 倍
--frozen-lockfile(CI 中的默认选项)会跳过解析,实现最快安装
工作区性能:
- 使用
--filter仅在受影响的软件包上运行命令 - 对没有跨软件包依赖的任务(test、lint)使用
--parallel - 在资源受限的 CI 中使用
--workspace-concurrency控制并行度 - 基于变更的筛选(
--filter "...[origin/main]")会跳过未变更的软件包
节省磁盘空间:
- 全局存储会在多个项目之间去重(
pnpm store path) - 通常比 npm 节省 50-70% 的磁盘空间
CI 缓存:
- uses: actions/setup-node@v4
with:
node-version: 22
cache: "pnpm"
这会在多次运行之间缓存 pnpm 内容寻址存储,使后续安装几乎瞬间完成。
<decision_framework>
决策框架
何时使用 Catalogs,何时直接指定版本
Does 3+ packages use this dependency?
YES -> Use catalog: protocol
NO -> Direct version is fine
Will this dependency version be updated frequently?
YES -> Use catalog: (one-line update)
NO -> Direct version is acceptable
workspace:* 与 workspace:^ 和 workspace:~
Are you publishing packages to npm?
NO -> Use workspace:* (exact local linking, version irrelevant)
YES -> Do consumers need flexible version ranges?
YES -> workspace:^ (caret range on publish)
NO -> workspace:* (exact version on publish)
何时使用 --filter,何时使用 -r
Running a command in CI?
YES -> Use --filter "...[origin/main]" (only affected packages)
NO -> Running locally?
ALL packages -> pnpm -r <cmd>
ONE package -> pnpm --filter <name> <cmd>
Is the command order-dependent (build)?
YES -> Use -r (topological order) or --filter with ...
NO -> Use --parallel for speed (lint, test)
pnpm、npm 与 Yarn Workspaces
Need strict dependency isolation?
YES -> pnpm (non-flat node_modules by default)
NO -> Any works
Need disk efficiency?
YES -> pnpm (content-addressable store)
NO -> Any works
Need zero-config PnP (no node_modules)?
YES -> Yarn Berry with PnP
NO -> pnpm or npm
</decision_framework>
<red_flags>
危险信号
高优先级问题:
- 内部包使用硬编码版本,而不是
workspace:*(会破坏本地链接,并从注册表安装) - 将本应放在
pnpm-workspace.yaml中的设置写入.npmrc(在 pnpm v10 中会被静默忽略) - 仓库根目录缺少
pnpm-workspace.yaml(pnpm 将无法识别工作区包) - 在 CI 中运行
pnpm install时未使用--frozen-lockfile(锁文件可能被静默修改) - 在包之间存在依赖关系时,使用
--parallel运行构建命令(会导致竞态条件)
中优先级问题:
- 当 3 个或更多包共享相同依赖版本时,未使用
catalog:(会导致版本漂移) - 在 CI 中运行
pnpm -r build,而不是--filter "...[origin/main]"(浪费时间) - 内部包缺少
private: true(存在意外发布到 npm 的风险) - 未配置
allowBuilds(或旧版onlyBuiltDependencies)允许列表(会在 v10 中阻止所有安装脚本)
常见错误:
- 混用包管理器(在 pnpm 工作区中运行 npm install 会破坏锁文件)
- 将
shamefullyHoist: true当作快速修复,而不是正确声明缺失的依赖项 - 忘记在 GitHub Actions checkout 中设置
fetch-depth: 0(会破坏基于 Git 的变更检测) - 本地与 CI 使用不同的 pnpm 版本(会导致锁文件格式不兼容)
注意事项与边缘情况:
pnpm publish会将workspace:*替换为实际版本——这是预期行为,不是 bugcatalog:条目必须与依赖项名称完全一致——拼写错误会被静默忽略--filter "...[origin/main]"需要 Git 历史记录——请使用fetch-depth: 0,或至少使用fetch-depth: 2- pnpm v10 默认会阻止所有生命周期脚本——使用
pnpm approve-builds将需要运行postinstall的包(如esbuild、sharp)加入允许列表,或在pnpm-workspace.yaml中配置allowBuilds pnpm deploy必须设置injectWorkspacePackages: true(或使用pnpm deploy --legacy绕过)- 循环工作区依赖会导致脚本执行顺序不可预测——请使用
disallowWorkspaceCycles: true saveWorkspaceProtocol: rolling(默认值)意味着pnpm add会自动使用workspace:保存依赖——这是正确行为
</red_flags>
<critical_reminders>
重要提醒
所有代码都必须遵循项目在 CLAUDE.md 中的约定(kebab-case、命名导出、导入顺序、
import type、命名常量)
(所有内部包依赖都必须使用 workspace:* 协议——绝不要硬编码版本)
(CI 中必须使用 --frozen-lockfile——pnpm 在 CI 环境中默认启用此选项)
(必须在仓库根目录的 pnpm-workspace.yaml 中定义工作区包)
(pnpm 专属设置必须放在 pnpm-workspace.yaml 中——不要放在 .npmrc 中(pnpm v10 的变更))
(当 3 个或更多包共享依赖版本时,必须使用 catalog: 协议)
(仅有特定包发生变更时,必须使用 --filter 运行针对性命令,而不是使用 pnpm -r)
不遵循这些规则会导致依赖解析失败、版本漂移、CI 缓存失效以及安全漏洞。
</critical_reminders>