背景

我们之前项目上,每个项目都需要安装隔离的npm包,在每个项目上,都需要在个人开发的电脑上npm install安装package中管理的包
后来我们搭建了自己的nexus私库,初步的想法就是在指定相同版本号,或者公司内网限制的前提下,大家可以在私库缓存的包中更新指定版本的包到自己项目的本地,方便开发统一安装的包,并且不报错。

但是后来很多项目中的package中有的包是^@1.2.3这样支持基于仓库上最新的包自动更新的管理方式,由于安装的时机不一样,所以很多时候安装下来的包都有差异。

疑问

能不能统一维护一个npm包在多个不同的项目中,这样每次运行一个新的项目,是不是就不需要在项目中执行npm install装相应的包了,而且也不存在磁盘中重复包的安装。

Yarn workSpace工作区

Yarn 从 1.0 版开始支持 Workspace (工作区)。

优点

  • Workspace 能更好的统一管理有多个项目的仓库,你所有的项目依赖将被安装在一起,这样可以让 Yarn 来更好地优化它们。
  • 每个项目下使用独立的 package.json 管理自己的依赖
  • 只需要运行一次 yarn install 就可以安装或者升级所有依赖等。
  • 更重要的是可以使多个项目共享同一个 node_modules 目录,提升开发效率和降低磁盘空间占用。
  • Yarn 将使用一个单一的 lock 文件,而不是每个项目多有一个,这意味着更少的冲突和更容易进行代码检查

一句话总结就是可以大大简化对多个项目的统一管理。很多知名的开源项目也使用了 Yarn Workspace,如 vuereactjest 等。

1. 如何使用?

【Step 1】

package.json 文件中添加以下内容,从现在开始,我们将此目录称为 “工作区根目录”:
package.json

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

请注意,private: true 是必需的!工作区本身不应当被发布出去,所以我们添加了这个安全措施以确保它不会被意外暴露。

【Step 2】

创建这个文件后,再创建两个名为 workspace-aworkspace-b 的子文件夹。 在每个文件夹里面,创建一个具有以下内容的 package. json 文件:
workspace-a/package.json:

  1. {
  2. "name": "workspace-a",
  3. "version": "1.0.0",
  4. "dependencies": {
  5. "cross-env": "5.0.5"
  6. }
  7. }

workspace-b/package.json:

  1. {
  2. "name": "workspace-b",
  3. "version": "1.0.0",
  4. "dependencies": {
  5. "cross-env": "5.0.5",
  6. "workspace-a": "1.0.0"
  7. }
  8. }

【Step 3】

最后,在某个地方运行 yarn install ,当然最好是在工作区根目录里面。如果一切正常,你现在应该有一个类似这样的文件层次结构:

  1. /package.json
  2. /yarn.lock
  3. /node_modules
  4. /node_modules/cross-env
  5. /node_modules/workspace-a -> /workspace-a
  6. /workspace-a/package.json
  7. /workspace-b/package.json

就是这样 ! workspace-b需要一个在workspace-a中的文件,现在将直接使用当前项目内部的文件,而不是直接去从 Github 上面取。 cross-env 包已正确去重并放在项目的根目录下,让 workspace-aworkspace-b可以一起使用这个包。

这会产生很多不良的问题:

  1. 如果 workspace-a和 workspace-b有相同的依赖项目 a,a 都会各自下载一次,这不仅耗时降低开发效率,还额外占用重复的磁盘空间;当 project 项目比较多的时候,此类问题就会显得十分严重。
  2. 如果 project2 依赖 project1,而 project1 并没有发布到 npm 仓库,只是一个本地项目,有两种方式配置依赖:

    1. 使用相对路径(如 file: 协议)在 project2 中指定 project1 的依赖。
    2. 使用 yarn|npm link 来配置依赖。

      第 1 种方式缺少版本号的具体指定,每次发布版本时都需要相应的依赖版本的修改;第 2 种方式需要自行手工操作,配置复杂易出错。 需要 npm-2.0.0+ 才支持模块间的相对路径依赖,详见 npm 官方文档 package.json/Local Paths

  3. 没有一个统一的地方对全部项目进行统一构建等,需要到各个项目内执行 yarn|npm build 来构架项目。

