包管理与 Monorepo
npm、 yarn、 pnpm 的区别
版本
目前我们常说的 npm
指的是 npm3+
版本。以上三者的区别即 node
包管理方式的区别。
npm2 的问题
npm2
存在的问题是对于node
包管理是嵌套形式,从而导致有些node
包依赖路径特别深,在window
中会报错路径长度超过限制。且依赖包的子包即使是相同版本也会被安装多次,浪费空间时间。
npm3 和 yarn 特性
npm3
和yarn
都是为了解决npm2
的嵌套层级问题的,两者实现的方式一致,即将node
包扁平化,统一下载到node_modules
目录下,可以解决嵌套路径问题和重复下载包问题。
npm3 和 yarn 新的问题
扁平结构会引发了另外的问题:幽灵依赖 以及 分身。
- 幽灵依赖
- 由于
node
依赖包之间是扁平的,可以预见的是,我们在项目中安装了一个node
包,但这个node
包本身又依赖了其他的node
包,那么其他的node
包也会被下载到我们项目的node_modules
目录下,根据npm
包的查找规则,业务代码中可以直接使用node_modules
下的包,但实际上这些包在package.json
中是不存在的,更无法指定包版本,与我们开发的直觉不符。 - 幽灵依赖的问题不止于此,例如在项目中的开发依赖
devDependencies
中安装了一个包,这个包依赖的其他包也被提升到node_modules
下,如果被我们在项目中直接引入使用的话,到生产环境就会报错包找不到,导致生产事故。
- 由于
- 分身
- 扁平化会提升包所在的目录层级,但是存在同一个包的不同版本,
npm
则会选择一个版本提升到node_modules
下,其他版本嵌套安装,这就导致在项目中引用不同包的时候其实是两个不同的实例,最终可能导致项目出错。
- 扁平化会提升包所在的目录层级,但是存在同一个包的不同版本,
pnpm 特性
同样是为了解决 npm2
的包嵌套问题,pnpm
采用了另外一种 node
包管理方式,可以避开 npm3
和 yarn
存在的问题。总结来说就是,pnpm
是通过自动硬链接和软链接来实现包的管理。
- 软链接:
pnpm
安装一个包时,并不会像npm3
那样将所有的依赖扁平化管理,node_modules
目录下只会有安装的包,其依赖包则会通过 软链 的形式链接到 node_modules/.pnpm 内部,在.pnpm
内部去详细管理依赖的版本,从而解决 幽灵依赖 和 分身 的问题。.pnpm
内部也是扁平化管理的,但是允许不同的版本依赖平铺到一个层级。
- 硬链接:
.pnpm
目录的一级文件,都是通过直接 硬链接 ,链接到全局的存储空间,从而可以实现多个项目共用一份依赖。
软链和硬链
计算机中我们文件夹中的文件实际上是一个指针,但这个指针并不是直接指向我们在磁盘中存储的文件的位置,而是指向一个 inode
块,inode
中存储着文件在磁盘中的各种信息,一般我们的文件指针指向对应文件的 inode
,这类链接称为 硬链接。
但还有一种链接,它存储的并不是实际的值,而是一个硬链接的地址,称为 软链接。
monorepo
monorepo
即仓库管理模式中的 单仓 模式,与传统的一个项目一个仓库来进行版本管理的方式不同,单仓模式是将多个项目使用一个仓库来进行管理。好处在于,对于有依赖关系的项目可以进行统一的版本管理,统一的代码权限,代码风格一致,一次提交可以修改多个项目。
那怎样进行依赖包管理
传统方案 yarn 和 lerna 的问题
常用的 lerna
和 yarn workspace
,这些 monorepo
管理工具主要解决在一个仓库中管理多个 包 遇到的问题:
- 第三方依赖重复安装,工作区
package A
和B
都引用了C
,会在两个包中安装两遍C
,造成时间空间浪费。 - 模块之间的引用,如果一个工作区的不同
package
需要互相引用,需要手动link
操作。
无论是 yarn 还是 lerna 都可以总结为:
- 将所有
package
的依赖都以扁平化的方式安装在工作区的根目录node_modules
,同时对于同一个依赖的不同版本,将其中一个版本安装到根目录,其他版本嵌套安装到 各自package
下的node_modules
,解决依赖不同版本的冲突问题。 - 通过将各个
package
都软链到根目录node_modules
,各个package
利用node
的递归查找机制,可以导入其他package
,不需要手动link
。 - 通过将各个
package
中的node_modules
的bin
文件夹软链到根目录中的node_modules
,保证每个package
的npm script
能正常运行。
虽然解决了核心问题,但是又引入了其他问题:
- 幽灵依赖被放大,全部依赖都扁平化到根目录
node_modules
,由于 node 的递归查找,你可以访问到任何其他package
的依赖,以及依赖的依赖。 - 分身更容易出现,大量依赖的依赖不同版本,随机出现在
node_modules
的第一层或依赖的依赖中。
pnpm
pnpm
内置了对 monorepo
的支持,只需在工作空间根目录创建 pnpm-workspace.yaml
和 .npmrc
配置文件,同时支持多种配置,还没有传统方案的幽灵依赖和分身的问题。