包管理器:npm/yarn/pnpm

364 阅读4分钟

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中会被多次安装,如下图: cmd-markdown-logo

cmd-markdown-logo

以上两种情况都有可能,取决于 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怎么体现的??????? cmd-markdown-logo

cmd-markdown-logo

像第一张图里这种带箭头的就是软链接;第二张图就能看到他实际对应的软链接地址,并且都是放在.pnpm里面了

使用pnpm安装依赖时,所有的东西其实是装在node_modules/.pnpm中的,同一个包多个版本也是平铺放在这里面(cd node_module,ls -lah可以查看): cmd-markdown-logo

痛点2:通过改变node_module的结构,解决了npm3/yarn扁平化(平铺node_module带来的依赖重复问题),也解决了幽灵依赖问题(提高安全性)

pnpm解决npm3/yarn扁平化的原理 pnpm是怎么解决版本重复安装的??????

cmd-markdown-logo 将所需的都平铺在.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中文文档

pnpm相关疑问

既然使用了软链接,为啥还要硬链接,为什么不直接符号(软)链接到全局商店 一个包在一台机器上可以有不同的依赖集。

在项目A foo@1.0.0中,可以将依赖项解析为bar@1.0.0,但在项目B中,相同的依赖项foo可能会解析为bar@1.1.0; 因此,pnpm 硬链接foo@1.0.0到使用它的每个项目,以便为其创建不同的依赖项集。

直接符号链接到全局存储将与 Node 的 --preserve-symlinks标志一起使用,但是,这种方法本身就有很多问题,所以我们决定坚持使用硬链接。有关做出此决定的原因的更多详细信息。官方回答