【依赖】pnpm、yarn、npm

404 阅读9分钟

pnpm

前置知识

符号链接/软连接(symbolic link)

以下解释来自百度

符号链接软链接)是一类特殊的文件, 其包含有一条以绝对路径或者相对路径的形式指向其它文件或者目录的引用。[1] 符号链接最早在4.2BSD版本中出现(1983年)。今天POSIX操作系统标准、大多数类Unix系统Windows VistaWindows 7都支持符号链接。Windows 2000Windows XP在某种程度上也支持符号链接。

其实也可以理解为快捷方式,在windows 下面右键文件可以创建,也可以使用linux 命令

// ln [参数][源文件或目录][目标文件或目录]
// -s 代表创建软连接,不加代表创建硬连接
ln -s test.text test_symbolic_link.txt

image.png

硬链接

以下解释来自百度

硬链接(hard link,也称链接)就是一个文件的一个或多个文件名。再说白点,所谓链接无非是把文件名和计算机文件系统使用的节点号链接起来。因此我们可以用多个文件名与同一个文件进行链接,这些文件名可以在同一目录或不同目录。

ln -s test.text test_hard_link.txt

使用 ls -i 可以看到文件的(文件系统中的物理索引(也称为inode)

image.png

硬链接和源文件的inode 是同一个

硬链接和软链接的区别

软链接

  • 1.软链接,以路径的形式存在。类似于Windows操作系统中的快捷方式
  • 2.软链接可以跨文件系统(FAT32、NTFS...) ,硬链接不可以
  • 3.软链接可以对一个不存在的文件名进行链接
  • 4.软链接可以对目录进行链接

硬链接

  • 1.硬链接,以文件副本的形式存在。但不占用实际空间(存疑??实际上我看到还有占了空间)

image.png

  • 2.不允许给目录创建硬链接
  • 3.硬链接只有在同一个文件系统中才能创建

内容可寻址存储(Contenct-Addressable Store,CAS)

是一种存储信息的方式,与此对应的还有固定内容存储(FAS)

内容可寻址存储,也称为内容寻址存储或缩写为CAS,是一种存储信息的方式,因此可以根据其内容而不是其位置来检索信息。它已被用于高速存储和检索的固定内容,如存储,符合政府规定的文件。内容可寻址存储类似于内容可寻址内存。 具体就不展开了,只要理解两个店,他是一种存储信息的方式,常使用加密哈希函数从文档生成的摘要,以标识存储系统中的该文档

使用pnpm的好处

节省磁盘空间

当使用npm时,如果你有100个项目使用一个依赖关系,你将有100份该依赖关系的副本保存在磁盘上。使用pnpm,该依赖关系将被存储在一个内容可寻址的存储器中,因此。

如果你依赖不同版本的依赖关系,只有不同的文件会被添加到存储区。例如,如果它有100个文件,而新版本只对其中的一个文件进行了修改,那么pnpm update将只向存储区添加一个新文件,而不是仅仅为了这个单一的修改而克隆整个依赖关系。

所有的文件都保存在磁盘上的一个地方。当软件包被安装时,它们的文件被硬链接到那个地方,不消耗额外的磁盘空间。这使得你可以在不同的项目中共享同一版本的依赖关系。

因此,你可以在磁盘上节省大量与项目和依赖关系数量成比例的空间,而且你的安装速度也会快很多

以上摘自官网,我理解是pnpmyarnnpm快的原因就是所有项目的依赖在一个store, 如果这个store里面有的话,则直接用,没有的话则从远程下载,这是快和节省空间的原因

那么问题来了这个store是在哪里,下面会给出答案

加快安装速度

  • 依赖性解析。所有需要的依赖被识别,并被提取到store
  • 目录结构计算。根据依赖关系计算 node_modules 目录结构。
  • 链接依赖项。所有剩余的依赖被获取并从store硬链接到node_modules

pnpm 究竟怎么管理依赖的

首先我们明白几个点, node_modules 下面究竟有哪些部分。以下以一个nextjs demo举例

pnpm 的 node_modules 布局使用符号链接来创建依赖项的嵌套结构

image.png

pnpm虚拟目录

image.png

以平铺(是不是和npm3.x有点像,注意是平铺在.pnpm下面)的形式项目所有的依赖包(包括间接依赖,每个依赖包都可以通过.pnpm/<name>@<version>/node_modules/<name>路径找到实际位置

.pnpm/array-name@2.1.0/node_modules/array-union

image.png

还是以官网的例子,真实demo中稍微比这复杂(比如上图有些包名为何是两个包甚至多个包名合起来的??)。 假设我们直接项目安装了foo@1.0.0bar@1.0.0qar@2.0.0是间接依赖

node_modules
├── foo -> ./.pnpm/foo@1.0.0/node_modules/foo   // 直接依赖。符号链接到./.pnpm/foo@1.0.0/node_modules/foo 
└── .pnpm
    ├── bar@1.0.0
    │   └── node_modules
    │       ├── bar -> <store>/bar   // 包中的每个文件都硬链接到pnpm store中的对应文件,即<pnpm store path>/bar
    │       └── qar -> ../../qar@2.0.0/node_modules/qar // 间接依赖qar,符号链接到../../qar@2.0.0/node_modules/qar 
    ├── foo@1.0.0
    │   └── node_modules
    │       ├── foo -> <store>/foo
    │       ├── bar -> ../../bar@1.0.0/node_modules/bar
    │       └── qar -> ../../qar@2.0.0/node_modules/qar
    └── qar@2.0.0
        └── node_modules
            └── qar -> <store>/qar

再来看这个图就很好理解了

image.png

另找了一下直接依赖下面的bin文件,看不太懂

image.png

我觉得这BEFE这个总结很好

总结:pnpm使用符号链接Symbolic link(软链接)来创建依赖项的嵌套结构,将项目的直接依赖符号链接到node_modules的根目录,直接依赖的实际位置在.pnpm/<name>@<version>/node_modules/<name>,依赖包中的每个文件再硬链接(Hard link).pnpm store

pnpm store

pnpm store path  // pnpm store的存储路径

image.png

终于看到了庐山真面目了,原来真正的文件在这里。注意

  • Content-addressable store 使用 pnpm store path 可查看
  • Virtual store 也就是.pnpm 目录

pnpm install 安装的时候, 你会看到以下提示

packages are hard linked form the contenct-addressable store to the virtual store

关于pnpm store 的一些命令

  • pnpm status

查看 store 中已修改的包。

  • pnpm add

功能上等同于 pnpm add,不同之处在于它只把包加入存储中,且没有修改存储外的任何项目或文件。

  • pnpm prune

删除未被引用的包

重点是这个命令,因为store随着项目的增多,肯定会越来越多,那么如何保证不庞大呢。 删除未被引用的包, 因为

pnpm install 期间,包 foo@1.0.0 被更新为 foo@1.0.1。 pnpm 将在存储中保留 foo@1.0.0 ,因为它不会自动除去包。 如果包 foo@1.0.0 没有被其他任何项目使用,它将变为未引用。 运行 pnpm store prune 将会把 foo@1.0.0 从存储中删除 。

但还是要注意不要经常清理(我运行的时候,电脑风扇会响)

运行 pnpm store prune 是无害的,对您的项目没有副作用。 如果以后的安装需要已经被删除的包,pnpm 将重新下载他们。 最好的做法是 pnpm store prune 来清理存储,但不要太频繁。 有时,未引用的包会再次被需要。 这可能在切换分支和安装旧的依赖项时发生,在这种情况下,pnpm 需要重新下载所有删除的包,会暂时减慢安装过程

  • 更多命令

image.png

pnpm如何管理 peer dependencies

还是以官网的例子来说,

foo 有两组不同的依赖项, 一个是baz@1.0.0,另外一个是baz@1.1.0,

- foo-parent-1  
- bar@1.0.0  
- baz@1.0.0  
- foo@1.0.0  
- foo-parent-2  
- bar@1.0.0  
- baz@1.1.0  
- foo@1.0.0

如果他们有peer,就有两组不同的

node_modules  
└── .pnpm  
├── foo@1.0.0_bar@1.0.0+baz@1.0.0  
│ └── node_modules  
│ ├── foo  
│ ├── bar -> ../../bar@1.0.0/node_modules/bar  
│ ├── baz -> ../../baz@1.0.0/node_modules/baz  
├── foo@1.0.0_bar@1.0.0+baz@1.1.0  
│ └── node_modules  
│ ├── foo  
│ ├── bar -> ../../bar@1.0.0/node_modules/bar  
│ ├── baz -> ../../baz@1.1.0/node_modules/baz  
├── bar@1.0.0  
├── baz@1.0.0  
├── baz@1.1.0  

这里的依赖包名就是我之前提出疑问的,为啥包名像是组合起来,难道是因为peer dependencies,不过我还不太明白,因为我看了并没有找到peer dependencies

附[V8.0.0]发布(github.com/pnpm/pnpm/r…)

image.png

怎么管理monoRerpo

如果 bar依赖于 foo@1.0.0

pnpm 支持 workspace 协议 workspace: 。 当使用此协议时,pnpm 将拒绝解析除本地 workspace 包含的 package 之外的任何内容。 因此,如果您设置为 "foo": "workspace:2.0.0" 时,安装将会失败,因为 "foo@2.0.0" 不存在于此 workspace 中。

看下umi定义的workspace

image.png

pnpm 工作空间功能的最受欢迎的开源项目

一些问题

为什么不直接创建到全局的symbolic link, 而是使用hard link

有没有觉得硬链接是多余的,事实官网也说可行,但是有bug

image.png

issue

硬链接好像也会占用空间?

空间大小和源文件一样 Why do hard links seem to take the same space as the originals?](unix.stackexchange.com/questions/8…)

【转】关于硬链接与软连接占用磁盘空间问题的分析研究

image.png

上图的意思是

  • 创建 1 2文件夹
  • 为1文件夹新建a文件,并且往a文件输入 “foo”
  • 为2文件夹新建a文件,并且往a文件输入 “bar”
  • 为1文件夹的a文件创建软连接b
  • 创建1文件夹b文件的硬链接到2文件夹的b文件

image.png

分别用 ls -li 1 2du -h 1 2 查看 文件夹 1 2 ,可以看到 两个命令统计出不一样的结果,第二个命令4.0k就是文件a的大小

image.png

  • 1代表 inode
  • 2代表读写权限
  • 3代表硬链接数
  • 4文件所有者
  • 5文件所属组
  • 6文件大小
  • 7修改时间
  • 8文件名

在实际项目中, .pnpm 被组合起来的包名究竟是啥意思,是因为peers dependencies么

image.png

对比yarn、npm

pnpm.io/benchmarks

image.png

功能对比

pnpm.io/zh/feature-…

image.png

竞争

  • Yarn

Yarn 在 v3.1 添加了 pnpm 链接器。 因此 Yarn 可以创建一个类似于 pnpm 创建的 node_modules 目录结构。

此外,Yarn 团队计划实现内容可寻址存储,以提高磁盘空间效率。

  • npm

npm 团队决定也采用 pnpm 使用的符号链接的 node_modules 目录结构(相关 RFC)。

TODO

  • pnpm.lock文件是怎么组织的

  • npm 或者yarn 转 pnpm

  • .pnpm 被组合起来的包名究竟是啥意思,是因为peers dependencies么

  • 可交互式升级

参考

juejin.cn/post/712138…

pnpm.io/zh/motivati…