包管理与 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 配置文件,同时支持多种配置,还没有传统方案的幽灵依赖和分身的问题。