Monorepo(Monolithic Repository)是指将多个项目的代码存储在同一个代码仓库中的开发策略。这些项目虽然在同一个仓库里,但依然可以作为独立的包(package)进行开发、测试和发布。
以 Babel 项目为例。Babel 被拆分为 @babel/core、@babel/cli、@babel/parser 等数十个功能包。如果采用传统的多仓库(Multi-repo)模式,每个包都将拥有独立的 Git 仓库,这会带来一系列问题:
Monorepo 通过将所有相关的包集中管理,有效地解决了这些痛点:
Monorepo 特别适合以下场景:
以下情况可能不适合使用 Monorepo:
尽管 Monorepo 优势显著,但它也带来了三大核心技术挑战。几乎所有 Monorepo 工具链(如 Lerna、pnpm、Yarn/NPM Workspaces)的设计初衷,都是为了解决这三个问题:
npm link 的繁琐操作build、test),并确保任务按照正确的依赖拓扑顺序执行CHANGELOG 生成,并实现原子化的发布流程,确保依赖更新时相关包也能同步更新以 pnpm 为例,快速搭建一个 Monorepo 项目:
mkdir my-monorepo
cd my-monorepo创建基本目录结构:
my-monorepo/
├── packages/
│ ├── core/ # 核心库
│ │ ├── src/
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── ui/ # UI 组件库
│ │ ├── src/
│ │ ├── package.json
│ │ └── tsconfig.json
│ └── utils/ # 工具库
│ ├── src/
│ ├── package.json
│ └── tsconfig.json
├── pnpm-workspace.yaml
├── package.json
└── pnpm-lock.yaml创建 pnpm-workspace.yaml:
# pnpm-workspace.yaml
packages:
- 'packages/*'创建根目录 package.json:
{
"name": "my-monorepo",
"private": true,
"scripts": {
"build": "pnpm -r run build",
"test": "pnpm -r run test",
"lint": "pnpm -r run lint"
},
"devDependencies": {
"typescript": "^5.0.0"
}
}packages/core/package.json:
{
"name": "@my-org/core",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"dev": "tsc -w"
},
"dependencies": {
"@my-org/utils": "workspace:*"
}
}packages/utils/package.json:
{
"name": "@my-org/utils",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"dev": "tsc -w"
}
}packages/ui/package.json:
{
"name": "@my-org/ui",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"dev": "tsc -w"
},
"dependencies": {
"@my-org/core": "workspace:*",
"@my-org/utils": "workspace:*"
}
}pnpm install安装完成后,pnpm 会自动创建软链接:
node_modules/
├── @my-org/
│ ├── core -> ../../packages/core
│ ├── ui -> ../../packages/ui
│ └── utils -> ../../packages/utils# 构建所有包(自动按依赖顺序)
pnpm build
# 只构建 core 及其依赖
pnpm --filter "@my-org/core..." run build
# 在开发模式下监听所有包
pnpm -r run dev开发阶段 发布阶段
│ │
├─ 修改代码 ├─ 创建 changeset
│ │
├─ 本地测试 ├─ 版本提升
│ │
├─ 提交代码 ────────────────┤
│ │
└─ CI 自动测试 ├─ 生成 CHANGELOG
│
└─ 发布到 npm在 Monorepo 环境中,一个包(如 app)常常需要依赖同一仓库中的其他包(如 header 和 footer)。为了让 app 在开发时能够实时使用 header 和 footer 的最新代码,需要一种机制来链接这些本地包。
npm link 的困境在 Monorepo 出现之前,开发者通常使用 npm link 来解决本地包依赖问题。其工作流程如下:
header 包的目录中,执行 npm link 注册到全局 node_modulesfooter 包的目录中,执行 npm link 注册到全局app 包的目录中,执行 npm link header 和 npm link footer,将全局链接的包引入到 app 的 node_modules 中这个过程实际上创建了两层软链接(Symbolic Links):
本地包 (packages/header)
↓ npm link
全局 node_modules
↓ npm link header
项目的 node_modules存在的问题:
为了解决 npm link 的痛点,现代包管理工具(npm、Yarn、pnpm)引入 workspace(工作区)的概念。workspace 是一种更高效、更自动化的本地包链接机制。
核心思想:在执行 npm install 或类似命令时,自动将 workspace 中声明的所有包通过软链接的方式链接到项目根目录的 node_modules 中。根据 Node.js 的模块解析规则(即逐级向上查找 node_modules),任何一个包都可以无缝地引用到 workspace 中的其他包。
在一个包含 a、b、c 三个包的 Monorepo 项目中,workspace 会创建如下的软链接结构:
/my-project
/node_modules
/a -> /my-project/packages/a # 软链接
/b -> /my-project/packages/b # 软链接
/c -> /my-project/packages/c # 软链接
/packages
/a
/src
/index.ts
package.json
/b
/src
/index.ts
package.json
/c
/src
/index.ts
package.json优势:
虽然 workspace 的理念一致,但不同工具的配置方式和特性略有差异。
npm 通过在根目录的 package.json 文件中添加 workspaces 字段来声明工作区。
// package.json
{
"name": "my-monorepo",
"private": true,
"workspaces": [
"packages/*",
"apps/*"
]
}配置说明:
"private": true:根目录不需要发布,避免误操作"workspaces":支持 glob 模式匹配多个目录常用命令:
# 安装所有依赖
npm install
# 在特定包中执行命令
npm run build --workspace=@my-org/core
# 在所有包中执行命令
npm run build --workspaces注意事项:
pnpm 使用独立的 pnpm-workspace.yaml 文件来定义工作区。
# pnpm-workspace.yaml
packages:
- 'packages/*'
- 'apps/*'
- '!**/test/**' # 排除测试目录优势特性:
// packages/app/package.json
{
"dependencies": {
"@my-org/utils": "workspace:*", // 使用最新版本
"@my-org/core": "workspace:^1.0.0" // 指定版本范围
}
}workspace 协议:
workspace:*:始终使用工作区内的最新代码workspace:^:遵循 semver 版本范围workspace:~:允许补丁版本更新常用命令:
# 安装依赖
pnpm install
# 递归执行命令(支持拓扑排序)
pnpm -r run build
# 过滤特定包执行
pnpm --filter "@my-org/core..." run build
# 只构建某个包及其依赖项
pnpm --filter "...@my-org/ui" run build性能优势:
Yarn 的配置方式与 npm 类似:
// package.json
{
"private": true,
"workspaces": {
"packages": ["packages/*"],
"nohoist": ["**/react-native", "**/react-native/**"]
}
}nohoist 配置:某些包需要保留在包内部的 node_modules 中(如 React Native)。
| 特性 | npm Workspaces | pnpm Workspaces | Yarn Workspaces |
|---|---|---|---|
| 配置文件 | package.json | .yaml 文件 | package.json |
| 拓扑排序 | ❌ | ✅ | ✅ |
| 磁盘空间优化 | ❌ | ✅(硬链接) | ✅(PnP) |
| 依赖提升控制 | 有限 | ✅ | ✅ |
| workspace 协议 | ❌ | ✅ | ✅ |
| 过滤器支持 | 基础 | ✅(强大) | 基础 |
| 幽灵依赖问题 | 存在 | ❌ | 可配置 |
推荐:对于新项目,推荐使用 pnpm Workspaces,它在性能、安全性和功能完整性方面表现最佳。
在 Monorepo 中经常需要对多个包执行相同的命令,例如 build、test 或 lint。如果手动进入每个包的目录去执行这些命令,不仅效率低下,而且无法保证正确的执行顺序。
什么是拓扑排序?
拓扑排序是指根据依赖关系确定任务执行顺序的算法。在 Monorepo 中,如果包 A 依赖包 B,那么在构建时必须先构建 B,再构建 A。
示例场景:
依赖关系:
app → header
app → footer
header → utils
footer → utils
正确的构建顺序:
1. utils(无依赖)
2. header(依赖 utils)
3. footer(依赖 utils)
4. app(依赖 header 和 footer)Lerna 是最早解决这一问题的工具之一。它提供 lerna run 命令,可以按照包的依赖关系,智能地执行任务。
lerna run build当执行上述命令时,Lerna 会:
package.json 里定义的 build 脚本并行执行示例:
时间线:
0s ─────────────────────────────────
├─ utils (开始)
2s └─ utils (完成)
├─ header (开始) ├─ footer (开始) ← 并行执行
4s └─ header (完成) └─ footer (完成)
├─ app (开始)
6s └─ app (完成)pnpm 通过 -r(recursive)标志来递归执行命令,并默认支持拓扑排序。
# 构建所有包
pnpm -r run build
# 并行构建(不等待依赖)
pnpm -r --parallel run build
# 串行构建(将并发数设为 1,pnpm 无 --serial 选项)
pnpm -r --workspace-concurrency=1 run build高级过滤功能:
# 只构建特定包及其依赖
pnpm --filter "@my-org/ui..." run build
# 构建依赖于特定包的所有包
pnpm --filter "...@my-org/utils" run build
# 构建有变化的包(Git 支持)
pnpm --filter "[origin/main]" run build
# 排除某些包
pnpm --filter "!@my-org/docs" run buildnpm 提供 --workspaces 标志来在所有包中执行命令:
# 在所有包中执行
npm run build --workspaces
# 在特定包中执行
npm run build --workspace=@my-org/core --workspace=@my-org/utils注意:npm 的 workspace 命令执行不支持拓扑排序,它会按照包在文件系统中的顺序执行,这在有依赖关系的项目中可能导致问题。
| 工具 | 拓扑排序 | 并行执行 | 过滤功能 | 缓存支持 |
|---|---|---|---|---|
| Lerna | ✅ | ✅ | ✅ | ✅ |
| pnpm | ✅ | ✅ | ✅✅ | ❌ |
| npm | ❌ | ❌ | 基础 | ❌ |
| Yarn | ✅ | ✅ | 基础 | ✅ |
版本管理与发布是 Monorepo 工作流中最为复杂的一环。当一个包更新后,需要:
package.json 文件中的版本号CHANGELOG:为每个更新的包生成变更日志,记录本次发布的新功能、修复的 Bug 等手动完成这些操作不仅耗时,而且极易出错。因此,自动化的版本管理与发布工具对 Monorepo 至关重要。
Changesets 是专注于 Monorepo 版本管理的工具,它通过将"版本意图"与"版本发布"分离,提供一套非常优雅和灵活的工作流。
核心思想:开发者在提交代码时,随同代码一起提交一个描述变更的 "changeset" 文件。这个文件记录了本次变更将影响哪些包,以及每个包应该进行何种类型的版本升级(Major, Minor, 或 Patch)。
开发阶段 发布阶段
│ │
├─ 修改代码 ├─ npx changeset version
│ │ (更新版本号)
├─ npx changeset add ──────┤
│ (创建变更集) ├─ 生成 CHANGELOG
│ │
└─ 提交到 Git ├─ 删除变更集文件
│
└─ npx changeset publish
(发布到 npm)# 安装
pnpm add -Dw @changesets/cli
# 初始化
pnpm changeset init创建的配置文件 .changeset/config.json:
{
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}重要配置项说明:
access:发布访问权限(restricted 私有包,public 公开包)baseBranch:基础分支名称updateInternalDependencies:内部依赖更新策略(patch 或 minor)fixed:版本号固定在一起的包组linked:版本号关联的包组当完成一个功能或修复一个 Bug 后,运行:
npx changeset addChangesets 会启动交互式命令行界面:
? Which packages would you like to include? (Press <space> to select)
❯◉ @my-org/core
◯ @my-org/utils
◉ @my-org/ui
? What kind of change is this for @my-org/core? (current version is 1.0.0)
❯ patch (1.0.1)
minor (1.1.0)
major (2.0.0)
? What kind of change is this for @my-org/ui? (current version is 1.0.0)
❯ patch (1.0.1)
minor (1.1.0)
major (2.0.0)
? Please enter a summary for this change:
│ Add new feature for core and ui components生成的变更集文件 .changeset/cold-animals-hug.md:
---
"@my-org/core": patch
"@my-org/ui": patch
---
Add new feature for core and ui components当准备发布时,运行:
npx changeset versionChangesets 会:
.changeset 目录下的所有变更集文件package.json 中的版本号CHANGELOG.md:为每个更新的包生成或更新 CHANGELOG.md 文件生成的 CHANGELOG.md 示例:
# @my-org/core
## 1.0.1
### Patch Changes
- Add new feature for core and ui components
## 1.0.0
### Major Changes
- Initial releasenpx changeset publish这个命令会:
@my-org/core@1.0.1)使用 GitHub Actions 自动化发布:
# .github/workflows/release.yml
name: Release
on:
push:
branches:
- main
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v3
with:
node-version: 18
cache: 'pnpm'
- run: pnpm install
- name: Create Release Pull Request or Publish
uses: changesets/action@v1
with:
publish: pnpm changeset publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}Lerna 也内置了一套强大的版本管理系统。
// lerna.json
{
"version": "independent", // 或 "fixed"
"npmClient": "pnpm",
"command": {
"version": {
"conventionalCommits": true,
"message": "chore(release): publish"
},
"publish": {
"registry": "https://registry.npmjs.org/"
}
}
}版本策略:
fixed:所有包使用相同版本号independent:每个包独立版本号# 版本提升(交互式)
lerna version
# 自动根据 commit 提升版本
lerna version --conventional-commits
# 发布
lerna publish
# 发布到私有仓库
lerna publish --registry=https://my-private-registry.com| 特性 | Changesets | Lerna |
|---|---|---|
| 工作流模式 | 分布式 | 集中式 |
| CI/CD 集成 | ✅✅ | ✅ |
| 变更记录 | 变更集文件 | Git commits |
| 版本策略 | 独立版本 | 独立/固定版本 |
| 学习曲线 | 中等 | 较低 |
| 社区活跃度 | 高 | 中等 |
| 推荐场景 | 大型项目、团队协作 | 中小型项目、快速上手 |
推荐:Changesets 已成为社区中最受欢迎的 Monorepo 版本管理工具,特别适合团队协作和 CI/CD 流程。
在选择 Monorepo 工具链时,需要考虑项目规模、团队习惯、性能需求等因素。
方案一:pnpm + Changesets
// package.json
{
"devDependencies": {
"@changesets/cli": "^2.26.0"
}
}优势:
适合:新项目、中大型项目、注重性能的团队
方案二:Lerna
npm install -D lerna
npx lerna init优势:
适合:大型企业级项目、需要极致性能优化的场景
方案三:npm Workspaces
// package.json
{
"workspaces": ["packages/*"]
}优势:
劣势:
适合:小型项目、快速原型开发
| 核心功能 | Lerna + pnpm | pnpm + Changesets | npm Workspaces |
|---|---|---|---|
| 本地包链接 | ✅ | ✅ | ✅ |
| 跨包任务执行 | ✅ (lerna run) | ✅ (pnpm -r) | ⚠️ (基础) |
| 拓扑排序 | ✅ | ✅ | ❌ |
| 版本管理 | ✅ (lerna) | ✅ (changesets) | ❌ |
| 任务缓存 | ✅✅ | ❌ | ❌ |
| 分布式执行 | ✅ | ❌ | ❌ |
| 磁盘空间优化 | ✅ | ✅✅ | ❌ |
| 学习曲线 | 中等 | 中等 | 低 |
项目规模 推荐方案
────────────────────────────
小型 npm Workspaces
中型 pnpm + Changesets
大型 Lerna + pnpm
超大型 Lerna + Nx Cloudmy-monorepo/
├── .changeset/ # Changesets 配置
│ ├── config.json
│ └── README.md
├── .github/
│ └── workflows/
│ ├── ci.yml # CI 流程
│ └── release.yml # 发布流程
├── packages/ # 公共包
│ ├── core/
│ ├── utils/
│ └── ui/
├── apps/ # 应用程序
│ ├── web/
│ ├── mobile/
│ └── docs/
├── tools/ # 开发工具
│ ├── scripts/
│ └── config/
├── pnpm-workspace.yaml
├── package.json
├── tsconfig.base.json # 共享 TypeScript 配置
├── .eslintrc.base.js # 共享 ESLint 配置
└── README.mdTypeScript 共享配置:
// tsconfig.base.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020"],
"moduleResolution": "node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
}
}// packages/core/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}ESLint 共享配置:
// .eslintrc.base.js
module.exports = {
root: true,
env: {
node: true,
es2020: true,
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
rules: {
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/explicit-function-return-type': 'off',
},
}// package.json(根目录)
{
"scripts": {
"build": "pnpm -r run build",
"test": "pnpm -r run test",
"lint": "pnpm -r run lint",
"clean": "pnpm -r run clean",
"changeset": "changeset",
"version": "changeset version",
"release": "changeset publish"
}
}// packages/core/package.json
{
"scripts": {
"build": "tsc",
"dev": "tsc -w",
"test": "jest",
"lint": "eslint src --ext .ts",
"clean": "rm -rf dist"
}
}使用构建工具的增量构建功能:
// packages/core/package.json
{
"scripts": {
"build": "tsc --incremental",
"dev": "tsc -w"
}
}# pnpm 并行构建
pnpm -r --parallel run build
# Lerna 并行构建
lerna run build --parallel注意:只在包之间无依赖时使用并行构建。
使用 Lerna 的任务缓存:
// lerna.json
{
"tasksRunnerOptions": {
"default": {
"runner": "nx",
"options": {
"cacheableOperations": ["build", "test", "lint"],
"cacheDirectory": ".cache/nx"
}
}
}
}效果:
首次构建: 全部执行(3分钟)
二次构建(无修改):命中缓存(5秒)
修改一个包:只构建受影响的包(30秒)对于大型项目,考虑使用 Turborepo:
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"test": {
"dependsOn": ["build"],
"outputs": []
}
}
}优势:
传统 npm:
项目A: 1GB node_modules
项目B: 1GB node_modules
项目C: 1GB node_modules
总计: 3GB
pnpm(硬链接):
全局存储: 500MB
项目A -> 全局存储(硬链接)
项目B -> 全局存储(硬链接)
项目C -> 全局存储(硬链接)
总计: 500MB + 少量元数据# .npmrc
shamefully-hoist=true # 提升所有依赖
node-linker=hoisted # 使用 npm 风格的提升权衡:
# pnpm-workspace.yaml
packages:
- 'packages/*'
- '!**/test/**' # 排除测试目录# .github/workflows/ci.yml
- name: Cache pnpm modules
uses: actions/cache@v3
with:
path: ~/.local/share/pnpm/store # Linux 默认路径(macOS 为 ~/Library/pnpm/store)
key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-- name: Build affected packages
run: pnpm --filter "[origin/main]" run buildstrategy:
matrix:
package: [core, ui, utils]
steps:
- name: Build ${{ matrix.package }}
run: pnpm --filter "@my-org/${{ matrix.package }}" run build# 使用 time 命令
time pnpm build
# 使用 Nx 的分析工具
npx nx graph# Lerna
npx lerna graph
# pnpm
pnpm list --depth=0 --json | npx dependency-cruiser -
# Nx
npx nx graph问题:不同包依赖同一个第三方库的不同版本。
解决方案:
// 根目录 package.json
{
"pnpm": {
"overrides": {
"lodash": "^4.17.21"
}
}
}或使用 workspace 协议统一版本:
{
"dependencies": {
"lodash": "workspace:*"
}
}问题:包 A 依赖 B,B 又依赖 A。
检测:
# 使用 madge 检测
npx madge --circular packages/解决方案:
问题:包 A 能引用包 B 的依赖,但 A 的 package.json 中没有声明。
在 pnpm 中的表现:
// 包 A 的代码
import 'lodash' // ❌ 错误:找不到模块
// 即使 lodash 在包 B 中安装了,包 A 也无法访问解决方案:
// 在包 A 的 package.json 中显式声明
{
"dependencies": {
"lodash": "^4.17.21"
}
}或者使用提升模式:
# .npmrc
shamefully-hoist=true问题:npm publish 失败,提示权限错误。
解决方案:
# 1. 检查登录状态
npm whoami
# 2. 重新登录
npm login
# 3. 检查包名是否已被占用
npm search @my-org/core
# 4. 使用正确的 registry
npm config set registry https://registry.npmjs.org/对于私有包:
// package.json
{
"publishConfig": {
"registry": "https://npm.pkg.github.com",
"access": "restricted"
}
}问题:修改了本地包的代码,但引用的包没有更新。
解决方案:
# 方案1: 重新安装依赖
rm -rf node_modules
pnpm install
# 方案2: 使用开发模式监听变化
pnpm -r run dev
# 方案3: 手动构建受影响的包
pnpm --filter "...@my-org/utils" run build问题:多个包的版本号不一致,难以管理。
解决方案:使用 Changesets 的固定版本策略。
// .changeset/config.json
{
"fixed": [["@my-org/core", "@my-org/ui", "@my-org/utils"]]
}这样这些包会保持相同的版本号。
问题:每次 CI 都要构建所有包,时间太长。
解决方案:
# 只测试变化的包
- name: Test affected packages
run: pnpm --filter "[origin/main]" run test
# 使用缓存
- uses: actions/cache@v3
with:
path: .cache
key: ${{ hashFiles('**/pnpm-lock.yaml') }}或使用 Turborepo 的远程缓存:
- name: Turbo Remote Cache
run: pnpm turbo build --token=${{ secrets.TURBO_TOKEN }}问题:TypeScript 无法正确解析 workspace 包。
解决方案:
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@my-org/*": ["packages/*/src"]
}
},
"references": [
{ "path": "./packages/core" },
{ "path": "./packages/ui" }
]
}问题:应该提交哪些文件到 Git?
最佳实践:
# .gitignore
# 依赖
node_modules/
.pnpm-store/
# 构建产物
dist/
*.tsbuildinfo
# 日志
*.log
npm-debug.log*
# 环境变量
.env
.env.local
# 编辑器
.vscode/
.idea/
# 操作系统
.DS_Store
Thumbs.db
# 测试覆盖率
coverage/
# 缓存
.cache/
.turbo/应该提交:
pnpm-lock.yaml.changeset/ 目录下的变更集文件不应该提交:
node_modules/dist/ 目录.cache/ 缓存目录问题:如何在 Monorepo 中管理私有包?
解决方案:
// package.json
{
"name": "@my-org/private-package",
"private": true, // 标记为私有
"publishConfig": {
"registry": "https://npm.pkg.github.com",
"access": "restricted"
}
}或使用 .npmrc:
# .npmrc
@my-org:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NPM_TOKEN}ERR_PNPM_NO_MATCHING_VERSION错误信息:
ERR_PNPM_NO_MATCHING_VERSION No matching version found for @my-org/core@workspace:*原因:workspace 中找不到指定的包。
排查步骤:
# 1. 检查包名是否正确
cat packages/core/package.json | grep "name"
# 2. 检查 workspace 配置
cat pnpm-workspace.yaml
# 3. 重新安装依赖
rm -rf node_modules pnpm-lock.yaml
pnpm installERESOLVE unable to resolve dependency tree错误信息:
ERESOLVE unable to resolve dependency tree原因:依赖版本冲突。
解决方案:
# 方案1: 使用 --force 强制安装
pnpm install --force
# 方案2: 清理缓存后重装
pnpm store prune
pnpm install
# 方案3: 检查并修复 lockfile
pnpm install --no-frozen-lockfilelerna ERR! EUNCOMMIT错误信息:
lerna ERR! EUNCOMMIT Working tree has uncommitted changes原因:Lerna 发布时要求工作区干净。
解决方案:
# 1. 提交或暂存更改
git add .
git commit -m "chore: commit before release"
# 或暂存
git stash
# 2. 发布
lerna publish
# 3. 恢复暂存(如果需要)
git stash pop问题:运行 changeset version 后版本号没有变化。
排查步骤:
# 1. 检查是否有变更集文件
ls .changeset/*.md
# 2. 检查变更集内容
cat .changeset/*.md
# 3. 确认包名正确
cat packages/*/package.json | grep "name"注意:变更集文件中列出的包名必须与 package.json 中的 name 完全一致。
Module not found 错误错误信息:
Error: Cannot find module '@my-org/core'排查步骤:
# 1. 检查包是否已链接
ls -la node_modules/@my-org/core
# 2. 检查包是否已构建
ls packages/core/dist
# 3. 检查 package.json 的 main 字段
cat packages/core/package.json | grep "main"
# 4. 重新链接
pnpm install问题:配置了 husky 但 hooks 不执行。
解决方案:
# 1. 重新安装 husky
pnpm add -Dw husky
# 2. 初始化 husky(v9)
npx husky init
# 3. 编辑 hook 文件(v9 移除了 husky add,直接写入命令)
echo "pnpm lint-staged" > .husky/pre-commit
# 4. 确保 package.json 有 prepare 脚本(v9 用法)
# package.json
{
"scripts": {
"prepare": "husky"
}
}# pnpm
pnpm list --depth=0 --json | jq
# 使用依赖图可视化
npx lerna graph# 使用 --dry-run 参数
lerna publish --dry-run
# changesets 可用 status 预览待发布的包(无 --dry-run 选项)
changeset status# Lerna 详细模式
lerna run build --loglevel verbose
# pnpm 详细模式
pnpm install --reporter=default# macOS/Linux
ls -la node_modules/@my-org/
# Windows
dir node_modules\@my-org\ /AL如果遇到无法解决的问题,可以尝试完全重置:
#!/bin/bash
# reset-monorepo.sh
# 删除所有 node_modules
find . -name "node_modules" -type d -prune -exec rm -rf '{}' +
# 删除所有构建产物
find . -name "dist" -type d -prune -exec rm -rf '{}' +
find . -name "*.tsbuildinfo" -type f -delete
# 删除 lockfile
rm -f pnpm-lock.yaml
# 删除缓存
rm -rf .cache
rm -rf .turbo
rm -rf .nx
# 重新安装
pnpm install
# 重新构建
pnpm buildMonorepo 是一种强大的代码组织方式,特别适合需要管理多个相关包的项目。通过合理使用 workspace、版本管理工具和构建优化策略,可以显著提升开发效率和代码质量。
关键要点:
学习资源: