认识
一、认识
PNPM(Performant Node Package Manager)
是一个用于管理 Node.js
项目依赖的包管理工具。它被设计用于替代传统的 NPM(Node Package Manager)
,以提供更高效的依赖管理和更好的性能。PNPM
的核心理念是通过 符号链接 和 全局存储 来优化 依赖管理,解决 NPM
在大型项目中常见的性能和管理问题。
二、核心
2.1 硬链接
硬链接(Hard Link
) 是在文件系统中创建的指向某个文件数据块的引用。与符号链接不同,硬链接是文件系统级别的链接,它们指向相同的物理数据块,而不是文件的路径。因此,硬链接是对文件内容的直接引用,而符号链接是对文件路径的引用。
PNPM
使用硬链接来提高性能和节省磁盘空间。具体来说,PNPM
在安装依赖时,会在项目的 node_modules
目录中创建指向全局存储中实际包文件的硬链接。这样做的好处是避免了重复下载和存储相同的包内容,同时提高了文件访问的效率。
PNPM
硬链接的工作原理
-
全局存储: 当第一次下载某个依赖包时,
PNPM
会将其存储在全局存储目录中(通常在~/.pnpm-store
)。 -
创建硬链接: 在项目的
node_modules
目录中,PNPM
会创建指向全局存储中相应包文件的硬链接。这些硬链接指向相同的数据块,因此不会占用额外的磁盘空间。 -
依赖结构: 使用硬链接后,项目的
node_modules
目录中依赖项看起来像是独立文件,但实际上它们共享相同的数据块。
PNPM
硬链接优点
-
节省磁盘空间: 相同版本的包只会在全局存储中保存一次,通过硬链接共享这些包,避免重复存储,节省了磁盘空间。
-
提高文件访问速度: 由于硬链接是文件系统级别的引用,文件访问速度比符号链接更快。
-
一致的数据管理: 修改硬链接或原文件,数据同步更新,确保了一致性。
2.2 符号链接
在 PNPM
中,符号链接(Symbolic Links, symlinks
) 是用于在文件系统中创建指向另一个文件或目录的引用。这种机制允许在一个位置创建一个链接,指向存储在另一个位置的实际内容。在 PNPM
的上下文中,符号链接用于将 node_modules
目录中的依赖项链接到存储在 .pnpm
子目录中的实际包内容。这种方法有助于高效地管理依赖项,节省磁盘空间,并避免版本冲突。
符号链接: 每个项目的 node_modules
目录中的依赖项不是实际的包副本,而是指向全局存储中相应包的符号链接。PNPM
使用符号链接将项目的 node_modules
目录中的包链接到 .pnpm
目录中存储的实际包内容。这意味着在项目的 node_modules
目录中看到的包实际上是指向 .pnpm
目录中存储的内容的快捷方式。
PNPM
符号链接的工作原理
假设我们有一个项目,其 package.json
文件如下:
{
"dependencies": {
"express": "^4.17.1",
"lodash": "^4.17.21"
}
}
当运行 pnpm install
时,PNPM
的工作流程如下:
-
解析依赖树: 读取
package.json
文件并解析所有的依赖项及其版本要求。 -
检查和下载包: 检查全局存储中是否已经存在所需版本的包,如果不存在则下载这些包并存储到全局存储中。
-
创建
.pnpm
目录: 在项目的node_modules
目录下创建.pnpm
子目录,并将所有包存储在这个子目录中,按照包名和版本号进行组织。 -
创建符号链接: 在
node_modules
目录中创建指向.pnpm
目录中相应包的符号链接。
生成的目录结构:
project
└── node_modules
├── .pnpm
│ ├── express@4.17.1
│ │ ├── node_modules
│ │ │ └── ...
│ ├── lodash@4.17.21
│ └── ...(其他依赖)
├── express -> .pnpm/express@4.17.1/node_modules/express
└── lodash -> .pnpm/lodash@4.17.21/node_modules/lodash
-
.pnpm
目录:实际的包内容存储在node_modules/.pnpm
子目录中,按照包名和版本号进行组织。 -
符号链接: 在
node_modules
目录中,express
和lodash
是符号链接,指向.pnpm
目录中的实际包内容。
PNPM
符号链接优点
-
节省磁盘空间: 相同版本的包只会在
.pnpm
目录中存储一次,通过符号链接共享这些包,避免重复安装,节省了磁盘空间。 -
依赖隔离: 符号链接确保了每个包使用其特定版本的依赖项,避免了版本冲突和相互干扰
-
提高安装速度: 全局存储和符号链接机制避免了重复下载和解压包,提高了安装速度
-
一致的依赖管理: 符号链接和
.pnpm
目录结构使得依赖关系更加清晰和可控,便于管理和调试。
2.3 隔离依赖
隔离依赖: 每个包的依赖项存储在 .pnpm
子目录中,并且包之间的依赖关系通过符号链接来管理。这种方式确保了依赖项之间的严格隔离,避免了不同包之间的版本冲突。
2.4 全局存储
全局存储: PNPM
使用一个全局存储(例如 ~/.pnpm-store
)来存放所有安装过的包。
三、特点
3.1 节省磁盘空间和带宽
NPM
在每个项目中都会安装和保存相同依赖的多个副本,导致大量磁盘空间被占用。而 PNPM
通过全局存储和符号链接机制避免了重复安装相同版本的包,大大节省了磁盘空间。
-
符号链接机制
:PNPM
使用符号链接来管理依赖,这意味着相同版本的依赖项只会在全局存储中保存一次,而不会在每个项目中重复存储,大大节省了磁盘空间。 -
快速的网络传输
: 当一个包已经存在于全局存储中时,PNPM
只需创建符号链接,而不需要下载和安装该包的副本,节省了带宽和下载时间。
3.2 快速的安装和更新
NPM
每次安装相同的依赖项时都需要重新下载和解压。PNPM
由于使用了 符号链接 和 全局存储 和 并行安装 提高了安装速度。PNPM
在安装相同版本的依赖时无需重新下载和解压,大幅提升了安装速度。
3.3 严格的版本控制和依赖隔离
NPM
的扁平依赖结构可能导致版本冲突。PNPM
使用硬链接和符号链接来确保依赖项的版本隔离,即使不同项目依赖相同的包,它们也不会互相干扰,避免了版本冲突带来的问题。
3.4 创建非扁平的 node_modules 目录结构
创建非扁平的 node_modules
目录结构: PNPM
的 node_modules
目录结构与 NPM
不同,它不会创建一个完全扁平的目录结构,而是会在 node_modules
目录下创建一个 .pnpm
子目录来存储包的内容,并通过符号链接将依赖项链接到项目的 node_modules
目录中。这种结构确保了依赖项之间的严格隔离,避免版本冲突。
示例: 假设我们有一个项目,该项目依赖于 express
和 lodash
包,并且 express
依赖于 accepts
包。
-
安装依赖
pnpm install express lodash
-
目录结构: 安装完成后,项目的目录结构可能如下:
project
└── node_modules
├── .pnpm
│ ├── express@4.17.1
│ │ ├── node_modules
│ │ │ ├── accepts@1.3.7
│ │ │ └── ...
│ ├── lodash@4.17.21
│ └── ...
├── express -> .pnpm/express@4.17.1/node_modules/express
└── lodash -> .pnpm/lodash@4.17.21/node_modules/lodash-
.pnpm
目录中包含express
和lodash
的实际包内容。 -
node_modules
目录中包含指向.pnpm
子目录中相应包的符号链接。 -
express
的依赖(例如accepts
)也存储在.pnpm
目录中,并且express
包中包含指向其依赖的符号链接。
-
在使用 PNPM
时,项目的 node_modules
目录将通过符号链接指向 .pnpm
子目录中存储的实际包内容,确保依赖项之间的隔离和高效共享。这样可以减少磁盘空间的使用,并且在依赖项之间建立清晰的关系,避免版本冲突。
通过这种非扁平结构,PNPM
能够更高效地管理和共享依赖项,避免了重复安装相同版本的包,从而提升了安装速度和节省了磁盘空间。
四、工作
假设我们有一个项目,其中 package.json
文件中列出了以下依赖项:
{
"dependencies": {
"express": "^4.17.1",
"lodash": "^4.17.21"
}
}
运行 pnpm install
时,PNPM
的工作流程如下:
4.1 解析依赖树
PNPM
会首先读取项目的 package.json
文件,解析出所有的依赖项及其版本要求。接着,它会解析出这些依赖项的依赖树,确保所有的依赖项及其子依赖项都被正确解析和记录。
在本例中, PNPM
解析 express
和 lodash
的依赖关系。解析出 express
的子依赖项,例如 accepts
、body-parser
等。
4.2 检查和下载包
PNPM
会检查全局存储中(通常在 ~/.pnpm-store
目录下)是否已经存在所需版本的包。如果所需包已经存在,它将跳过下载步骤。如果包不存在,PNPM
会从注册表(通常是 npm registry
)中下载包并存储到全局存储中。
在本例中, PNPM
检查全局存储中是否已经有 express
和 lodash
及其子依赖项。如果全局存储中没有这些包,PNPM
会从 npm registry
下载这些包,并存储到全局存储中。
4.3 创建 .pnpm 目录
在项目的 node_modules
目录下,PNPM 会创建一个 .pnpm
子目录。这个目录将包含所有依赖项的实际包内容,按照包名和版本号进行组织。每个包的目录中还会包含其自身的 node_modules
目录,其中存储着该包的依赖项。
在本例中,在项目的 node_modules
目录下创建 .pnpm
子目录。将 express
、lodash
及其子依赖项存储到 .pnpm
目录中。
4.4 创建符号链接
PNPM
会在项目的 node_modules
目录中创建符号链接,这些链接指向 .pnpm
目录中的实际包内容。这样,当你在项目中导入一个依赖项时,Node.js
会通过符号链接找到实际的包内容。
在本例中,在项目的 node_modules
目录中创建指向 .pnpm
目录中相应包的符号链接。
project
└── node_modules
├── .pnpm
│ ├── express@4.17.1
│ │ ├── node_modules
│ │ │ ├── accepts@1.3.7
│ │ │ └── ...
│ ├── lodash@4.17.21
│ └── ...
├── express -> .pnpm/express@4.17.1/node_modules/express
└── lodash -> .pnpm/lodash@4.17.21/node_modules/lodash
4.5 更新 shrinkwrap.yaml 文件
PNPM
会生成或更新项目的 shrinkwrap.yaml
文件(类似于 NPM
的 package-lock.json
文件)。这个文件记录了所有安装的依赖项及其版本,以及它们的依赖关系。这有助于确保在未来安装或更新依赖项时能够重现相同的依赖树。
4.6 最终的 node_modules 非扁平目录结构
假设项目依赖于 express
和 lodash
,并且 express
依赖于 accepts
:
project
└── node_modules
├── .pnpm
│ ├── accepts@1.3.7
│ ├── express@4.17.1
│ │ ├── node_modules
│ │ │ └── accepts@1.3.7
│ ├── lodash@4.17.21
│ └── ...(其他依赖)
├── accepts -> .pnpm/accepts@1.3.7/node_modules/accepts
├── express -> .pnpm/express@4.17.1/node_modules/express
└── lodash -> .pnpm/lodash@4.17.21/node_modules/lodash
-
.pnpm
目录 所有包的实际内容存放在.pnpm
目录中,按照包名和版本号进行组织。 -
node_modules
目录中包含指向.pnpm
目录中实际包内容的符号链接。例如,express
在node_modules
中是一个指向.pnpm/express@4.17.1/node_modules/express
的符号链接。 -
同一版本的包只存储一次,通过符号链接进行共享,节省了磁盘空间。
PNPM
非扁平结构: PNPM
使用了一种非扁平结构,它在 node_modules
目录下创建一个 .pnpm
子目录,实际的包内容存储在这个子目录中,然后通过 符号链接(symlink
) 将依赖项链接到项目的 node_modules
目录中。这种结构确保了依赖项的隔离,避免了版本冲突。PNPM
非扁平结构 通过 符号链接 和 .pnpm
目录隔离依赖项,确保不同项目之间的依赖项互不干扰,避免版本冲突, 全局存储和符号链接机制避免了重复安装相同版本的包, 节省了磁盘空间。全局存储和并行安装提高了安装速度。但是,PNPM
需要处理符号链接,可能会对一些工具和脚本造成兼容性问题。
六、问题
6.1 PNPM 的弊端?
-
调试问题: 所有项目引用的包都在全局一个地方,如果想对某个包进行调试,其他项目正好引用了,本地运行也会收到影响。
-
兼容问题: 即软连接的方式可能会在
windows
存在一些兼容的问题,但是针对这个问题,pnpm
也提供了对应的解决方案:在win
系统上使用一个叫做junctions
的特性来替代软连接,这个方案在window
上的兼容性要好于symlink
6.2 NPM node_modules 扁平结构和 PNPM node_modules 的非扁平结构式如何理解的?
NPM
扁平结构: 在 NPM
的扁平结构中,node_modules
目录尽可能地将依赖项安装在根目录中,以减少嵌套层级。这种扁平化的安装方式旨在避免过深的目录结构和长路径问题。NPM
扁平结构 可以避免过深的目录嵌套,减少了路径长度的问题。安装方式也很简单。但是,可能导致版本冲突。多个包依赖不同版本的同一依赖项时,NPM
可能会将它们扁平化到同一目录下,造成版本冲突。而且重复安装依赖项。相同的依赖项可能在多个项目中重复下载和安装,浪费磁盘空间。
PNPM
非扁平结构: PNPM
使用了一种非扁平结构,它在 node_modules
目录下创建一个 .pnpm
子目录,实际的包内容存储在这个子目录中,然后通过 符号链接(symlink
) 将依赖项链接到项目的 node_modules
目录中。这种结构确保了依赖项的隔离,避免了版本冲突。PNPM
非扁平结构 通过 符号链接 和 .pnpm
目录隔离依赖项,确保不同项目之间的依赖项互不干扰,避免版本冲突, 全局存储和符号链接机制避免了重复安装相同版本的包, 节省了磁盘空间。全局存储和并行安装提高了安装速度。但是,PNPM
需要处理符号链接,可能会对一些工具和脚本造成兼容性问题。