使用pnpm极速进入monorepos模式

1,655 阅读4分钟

前置知识:会使用或了解npm,yarn,pnpm等工具之一。

不想看背景和为什么的,请直接看实践部分

背景

近来对tripdocs编辑器项目(已开源)进行重构,目标是使他能够按需加载指定的功能。因为要让插件能够分开加载,所以我需要把插件打包多个npm包。这时候,一个问题来了,多个git仓库还是一个git仓库。

  • 多repo仓库管理 (multirepos)——一个git仓库一个项目,发布npm的时候一个仓库发一个包。
  • 单repo仓库但是多包管理 (monorepos)——一个git仓库多个个项目,发布npm的时候一个仓库里发多个包。

于是我参考了多个开源项目,最终选择了一个git仓库,多个项目。即现在流行的monorepos

tripdocs编辑器项目是基于携程内部在线文档编辑器内核,提炼的一款通用的,现代的、稳定的、支持协同的、可用于生产环境的在线文档编辑器

monorepos 与 multirepos 比较

monorepos的优点

选择monorepos的原因有多个:

方便配置

不用配置多个仓库,一个仓库搞定所有。新成员下载项目的时候,下载一个仓库就够了。

方便维护

有时候,一个改动会涉及多个包,如果使用multirepos会带来一些麻烦。

  • 修改的时候,ide(比如vscode)搜索功能和git功能都会出现一点使用障碍,比如git功能里面更加杂乱,因为展示了多个git仓库。
  • 发布的时候,如果你依赖公司内部的gitlab发布,要打开多个gitlab页面一个个点击发布。
  • 定版本的时候,统一更新版本需要到对应目录下,打tag。如果不统一,记住a包对应b包的什么版本,将会消耗额外的精力。
  • 基本配置不通用,比如lint和git hooks,如果多个项目一个个配置,这几乎是一个灾难。

其他

比如,multirepos会导致分仓库的star数量远低于主仓库。然后开源的开发者更乐意去star数量多的主仓库。

monorepos的缺点

当然,monorepos也有缺点,比如主仓库会变大,这样IDE加载时间会变长。。(我觉得完全能接受)

monorepos教程——pnpm版本

为什么要用pnpm?

因为pnpm显著加快了安装依赖的速度,减少了依赖包占用的电脑硬盘空间。

然后还因为npm和yarn做多包管理,存在两个问题。

  • phantom dependencies(幽灵依赖)
  • npm doplgangers(这里我先把它称为:npm 重复依赖)

phantom dependencies

phantom dependencies指的是某个包没有被安装(package.json 中没有,但能够引用它)。

这是因为,如果使用npm和yarn做多包管理,a包依赖b包,b包依赖c包。node_modules下结构如下:

// a包下面b和c是同级的
node_modules
    b
    c

所以你这个时候require('c')是可以运行的。

npm doplgangers

npm doplgangers指的是相同版本依赖包重复安装的现象。

在a包依赖b包,b包依赖c包(版本2)的基础上,如果a包依赖c包(版本1)。此时再安装一个d包依赖c包(版本2)。

// a包下面b和c是同级的,
node_modules
    b
        node_modules
            c(版本2c(版本1)
    d
        node_modules
            c(版本2

此时,c(版本2)就重复安装了2次,大型项目中,这很常见。他们会侵蚀你的大量电脑硬盘空间。

以上两个问题,你可以很简单的验证出来,我提供了一个demo

{
  "name": "testphantom",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "brace-expansion": "^1.1.11", // c包
    "glob": "^8.1.0",  // d包
    "minimatch": "^6.1.6" // b包
  }
}

特别的,对于npm doplgangers,你可以观察一下node_modules下glob和minimatch的node_modules中的brace-expansion。lock文件中也有体现

缺点(已修复)

2022年之前提到了 pnpm 因为软连接而不能使用的场景:

  • Electron 应用无法使用 pnpm
  • 部署在 lambda 上的应用无法使用 pnpm
  • react native打包

2022年之后官方提出了解决方案,退回yarn的包管理模式,支持无符号链接的 hoisted 的node_modules(从v6.25.0开始)

利用 monorepos 实践

项目目录


packages/
    foo
        ...
        package.json
    core
        ...
        package.json
    ...

  1. 创建pnpm-workspace.yaml 文件,内容如下
packages:
  # all packages in direct subdirs of packages/
  - 'packages/*'
  # all packages in subdirs of components/
  - 'components/**'
  # exclude packages that are inside test directories
  - '!**/test/**'
  1. 在core中package.json中使用 "foo": "workspace:../foo" 引入包。当然,你也可以这样:
{
    "dependencies": {
        "foo": "workspace:*",
        "bar": "workspace:~",
        "qar": "workspace:^",
        "zoo": "workspace:^1.5.0"
    }
}

假设他们package中的版本都是1.5.0,利用pnpm发布(pnpm publish)的时候,他们会被转化为

{  
    "dependencies": {  
        "foo": "1.5.0",  
        "bar": "~1.5.0",  
        "qar": "^1.5.0",  
        "zoo": "^1.5.0"  
    }  
}

特别的:如果你写明了版本,比如 "zoo": "^1.5.0",那么要检查本地pnpm-workspace_yaml声明的范围内,是否存在符合规则的版本。如果有,会从本地加载,否则会从远程npm仓库安装。

此时,我已经可以从core中引用foo的代码了。

so easy.

参考链接:

官网:pnpm-workspace_yaml

官网:pnpm workspaces

谈论从把多项目合并成一个项目中获得的收益

Pnpm: 最先进的包管理工具