一、为什么要用Yarn管理monorepo项目

当项目规模越来越大时,我们会发现传统的多仓库管理方式开始变得笨重。想象一下,你正在开发一个电商平台,需要同时维护前端网站、移动端App、后台管理系统和公共组件库。如果每个项目都单独建仓库,会出现以下问题:

  1. 跨项目修改极其麻烦,比如修改一个公共组件需要同时在多个仓库提交代码
  2. CI/CD流程复杂,需要为每个仓库单独配置
  3. 依赖版本难以统一,容易出现版本冲突

这时候monorepo(单一代码仓库)就派上用场了。而Yarn作为Node.js生态中最流行的包管理工具之一,提供了强大的workspaces功能来支持monorepo项目结构。

// 示例项目结构 (Node.js技术栈)
my-monorepo/
├── package.json       # 根目录的package.json
├── packages/
│   ├── common/        # 公共工具库
│   │   └── package.json
│   ├── web-app/       # 前端项目
│   │   └── package.json
│   └── server/        # 后端服务
│       └── package.json
└── yarn.lock          # 统一的依赖锁文件

二、Yarn workspaces基础配置

配置一个基本的Yarn monorepo项目其实很简单。首先在根目录的package.json中添加workspaces配置:

// 根目录package.json
{
  "name": "my-monorepo",
  "private": true,  // monorepo根目录通常设为private
  "workspaces": [
    "packages/*"     // 指定workspaces目录
  ],
  "scripts": {
    "start": "yarn workspace web-app start",
    "build": "yarn workspaces run build"
  }
}

关键点说明:

  1. private: true 防止根目录被意外发布
  2. workspaces 字段定义了子包的位置模式
  3. 可以在根目录定义全局脚本,通过yarn workspace命令操作特定子包

安装依赖时,Yarn会自动将重复的依赖提升到根目录的node_modules中,这被称为"依赖提升"。比如:

# 为web-app安装react
yarn workspace web-app add react

# 为所有子包安装lodash
yarn workspaces add lodash

三、高级依赖管理技巧

3.1 处理peerDependencies

在monorepo中,peerDependencies需要特别注意。假设我们的common包声明了react作为peerDependency:

// packages/common/package.json
{
  "name": "@my/common",
  "peerDependencies": {
    "react": "^17.0.0"
  }
}

这时我们需要确保所有依赖common包的项目都使用兼容的react版本。Yarn 2+版本提供了packageExtensions来解决这类问题:

// 根目录.yarnrc.yml
packageExtensions:
  "@my/common@*":
    peerDependencies:
      react: "^17.0.0"

3.2 跨包任务执行

Yarn提供了强大的run命令来跨包执行脚本:

# 在所有子包中运行test脚本
yarn workspaces run test

# 按拓扑顺序执行build(先构建依赖项)
yarn workspaces foreach --topological --parallel run build

3.3 版本发布管理

对于需要统一发布的monorepo项目,可以使用changeset工具:

# 安装changeset
yarn add -W @changesets/cli

# 初始化changeset
yarn changeset init

# 添加变更说明
yarn changeset

四、实战案例:一个完整的项目配置

让我们看一个电商平台的完整配置示例:

// 根目录package.json
{
  "name": "e-commerce-platform",
  "version": "1.0.0",
  "private": true,
  "workspaces": [
    "packages/*",
    "libs/*"
  ],
  "scripts": {
    "start": "concurrently \"yarn workspace admin dev\" \"yarn workspace api dev\"",
    "build": "yarn workspaces foreach -pt run build",
    "test": "yarn workspaces run test"
  },
  "devDependencies": {
    "concurrently": "^7.0.0",
    "typescript": "^4.0.0"
  }
}

子包之间的依赖通过workspace协议指定:

// packages/web-app/package.json
{
  "name": "@ecom/web-app",
  "dependencies": {
    "@ecom/common": "workspace:*",  // 使用workspace协议
    "react": "^17.0.0"
  }
}

五、性能优化与常见问题

5.1 安装加速

Yarn的离线镜像可以显著提升安装速度:

# 设置离线镜像
yarn config set yarn-offline-mirror ./npm-packages-offline-cache

5.2 解决依赖冲突

当出现依赖冲突时,可以使用resolutions字段强制指定版本:

// 根目录package.json
{
  "resolutions": {
    "lodash": "4.17.21"
  }
}

5.3 缓存清理

定期清理缓存可以避免一些奇怪的问题:

yarn cache clean

六、应用场景与技术选型

6.1 适用场景

Yarn monorepo特别适合:

  • 多前端项目共享组件
  • 全栈应用(前后端同仓库)
  • 微前端架构
  • SDK与示例代码共存的项目

6.2 技术对比

与其它方案相比:

  • pnpm:磁盘效率更高,但生态略逊
  • Lerna:功能更全但配置复杂
  • npm workspaces:功能较基础

6.3 注意事项

  1. IDE支持:VS Code需要适当配置才能正确识别跨包引用
  2. 构建工具:需要确保Webpack/Rollup等支持monorepo
  3. 代码规范:建议在根目录统一配置ESLint/Prettier

七、总结与最佳实践

经过多个项目的实践,我总结了以下最佳实践:

  1. 保持根目录尽可能简洁,只包含共享配置
  2. 使用统一编码规范和提交约定
  3. 为每个子包维护详细的README
  4. 自动化版本发布流程
  5. 定期检查依赖关系,移除无用依赖

Yarn的monorepo支持虽然强大,但也需要团队遵守一定的规范。当项目规模较小时,可能感受不到它的优势;但当项目发展到一定规模后,合理的monorepo结构能显著提升开发效率和代码质量。