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

1
2
3
4
{
  "private": true,
  "workspaces": ["workspace-a", "workspace-b"]
}

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

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

1
2
3
4
5
6
7
8
{
  "name": "workspace-a",
  "version": "1.0.0",

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

workspace-b/package.json

1
2
3
4
5
6
7
8
9
{
  "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