使用 node 管理器管理 monorepo

使用 node 管理器管理 monorepo

不包含工具的使用,一方面因为我没用到过工具,另外一方面看了一下 Lerna,说 Learna 底层还是用到了 yarn 去进行管理,二者并不冲突,所以打算先学习一下基础再说。

顾名思义,monorepo 指的就是 mono(一个)repository,即使用一个 repo 去管理整个项目,也就是代码(code)和资源(assets),其结构大概为:

|- repo
|-  |- app-a
|-  |- app-b
|-  |- lib-a
|-  |- lib-b

顺便,如果使用的是 symlink,有一个好处就在于修改 lib-b 中的代码,是不需要重新进行打包的,其他的 package 可以自动获取最新的代码。

构建一个 monorepo

这里虽然会带一点使用 npm 去运行 monorepo 的方法,不过主要还是用 yarn。

基础结构为:

|- packages
|  |- module-a
|  |  |- index.js
|  |  |- package.json
|  |- module-b
|  |  |- index.js
|  |  |- package.json

模块中的 package.json 可以使用默认初始化进行生成,即 npm init -y,index.js 中的代码就是一段 log 输出 a 或者 b。

暴力方法

即将 packages 的名称改为 node_modules,node 这样会自动寻找 node_modules 目录下的包并运行,如:

在这里插入图片描述

当然这个方法不是一个长期有效的方法,尤其是项目会涉及到第三方库时,管理就会变得非常的麻烦。

毕竟大多数情况下,node_modules 中的内容都会被 .gitignore 所忽略,一个 workaround 是重写 .gitignore 文件,指定不需要被 git 忽略的文件,不过依旧,在涉及到使用一些第三方库的前提下,单独在 .gitignore 中列举文件很快就会变得难以管理。

symlink

这一部分代码不变,指令如下:

➜  monorepo git:(main)cd packages
➜  packages git:(main)cd module-b
➜  module-b git:(main)npm link

added 1 package, and audited 3 packages in 458ms

found 0 vulnerabilities

➜  module-b git:(main)cd ../module-a
➜  module-a git:(main)npm link module-b

added 2 packages, and audited 5 packages in 408ms

found 0 vulnerabilities
➜  module-a git:(main)node index.js
b
a
➜  module-a git:(main)

link 指令 npm 和 yarn 通用,这里会将当前的 package 注册为 global module,随后切换到 module-a 中去进行对应的注册,这样就可以在 module-a 中使用 module-b 了。

在这里插入图片描述

重新跑了一遍,之前有报错,有点懒得打码了

如果项目需求比较简单,这也不失为一个短期的解决方法。

workspaces

三大管理器其实都有使用 workspace,但是 yarn 出来的最早,所以这里以 yarn 优先说明。

使用 workspaces 最大的优势在于管理器可以自动进行 symlink,省去了手动管理的麻烦。同时 npm 也会尽可能地使用 作用域升(hoisting),使得项目之间可以共用同样的 dependencies,从而提升性能。

此时项目变动如下:

|- packages
|  |- module-a
|  |  |- index.js
|  |  |- package.json
|  |- module-b
|  |  |- index.js
|  |  |- package.json
|  |- package.json

模块之间的代码没有任何变化,

基础配置

package.json 更新内容如下

{
  "name": "monorepo",
  "workspaces": {
    "packages": ["packages/*"]
  },
  "private": true
}

"packages/*" 是对 packages 下面进行了模糊匹配,一个个列举出来也是可以的。

关于 private 这个变量,官方文档其实有说明:

Worktrees used to be required to be private (ie list "private": true in their package.json). This requirement got removed with the 2.0 release in order to help standalone projects to progressively adopt workspaces (for example by listing their documentation website as a separate workspace).

如果 yarn 的版本为 v2 以上则可以不用添加,运行结果:

➜  monorepo git:(main)yarn --version
1.22.19
➜  monorepo git:(main)yarn
yarn install v1.22.19
info No lockfile found.
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
✨  Done in 0.10s.
作用域提升

