前置知识:会使用或了解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)。
代码语言:javascript复制// a包下面b和c是同级的,
node_modules
b
node_modules
c(版本2)
c(版本1)
d
node_modules
c(版本2)
此时,c(版本2)就重复安装了2次,大型项目中,这很常见。他们会侵蚀你的大量电脑硬盘空间。
以上两个问题,你可以很简单的验证出来,我提供了一个demo
代码语言:javascript复制{
"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 实践
项目目录
代码语言:javascript复制packages/
foo
...
package.json
core
...
package.json
...
- 创建
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/' ```
在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)的时候,他们会被转化为
代码语言:javascript复制{
"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: 最先进的包管理工具