skip to content
OnionTalk

使用Yarn Workspace管理多project repo

node的包管理方式和js社区重复造轮子的坏习惯一直为人所诟病。Yarn的横空出世很好的解决了单个package.json之下依赖重复的问题。对于多个package.json的项目,有没有办法解决呢?答案是Yes,Yarn workspace能很好的帮你做到这一点。

背景

管理过在一个 javascript repo 下面有多个子 project 的同学可能都遇到过依赖管理困难的问题。对于一些公用的包(比如 node-sass),我们想尽可能保证版本的一致,享受 yarn 的 hoist 特性,同时也不想下载多次。但是对于一些特殊的包,我们又想保持各个 repo 的独立性。 在 Yarn 1.0 版本以前,以上念想其实是很去实现的,我们不得不花费大量的人力去维护我们各个子 project 的依赖包关系。但是 Yarn 在 1.0 版本引入了workspace这个特性之后,这些问题就不再存在了。

什么是 workspace

yarn 官方对于 workspace 的定义是It allows you to setup multiple packages in such a way that you only need to run yarn install once to install all of them in a single pass. 。简而言之,workspace 能帮助你更好的管理有多个子 project 的 repo。你既可以在每个子 project 下使用独立的 package.json 管理你的依赖,又可以享受一条 yarn 命令安装或者升级所有依赖的都便利性。

workspace 能帮你做什么

简而言之,workspace 能帮你做这么两件事情

简化你的工作流

当你有多个子 project 的时候,你可能做的最多的事情就是分别进入每个文件夹,然后执行yarn install 或者 yarn upgrade foo。 但是引入 workspace 之后你需要的只是一条在根目录运行yarn install或者yarn upgrade foo命令,你的所有的依赖都会被安装或者更新。

降低包安装和包升级的成本

这里讲的成本主要指的是时间成本和管理的精力成本。

时间成本

在使用 workspace 之前,你想要安装整个 repo 的依赖时,你需要独立安装每个子 project 的依赖。这就意味着,如果你的 repo 下面同时以来于 node-sass,你需要重复下载三次 node-sass 的安装包。当你的子 project 或者包依赖并不是特别多的时候,这些时间成本似乎影响并不是那么大。但是随着你的 repo 越来越大,依赖越来越复杂,消耗的时间也会变得越来越恐怖。 在引入 workspace 之后,yarn 会帮助你分析各个子 project 里面的依赖关系,保证所有子 project 里面共同的依赖只会被下载和安装一次,大大缩减安装的时间。

精力成本

设想我们引用了一个有严重安全漏洞的包 foo,我们想要将整个 repo 中的这个包都升级到最新版本来修复这个严重的安全漏洞。在没有使用 workspace 之前,你需要仔细且耐心的遍历每个子 project 的 package.json 文件,然后手动执行升级操作。 在引入 workspace 之后,你大可以在 repo 的根目录简单运行一条yarn upgrade foo命令,你的所有子 project 中依赖的 foo 包都会自动升级到 package.json 里面指定的版本。 当然,如果你想单独的升级某个子 project 的包,可以简单的使用yarn workspace <workspace-name> upgrade … 来做到。

怎么使用 workspace

yarn workspace 并不需要安装什么其他的包,只需要简单的更改 package.json 便可以工作。 首先我们需要确定workspace root,一般来说 workspace root 都会是 repo 的根目录。

package.json

{
  "private": true,
  "workspaces": ["workspace-a", "workspace-b"]
}

首先需要注意的是,repo 根目录下的 private 必须设置成 true,否则 workspace 不会被起用。 在 package.json 中我们新加入了 workspaces 这个属性,其值是一个字符串数组,每一个字符串指代了一个 workspace 的路径,这些路径支持 glob 匹配。需要注意的是,这里的路径指向指的是 package.json 所在文件夹文件夹名。而对于各个子 project,我们并不需要做其他的配置。

接下来,我们开始申明依赖 workspace-a/package.json

{
  "name": "workspace-a",
  "version": "1.0.0",

  "dependencies": {
    "cross-env": "5.0.5"
  }
}

workspace-b/package.json

{
  "name": "workspace-b",
  "version": "1.0.0",

  "dependencies": {
    "cross-env": "5.0.5",
    "workspace-a": "1.0.0"
  }
}

接下来在根目录执行 yarn install

{{

}}

你会发现整个 repo 只生成了一份 yarn.lock,绝大多数的依赖包都被提升到了根目录下的 node_modules 之内。各个子 project 的 node_modules 里面不会重复存在依赖,只会有针对根目录下 cross-env 的引用。不仅如此,你会发现,对于 repo 内部的依赖关系(比如 workspace-b 依赖于 workspace-a),yarn 也能很好的进行管理。

一些使用 yarn workspace 的小 tips

  1. 尽可能多的保证子 project 的包版本一致,可能的情况下,不要让某个子 project 依赖于某个特定版本的包。在这种情况下,yarn 会尽可能的帮你做 hoist,减少安装成本。同时,当你想要升级这个包的时候,一切也会变得非常方便。
  2. 对于一些包,由于没法做 hoist(比如 react-native),我们可以使用nohoist属性进行声明。
  3. 如果在上面的例子中,如果 workspace-b 中依赖的 workspace-a 的版本并不是 1.0.0,那么 yarn 会从 github 而不是从你本地去安装 workspace-a 这个依赖。

workspace 有哪些不足和限制?

  1. yarn workspace 并没有像lerna那样封装大量的高层 API,整个 workspace 整体上还是依赖于整个 yarn 命令体系。
  2. workspace 不能嵌套(只能有一个根 workspace)
  3. workspace 采用的是向上遍历,所以 workspace 并不能识别根 workspace 之外的依赖。

参考资料

yarn workspace 官方文档

yarn workspace nohoist

yarn workspace command