前端基建工具(pnpm)

110 阅读6分钟

官方文档地址?看文档更准确。

答:www.pnpm.cn/


什么是 pnpm?

答:PNPM,全称为“Performant npm”,是一个高性能的包管理器,旨在优化npm和yarn的性能和使用场景。


那为什么要选 pnpm ,而不是 npm 或 yarn 呢?给出理由

答:如下↓


  1. 节省磁盘空间

pnpm以其快速和高效的特点而著称。它使用并行下载和安装依赖项的方式,大大减少了项目的构建时间。pnpm通过从单个内容可寻址存储器链接node_modules中的文件,提高了磁盘空间的使用效率,避免了同一个依赖项不同版本之间的重复安装,从而节省了磁盘空间。

当使用 npm 或 Yarn 时,如果你有 100 个项目,并且所有项目都有一个相同的依赖包,那么, 你在硬盘上就需要保存 100 份该相同依赖包的副本。然而,如果是使用 pnpm,依赖包将被存放在一个统一的位置

如果你对同一依赖包需要使用不同的版本,则仅有版本之间不同的文件会被存储起来。例如,如果某个依赖包包含 100 个文件,其发布了一个新 版本,并且新版本中只有一个文件有修改,则 pnpm update 只需要添加一个 新文件到存储中,而不会因为一个文件的修改而保存依赖包的 所有文件。

所有文件都保存在硬盘上的统一的位置。当安装软件包时,其包含的所有文件都会硬链接自此位置,而不会占用 额外的硬盘空间。 这让你可以在项目之间方便地共享相同版本的依赖包。

  1. 安装速度快

在多数场景下,pnpm 包安装的速度都是明显优于 npm/yarn,速度会快 2-3 倍。

image.png

  1. 非扁平 node_module 结构

当使用 npm/yarn 安装依赖包时,所有软件包都将被提升到 node_modules 的根目录下。pnpm在包管理上非常严格,确保每个包只能访问其package.json中指定的依赖项。这种严格性有助于减少不必要的依赖和冲突。同时,pnpm还提供了一个名为pnpm-lock.yaml的锁文件,用于确保安装的依赖项具有确定性,即在相同的环境下,使用相同的pnpm-lock.yaml文件可以安装相同的依赖项版本。

这样会有几个问题:幽灵依赖、不确定性、依赖分身,我们逐个解析~

  • 幽灵依赖

幽灵依赖的定义是:子依赖提升造成的,虽然不会出现在package.json中(声明缺失),但是仍可以在项目中正常被 import,即可以访问非法(未声明的) npm 包。

比如 A@1.0.0 依赖 B@1.0.0

node_modules
├── A@1.0.0
├── B@1.0.0
└── C@1.0.0
  └── node_modules
    └── B@2.0.0

在package.json中,没有 B 依赖的声明,因为它是子依赖,被扁平化 提升 到了和 A 一个层级而已。

{
  "dependencies": {
    "A": "^1.0.0",
    "C": "^1.0.0"
  }
}

虽然没有在package.json中声明,但在项目中引用 B 还是能正常工作的,如果某天某个版本的 A 依赖不再依赖 B 或者 B 的版本发生了变化,那么就会造成依赖缺失或兼容性问题。

  • 不确定性

不确定性是指:同样的 package.json 文件,install 依赖后可能不会得到同样的 node_modules 目录结构

沿用上面的例子,A 依赖 B@1.0,C 依赖 B@2.0,在 package.json 安装 B 依赖,究竟应该提升 B 的 1.0 还是 2.0?就有两种情况了:

node_modules
├── A@1.0.0
├── B@1.0.0
└── C@1.0.0
  └── node_modules
    └── B@2.0.0
node_modules
├── A@1.0.0
│ └── node_modules
│   └── B@1.0.0
├── B@2.0.0
└── C@1.0.0

这样就取决于用户的安装顺序了,或者要删掉重装,否则导致扁平化处理中提升不一致,导致“不确定性”。

  • 依赖分身

依赖分身指的是:相同版本的依赖被重复安装