使用 Yarn Workspace 之后,上述问题都能得到很好的解决。而且这是 Yarn 内置的功能,并不需要安装什么其他的包,只需要简单的在 projects 目录(Yarn 称之为 workspace-root)下增加如下内容的 package.json 文件即可。
projects/package.json:

  1. {
  2. "private": true,
  3. "workspaces": ["project1", "project2"] // 也可以使用通配符设置为 ["project*"]
  4. }

开源社区则都基本上使用 "workspaces": ["packages/*"] 的目录结构,这与 Lerna 的目录结构一致。

在 workspace-root 目录下执行 yarn install

  1. $ cd projects
  2. $ rm -r project1/node_modules
  3. $ rm -r project2/node_modules
  4. $ yarn install
  5. yarn install v1.22.0
  6. info No lockfile found.
  7. [1/4] 🔍 Resolving packages...
  8. [2/4] 🚚 Fetching packages...
  9. [3/4] 🔗 Linking dependencies...
  10. [4/4] 🔨 Building fresh packages...
  11. success Saved lockfile.
  12. Done in 0.56s.

此时查看目录结构如下:

  1. projects/
  2. |--package.json
  3. |--project1/
  4. | |--package.json
  5. |--project2
  6. | |--package.json
  7. |--node_modules/
  8. | |--a/
  9. | |--project1/ -> ./project1/

说明:

  • projects 是各个子项目的上级目录,术语上称之为 workspace-root,而 project1 和 project2 术语上称之为 workspace
  • yarn install 命令既可以在 workspace-root 目录下执行,也可以在任何一个 workspace 目录下执行,效果是一样的。
  • 如果需要某个特殊的 workspace 不受 Yarn Workspace 管理,只需在此 workspace 目录下添加 .yarnrc 文件,并添加如下内容禁用即可:

    1. workspaces-experimental false
  • 在 project1 和 project2 目录下并没有 node_modules 目录(特殊情况下才会有,如当 project1 和 project2 依赖了不同版本的 a 时)。

  • /node_modules/project1/project1 的软链接,软链接的名称使用的是 /project1/package.json#name 属性的值。
  • 如果只是修改单个 workspace,可以使用 --focus 参数来快速安装相邻的依赖配置从而避免全部安装一次。

    2. 可用的 Yarn Workspace 命令

    2.1. yarn workspace

    针对特定的 workspace 执行指定的 <command>,如:
  1. $ yarn workspace project1 add vue --dev project1 添加 vue 开发依赖
  2. $ yarn workspace project1 remove vue project1 移除 vue 依赖

{workspace}/package.json#scripts 中定义的脚本命令,也可以作为 <command> 来执行。
下面是一个利用这个特点创建统一构建命令的例子:
projects/package.json:

  1. {
  2. "scripts": {
  3. "build": "yarn workspaces run build"
  4. }
  5. }

project1|project2/package.json:

  1. {
  2. "scripts": {
  3. "build": "rollup -i index.js -f esm -o dist/bundle.js"
  4. }
  5. }

执行 yarn build 的结果:

  1. $ yarn build
  2. yarn run v1.22.0
  3. $ yarn workspaces run build
  4. > project1
  5. $ rollup -i index.js -f esm -o dist/bundle.js
  6. index.js dist/bundle.js...
  7. created dist/bundle.js in 70ms
  8. > project2
  9. $ rollup -i index.js -f esm -e project1 -o dist/bundle.js
  10. index.js dist/bundle.js...
  11. created dist/bundle.js in 80ms
  12. Done in 2.45s.

2.2. yarn workspaces

2.2.1. yarn workspaces run <command>

在每个 workspace 下执行 <command>。如:

  1. yarn workspaces run test

将会执行各个 workspace 的 test script。

2.2.2. yarn workspaces info [--json]

显示当前各 workspace 之间的依赖关系树。

  1. $ yarn workspaces info
  2. yarn workspaces v1.21.1
  3. {
  4. "project1": {
  5. "location": "project1",
  6. "workspaceDependencies": [],
  7. "mismatchedWorkspaceDependencies": []
  8. },
  9. "project2": {
  10. "location": "project2",
  11. "workspaceDependencies": [
  12. "project1"
  13. ],
  14. "mismatchedWorkspaceDependencies": []
  15. }
  16. }
  17. Done in 0.12s.