这个是使用 workspace 的优势之一,如 a 和 b 同时都会使用 相同的包,与其在每个模块中安装对应的 lodash,yarn/npm 会将下载的依赖包放到项目顶部的 node_modules 中,如:

➜  monorepo git:(main)cd packages/module-a
➜  module-a git:(main)yarn add lodash
yarn add v1.22.19
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...

success Saved lockfile.
success Saved 1 new dependency.
info Direct dependencies
info All dependencies
└─ lodash@4.17.21
✨  Done in 1.97s.
➜  module-a git:(main)cd ../module-b
➜  module-b git:(main)yarn add lodash
yarn add v1.22.19
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...

success Saved 1 new dependency.
info Direct dependencies
info All dependencies
└─ lodash@4.17.21
✨  Done in 0.44s.

效果如下:

在这里插入图片描述

如果二者装的依赖版本不一样,那么其中一个项目会将该依赖安装与当前项目中的 node_modules 中:

➜  module-b git:(main)yarn add lodash@3
yarn add v1.22.19
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...

success Saved lockfile.
success Saved 1 new dependency.
info Direct dependencies
info All dependencies
└─ lodash@3.10.1
✨  Done in 4.50s.

在这里插入图片描述

如果想要在当前项目中禁止 hoisting,也可以直接在 package.json 中修改:

{
  "name": "monorepo",
  "workspaces": {
    "packages": ["packages/*"],
    "nohoist": []
  }
}

一般来说默认不会被 hoist 的项目是一些会与二进制打交道的项目,比如说 react-native、electron 之类的跨平台实现。。

运行脚本

这是一个比较少见的 usecase,不过也肯定会存在。

这里的假设就是,module-b 的作用类似于 cli,需要将整体代码打包让其他的项目去运行这个模块。

这里首先修改 module-b 的 index.js:

#!/usr/bin/env node
// 上面这段代码告知 npm/yarn 下面代码需要直接用 node 去执行

console.log('module b is running');

随后更新 package.json:

{
  "name": "module-b",
  "bin": "./index.js"
}

随后在 terminal 重新安装一下,更新 symlink:

➜  packages git:(main)yarn install --force
yarn install v1.22.19
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Rebuilding all packages...
success Saved lockfile.
✨  Done in 0.21s.

这里必须要用 --force 去强行执行一下更新,否则 yarn 会因为 dependency 没有变动而忽略掉 bin 中的内容。

最后就可以在直接脚本中运行 module-b,如:

{
  "name": "monorepo",
  "workspaces": {
    "packages": ["packages/*"],
    "nohoist": []
  },
  "private": true,
  "scripts": {
    "start": "module-b"
  }
}
➜  packages git:(main)yarn start
yarn run v1.22.19
$ module-b
module b is running
✨  Done in 1.05s.

如果要重命名 module-b 导出的模块,可以将 bin 中的内容修改成对象:

{
  "name": "module-b",
  "bin": {
    "mdb": "./index.js"
  }
}

重新运行一下 yarn install --force 后,就可以以 "scripts": { "start": "mdb" } 的语法去运行 module-b 了。

同时,因为这个脚本是在全剧的 node_modules 下进行的 symlink,因此该项目下所有的包都可以找到并运行这个脚本。

workspace 指令

刚才运行的指令其实都有在切换一些 repo,但是 yarn 有提供 workspace 指令,可以直接在某一个目录中使用 yarn workspace module command 去进行操作,如:

# 一直在根目录 monorepo 下
➜  monorepo git:(main)yarn workspace module-a add lodash-contrib
yarn workspace v1.22.19
yarn add v1.22.19
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...

success Saved lockfile.
success Saved 1 new dependency.
info Direct dependencies
info All dependencies
└─ lodash-contrib@4.1200.1
✨  Done in 2.36s.
✨  Done in 3.30s.
➜
➜  monorepo git:(main)yarn workspace module-a start
yarn workspace v1.22.19
yarn run v1.22.19
$ mdb
module b is running
✨  Done in 1.00s.
✨  Done in 1.90s.
➜
➜  monorepo git:(main)yarn workspace module-b build
yarn workspace v1.22.19
yarn run v1.22.19
$ node ./index.js
module b is running
✨  Done in 0.90s.
✨  Done in 1.75s.