在上例基础上,我们假设再新增两个依赖关系:A 和 D 都依赖 B@1.0.0、C 和 E 都依赖 B@2.0.0

关系演变成:

node_modules
├── A@1.0.0
├── B@1.0.0
├── D@1.0.0
├── C@1.0.0
│ └── node_modules
│   └── B@2.0.0
└── E@1.0.0
  └── node_modules
    └── B@2.0.0

可以看到 B@2.0 会被安装两次,实际上无论提升 B@1.0.0 还是 B@2.0.0,都会被重复安装。

默认情况下,pnpm 则是通过使用符号链接的方式仅将项目的直接依赖项添加到 node_modules 的根目录下,默认非扁平结构,因此代码无法对任意软件包进行访问。既保证了安全性,又解决了非法访问依赖、不确定性、重复安装的问题。

  1. 支持monorepos

除了上述的速度、效率和严格性外,pnpm还具有其他优势。例如,它支持单体仓库(monorepo),这意味着可以在一个项目中管理多个子项目的依赖关系。

  • pnpm 内置了对单个源码仓库中包含多个软件包的支持,默认重点

华丽分割线


怎么使用?

答:跟 npm install 类似,安装项目下所有的依赖。

支持多平台,包括Windows、Linux和macOS。pnpm还提供了与npm相似的配置方式,用户可以轻松地将npm配置迁移到PNPM。

image.png

npm install 过程

image.png

pnpm

基于npm扁平化node_modules的结构下,虽然解决了依赖嵌套、重复安装的问题,但多重依赖和幽灵依赖并没有好的解决方式。

image.png pnpm出现就是为了解决现在npm 存在的问题,正如官网pnpm 所形容自己的是一款速度快,节省磁盘空间的软件包管理器。

image.png

前置知识 软链接&硬链接

硬链接就是多个文件名指向了同一个文件,这多个文件互为硬链接。

像是JS 中的两个相同的对象,a 和b 的真实内容指向堆中同一个地址,修改一个,同时改变,一荣俱荣,一损俱损。删除一个,并不影响另一个。

let a = {test:1} 
let b = a
a.test = 2
console.log(b) // {test:2}

软链接就是快捷方式,是一个单独文件。

就像我们电脑桌面上的快捷方式,大小只有几字节,指向源文件,点击快捷方式,其实执行的就是源文件。

pnpm node_modules的层级结构

进行pnpm i 之后,node_modules的层级结构如下

双键头代表硬链接

单箭头代表软链接

node_modules
|_ A -> .pnpm/A@1.0.0/node_modules/A
|_ B -> .pnpm/B@1.0.0/node_modules/B
|_ .pnpm
  |_ A@1.0.0
    |_ node_modules
      |_ A => pnpm/store/A 
      |_ C -> ../../C@1.0.0/node_modules/C
  |_ B@1.0.0
    |_ node_modules
      |_ B => pnpm/store/B 
      |_ C -> ../../C@2.0.0/node_modules/C
  |_ C@1.0.0
    |_ node_modules
      |_ C => pnpm/store/C 
  |_ C@2.0.0
    |_ node_modules
      |_ C => pnpm/store/C

以A 包为例,A的目录下并没有node_modules,是一个软链接,真正的文件位于 .pnpm/A@1.0.0/node_modules/A 并硬链接到全局store中。

A 和 B 是我们在项目package.json中声明的依赖包,node_modules除了A,B 没有其他包,说明不是扁平化结构。也就不存在 幽灵依赖的问题

.pnpm 中存放着所有的包。最终硬链接指向指向全局pnpm 仓库里的store目录下。

也就是说,我们所有的包,最终都以硬链接的形式,最终都在全局 pnpm/store 中,可以使得不同的项目从全局 store 寻找到同一个依赖,大大节省了磁盘空间

如果上面这个文件列表不够直观,可以参看如下结构图

参考链接

pnpm官网 pnpm.io/zh/

软链、硬链对 Node.js 包寻找的影响 meixg.cn/2021/01/25/…

前端包管理工具 npm yarn cnpm npx juejin.cn/post/710244…

pnpm 有什么优势 q.shanyue.tech/engineering…