一、为什么要用Yarn workspace管理多包项目

想象一下你正在开发一个电商平台,需要同时维护前端React组件库、后端Node.js微服务和共享的工具函数库。如果每个项目单独放在不同仓库,会出现这样的烦恼:

  1. 改个工具函数要同时在三个仓库提交代码
  2. 本地调试时需要反复npm link
  3. 团队新人要clone十几个仓库才能开始开发

Yarn workspace就像个智能文件夹管理器,它可以把多个相关项目放在同一个大仓库(monorepo)里,还能自动处理它们之间的依赖关系。我们来看个实际例子:

// 技术栈:Node.js + Yarn
// 项目结构示例:
my-monorepo/
  ├── package.json
  ├── packages/
  │   ├── frontend/      # 前端项目
  │   │   └── package.json
  │   ├── backend/       # 后端API
  │   │   └── package.json
  │   └── utils/         # 共享工具库
  │       └── package.json

二、快速搭建你的第一个workspace

让我们从零开始搭建。首先确保安装了Yarn 1.x以上版本,然后执行:

mkdir my-monorepo && cd my-monorepo
yarn init -y

接着修改根目录的package.json:

{
  "name": "my-monorepo",
  "private": true,
  "workspaces": ["packages/*"],  // 关键配置:声明workspace目录
  "scripts": {
    "start": "yarn workspace frontend start"
  }
}

现在创建三个子包:

mkdir -p packages/{frontend,backend,utils}

# 初始化前端项目
cd packages/frontend && yarn init -y
echo "console.log('前端启动');" > index.js

# 初始化后端项目 
cd ../backend && yarn init -y
echo "console.log('API服务启动');" > index.js

# 初始化工具库
cd ../utils && yarn init -y
echo "exports.add = (a,b) => a + b;" > math.js

三、让包之间产生化学反应

假设前端需要用到工具库的add方法,传统方式要发布npm包或者用npm link。在workspace里只需:

// 在packages/frontend/package.json中添加
{
  "dependencies": {
    "utils": "1.0.0"  // 版本号要与utils包的package.json一致
  }
}

然后运行yarn install,神奇的事情发生了:

  • Yarn会自动创建软链接,把utils映射到node_modules
  • 修改utils代码会实时生效,不需要重新安装
  • 所有依赖会被提升到根node_modules,避免重复安装

我们可以在前端代码中直接使用:

// packages/frontend/index.js
const { add } = require('utils/math');
console.log(`1 + 2 = ${add(1, 2)}`);

四、进阶技巧:跨包脚本与依赖管理

  1. 统一运行命令:在根package.json中添加:
{
  "scripts": {
    "build:all": "yarn workspaces run build",
    "test:all": "yarn workspaces run test"
  }
}
  1. 处理peerDependencies:当多个包依赖不同版本的React时,可以这样解决:
// 在根package.json中添加
{
  "resolutions": {
    "react": "17.0.2",
    "react-dom": "17.0.2"
  }
}
  1. 选择性安装:只想给某个包装依赖?
yarn workspace frontend add lodash

五、实际开发中的场景与技巧

场景1:调试本地依赖 当utils包修改后,不需要发布或重新链接,前端项目能立即使用最新代码。

场景2:统一代码风格 在根目录添加.eslintrc和.prettierrc,所有子包继承配置。

场景3:CI/CD优化 只构建改动的包,比如通过git diff检测变更:

# 只构建发生变更的包
changed_packages=$(git diff --name-only HEAD~1 | grep 'packages/' | cut -d/ -f2 | uniq)
for pkg in $changed_packages; do
  yarn workspace $pkg build
done

六、优缺点与注意事项

优点

  • 依赖管理更智能,减少磁盘空间占用
  • 跨项目修改更安全,所有代码在一起
  • 统一工具链配置更方便
  • 适合组件库+演示项目协同开发

缺点

  • 仓库体积会随时间增长
  • 需要规范提交信息(建议用commitizen)
  • IDE可能需要额外配置(VSCode需要打开根目录)

注意事项

  1. 不要直接修改node_modules中的workspace包代码
  2. 慎用yarn add不加-W参数,可能会装错地方
  3. 子包之间避免循环依赖
  4. 建议用changesets管理版本发布

七、版本发布的艺术

发布workspace项目需要特殊处理,推荐流程:

  1. 安装changesets工具:
yarn add -W @changesets/cli
yarn changeset init
  1. 生成变更记录:
yarn changeset  # 交互式选择要发布的包
  1. 版本提升与发布:
yarn changeset version
git push --follow-tags
yarn workspaces foreach run publish

八、总结与最佳实践

经过实际项目验证,我们总结出这些经验:

  1. 按功能而非技术划分子包(如payment-service而非node-server
  2. 共享配置放在根目录(eslint、jest、typescript配置)
  3. 使用TurboRepo或Nx加速构建
  4. 重要项目独立CI流水线
  5. 文档统一放在根目录docs文件夹

记住:monorepo不是银弹,适合跨团队协作的中大型项目。如果是小型独立项目,传统多仓库可能更简单。