其他管理器

大部分的使用是通的,这里会列举一些细微的差别。

npm

需要注意的是,在 npm v7 以前,这个功能是不支持的,可以使用 yarn。

npm v6 应该可以粗略对标为 node v14,我看了下我本地的 14 用的是 6.14.12,node v16 用的是 8.19.2,目前最新的 npm 版本在 v9,应该对标的是 node v18?

想要具体查看当前的 node version 还是需要使用指令查看:

➜  module-a git:(main) ✗ nvm use 14
Now using node v14.16.1 (npm v6.14.12)
➜  module-a git:(main)npm --version
6.14.12
➜  module-a git:(main) ✗ nvm use 16
Now using node v16.17.1 (npm v8.19.2)
➜  module-a git:(main)npm --version
8.19.2

根目录下的 package.json 省去其他可以不需要的自动生成属性后如下:

{
  "name": "monorepo",
  "workspaces": ["packages/module-a", "packages/module-b"]
}

随后在根目录下运行 npm i:

➜  monorepo git:(main)npm i

added 2 packages, and audited 5 packages in 531ms

found 0 vulnerabilities

⚠️:虽然官方文档上用的是数组,不过我直接用 yarn 的 package.json,以对象的方式也是可以运行的。

workspace 指令的细微差异

使用 npm 的话,workspaceworkspaces 是作为一个 flag 存在的:

➜  monorepo git:(main)npm --workspace=module-a run start

> module-a@1.0.0 start
> mdb

module b is running

相对而言会比 yarn 要麻烦一些。

不过 npm 可以在同时运行所有名称相同的脚本,如:

➜  monorepo git:(main)npm --workspaces run start

> module-a@1.0.0 start
> mdb

module b is running
npm ERR! Lifecycle script `start` failed with error:
npm ERR! Error: Missing script: "start"

Did you mean one of these?
    npm star # Mark your favorite packages
    npm stars # View packages marked as favorites

To see a list of scripts, run:
  npm run
npm ERR!   in workspace: module-b@1.0.0
npm ERR!   at location:

为了预防这种错误,npm 也提供了一个 flag 去跳过不存在的指令:

➜  monorepo git:(main)npm --workspaces --if-present  run start

> module-a@1.0.0 start
> mdb

module b is running

注意指令上的细微不同,单独运行一个 module 用的是 workspace,运行所有的指令使用的是 workspaces

⚠️:目前 npm 不支持使用 nohoist,虽然出现版本冲突的处理方式依旧是一个会 hoist,另一个不会。

pnpm

yarn 和 npm 之间的配置还挺像的,但是 pnpm 的话会要求在 monorepo 的根目录下创建一个 pnpm-workspace.yaml 的文件:

packages:
  - packages/*

第二步是要检查版本,现在 a 中引用了 b,但是 pnpm 不像 npm/yarn 那样直接做 symlink,而是通过 virtual store 去进行版本管理,这个时候就要手动维护一下版本的一致性,下面就是 a 中的 package.json:

{
  "name": "module-a",
  "dependencies": {
    "module-b": "workspace:*"
  }
}

最后,pnpm 没有 workspace 这个 flag,而是使用 --filter 这个 flag:

➜  monorepo git:(main)pnpm i
Scope: all 3 workspace projects
Packages: +4
++++
Packages are hard linked from the content-addressable store to the virtual store.
  Content-addressable store is at: /Users/Library/pnpm/store/v3
  Virtual store is at:             node_modules/.pnpm
Progress: resolved 4, reused 4, downloaded 0, added 4, done
➜
➜  monorepo git:(main)pnpm -F "module-a" run start

> module-a@1.0.0 start /Users/study/monorepo/packages/module-a
> mdb

module b is running

pnpm 的优点在于,如果该脚本不存在,那么 pnpm 会直接忽略而不是报错。另外,因为 pnpm 使用的是 virtual store,因此也不存在 hoisting。

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值