多 git 仓库开发如何维护 package.json

1,031 阅读3分钟

主流的 monorepo 做法是只有一个 git 仓库,然后建立一个 packages 目录,每个子目录都是一个 npm package,有自己的 package.json。

如果我们不用这个主流做法,而是每个 npm package 都有一个自己的 git 仓库,该如何维护 package.json 文件?

问题

假设我们有 pkg-a,pkg-b,pkg-c 三个包,彼此有依赖关系。

列举可能遇到的问题:

  • 本地开发的时候如何让 pkg-a,pkg-b,pkg-c 依赖的都是本地正在开发的版本?
  • 本地开发能够选择 pkg-a,pkg-b,pkg-c 的指定版本进行集成开发测试?
  • 在 CI 服务器上跑测试的时候,pkg-a 如何找到 pkg-b,pkg-c,并使用正确的版本?
  • 如何频繁手工维护 package.json 升版本号?一旦忘记升版本,就导致一个包安装出两个版本来?

分析

拆分成多个 git 仓库之后,所有问题的实质都是单个 git 仓库的代码都是不完整的,必须组合了其依赖之后,才能跑单元测试

每个包,都有两个形态

  • git 仓库形态:代码从 git 服务器 clone 下来
  • npm 包形态:代码从 npm registry 下载下来

yarn 具有 workspaces 的功能。可以写一个 project 级别的大 package.json,把一堆小的 workspaces 给整合起来,选择每个包是从 git 拉,还是从 npm registry 来拉。

下面我们来看具体的解法。

解法

pkg-a 的 package.json 这么写

{
    "name": "pkg-a",
    "version": "0.1.0",
    "peerDependencies": {
        "pkg-b": "*",
        "pkg-c": "*"
    }
}

这么写的道理是:

  • version 填 0.1.0 是你当前开发所基于的上一个版本的版本号。发布的时候产生新的 git commit,修改这个 version 字段,并打 git tag
  • peerDependencies 指定对 pkg-b,pkg-c 的依赖,表示 pkg-a 本身并不明确依赖一个固定的版本号,或者版本范围,这样就不需要频繁维护 package.json 文件来升级 dependencies 字段指定的版本号

然后建立这样的本地开发目录结构

project ->
    package.json
    packages ->
        pkg-a (git submodule)
        pkg-b (git submodule)
        pkg-c (git submodule)

在 project/package.json 这么写

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

workspaces 是 yarn 的功能。当在 project 目录下执行 yarn install 的时候。会让 pkg-a,pkg-b,pkg-c 互相都依赖本地 workspaces 里指定的文件夹。

packages 目录下的 pkg-a 和 pkg-b 这些都是 git submodule,需要独立 git commit 和 git push。

如果不希望使用本地的 pkg-c,而是指定一个 npm registry 上的版本,可以不建立 packages/pkg-c 这个 submodule,然后修改一下 package.json

{
    "private": true,
    "name": "project",
    "workspaces": ["packages/*"],
    "dependencies": {
        "pkg-c": "0.1.0"
    }
}

这样我们就可以避免 submodule 越来越多的问题。同时也避免了单 git 仓库的 monorepo 越来越大的问题。可以自由选择把多少 pkg 用 git submodule 拉到本地来协同修改。

那么在 CI 服务器上,pkg-a 如何跑自己的单元测试呢?只有 peerDependencies 无法 yarn install 自己的依赖啊。

可以在 pkg-a 的目录下,新建一个 pkg-a/ci/package.json 文件

{
    "private": true,
    "workspaces": [".."],
    "scripts": {
        "postinstall": "cd .. && rm -rf node_modules && ln -s ci/node_modules"
    },
    "dependencies": {
        "pkg-b": "git://github.com/xxx/pkg-b.git",
        "pkg-c": "git://github.com/xxx/pkg-c.git"
    },
    "resolutions": {
        "pkg-b": "git://github.com/xxx/pkg-b.git",
        "pkg-c": "git://github.com/xxx/pkg-c.git"
    }
}

在 ci 脚本中,切换到 pkg-a/ci 子目录去做 yarn install,而不是在 pkg-a 自己这层目录做 yarn install

name: Continuous Integration

on: [push]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v1
        with:
          node-version: 15.5.0
      - run: cd ci && yarn
      - run: yarn ci

如果不加 resolutions 也可以,就是会报 warning。

其中 postinstall 这个 script 是为了修复 pkg-a/node_modules 里的内容不正确的问题。一般 workspaces 是不会写 ".." 这样的路径的。属于 yarn 没有考虑过的使用方式。

结论

  • 使用 peerDependencies 和 * 放宽对依赖的版本约束,免得天天改 package.json
  • 使用 yarn workspaces 链接本地的多个开发中的 package 目录
  • 给 CI 跑测试单独建一个 ci/package.json 指定 CI 时的依赖版本
  • 抄作业:github.com/rotcare/rx-…