npm/yarn install原理和现存的弊端
分为两个部分:
- 执行 npm/yarn install之后,包如何到达项目 node_modules 当中
- node_modules 内部如何管理依赖
包如何到达项目 node_modules 当中
执行命令后,首先会构建依赖树,然后针对每个节点下的包,会经历下面四个步骤:
- 将依赖包的版本区间解析为某个具体的版本号
- 下载对应版本依赖的 tar 包到本地离线镜像
- 将依赖从离线镜像解压到本地缓存
- 将依赖从缓存拷贝到当前目录的 node_modules 目录
依赖在 node_modules 内部中是什么样的目录结构
在npm1,npm2中结构树如下:
node_modules
└─ foo
├─ index.js
├─ package.json
└─ node_modules
└─ bar
├─ index.js
└─ package.json
如果 bar 当中又有依赖,那么又会继续嵌套下去。那么就造成了以下问题:
- 依赖层级太深,会导致文件路径过长
- 大量重复的包被安装,文件体积大
npm3和yarn通过
扁平化依赖解决了以上问题:
现在的依赖都被拍平到node_modules目录下,不再有很深层次的嵌套关系,如下:
node_modules
├─ foo
| ├─ index.js
| └─ package.json
└─ bar
├─ index.js
└─ package.json
这样在安装新的包时,根据 node require 机制,会不停往上级的node_modules当中去找,如果找到相同版本的包就不会重新安装,解决了大量包重复安装的问题,而且依赖层级也不会太深。
但是又造成了新问题,假设存在以下这种依赖,被依赖包版本不同:
.
├── package-a
│ ├── lodash@4.0.0
├── package-b
│ ├── lodash@4.0.0
├── package-c
│ ├── lodash@3.0.0
├── package-d
│ ├── lodash@3.0.0
那么在pnpm3/yarn中会被多次安装,如下图:
以上两种情况都有可能,取决于 package-a,package-b,package-c,package-d 在 package.json中的位置,如果 package-a,package-b 声明在前面,那么就是前面的结构,否则是后面的结构。
这就造成了lodash@4.0.0或者lodash@3.0.0一定有一个会被重复安装,造成体积变大。
而pnpm就解决了这个问题,改变了npm3/yarn的目录结构,采用软链接方式,节省了空间。并且解决了幽灵依赖的问题
pnpm
特点
- 节省空间(软/硬链接)
- 速度快(软/硬链接)
- 安全性高(幽灵依赖)
- 支持monorepo(一个仓库内可以同时管理多个子应用)
主要解决的痛点
痛点1:通过软链接,硬链接 的方式,解决了node_module 的体积占用问题,节省了空间的问题
软链接:指向源文件的指针,是个单独的文件,仅仅几个字节,拥有独立的inode
硬链接:与源文件同时指向一个物理地址,与源文件共享存储数据,与源文件拥有相同的inode
pnpm中怎么看是软,硬链接????????node_module怎么体现的???????
像第一张图里这种带箭头的就是软链接;第二张图就能看到他实际对应的软链接地址,并且都是放在.pnpm里面了
使用pnpm安装依赖时,所有的东西其实是装在node_modules/.pnpm中的,同一个包多个版本也是平铺放在这里面(cd node_module,ls -lah可以查看):
痛点2:通过改变node_module的结构,解决了npm3/yarn扁平化(平铺node_module带来的依赖重复问题),也解决了幽灵依赖问题(提高安全性)
pnpm解决npm3/yarn扁平化的原理 pnpm是怎么解决版本重复安装的??????
将所需的都平铺在.pnpm中,再引用软链接。
pnpm查找规则:可以看到在/node_modules/.pnpm/antd@4.20.0_react-dom@16.14.0+react@16.14.0/node_modules/antd中需要依赖classnames:pnpm会在/node_modules/.pnpm/antd@4.20.0_react-dom@16.14.0+react@16.14.0/node_modules/antd去require classnames,在做这个过程中,又会去目录上一级的node_modules里面找classnames(/node_modules/.pnpm/antd@4.20.0_react-dom@16.14.0+react@16.14.0/node_modules),那么找到后,这个classnames又是一个软链接,这种方式可以防止版本重复。更好理解的解释
相当于有依赖就会去互相引用软链接,这种方式可以防止版本重复。而且这样能复用的也多,体积自然就小了
幽灵依赖是什么意思??????:即某个包没有在package.json 被依赖,但是用户却能够引用到这个包。 (如:当使用npm3/yarn时会有lodash@x.x.x,但是用pnpm安装时候是很干净的):
.node_modules/package-a
.node_modules/package-b
.node_modules/package-c
.node_modules/package-d
不会出现.node_modules/package-d和.node_modules/lodash@x.x.x同级的情况,所以解决了幽灵依赖的问题,同时也解决了安全性的问题
pnpm相关疑问
既然使用了软链接,为啥还要硬链接,为什么不直接符号(软)链接到全局商店 一个包在一台机器上可以有不同的依赖集。
在项目A foo@1.0.0中,可以将依赖项解析为bar@1.0.0,但在项目B中,相同的依赖项foo可能会解析为bar@1.1.0; 因此,pnpm 硬链接foo@1.0.0到使用它的每个项目,以便为其创建不同的依赖项集。
直接符号链接到全局存储将与 Node 的 --preserve-symlinks标志一起使用,但是,这种方法本身就有很多问题,所以我们决定坚持使用硬链接。有关做出此决定的原因的更多详细信息。官方回答