Pnpm包管理机制

147 阅读4分钟

在这里插入图片描述

pnpm包管理机制理解

上图是pnpm官网的🌰,在项目根目录安装依赖包bar,而bar依赖了foo。点开项目根目录的node_modules,可以看到我们在项目中安装的bar,以及.pnpm文件夹。 那么pnpm是如何维护依赖的嵌套关系的呢?

  1. 首先在.pnpm文件夹以平铺的方式存储了项目中所有的依赖包,建立硬链接。命名的方式如下:
.pnpm/<organization-name>+<package-name>@<version>/node_modules/<name>

// 组织名(若无会省略)+包名@版本号/node_modules/名称(项目名称)

其中.pnpm中的包中的node_modules下面的包中的每个文件都是内容可寻址存储的硬链接。将foo和bar都放到node_modules文件夹,可以保证包能自行导入自己。

node_modules
└── .pnpm
    ├── foo@1.0.0
    │   └── node_modules
    │       └── foo -> 硬链接 到<store>/bar 
    │           ├── index.js
    │           └── package.json
    └── bar@1.0.0
        └── node_modules
            └── bar -> 硬链接 到<store>/bar 
                ├── index.js
                └── package.json
  1. 建立符号链接,由于bar依赖了foo,因此foo被符号链接到bar@1.0.0/node_modules文件夹下,
node_modules
└── .pnpm
    ├── foo@1.0.0
    │   └── node_modules
    │       └── foo -> 硬链接 到 <store>/bar 
    │           ├── index.js
    │           └── package.json
    └── bar@1.0.0
        └── node_modules
            └── bar -> 硬链接 到 <store>/bar 
            │    ├── index.js
            │    └── package.json
            └── foo -> 符号链接 到 .pnpm/foo@1.0.0 
  1. 处理直接依赖,bar被符号链接到根目录的node_modules文件夹
node_modules
├── bar -> 符号链接 到 ./.pnpm/bar@1.0.0/node_modules/bar 
└── .pnpm
    ├── foo@1.0.0
    │   └── node_modules
    │       └── foo -> 硬链接 到 <store>/bar
    │           ├── index.js
    │           └── package.json
    └── bar@1.0.0
        └── node_modules
            └── bar -> 硬链接 到 <store>/bar 
            │    ├── index.js
            │    └── package.json
            └── foo -> 符号链接 到 .pnpm/foo@1.0.0  
  1. pnpm的包管理机制符合node寻址机制

nodejs的寻址方式:

对于核心模块(core module) => 绝对路径 寻址

node标准库 => 相对路径寻址

第三方库(通过npm安装)到node_modules下的库(可以在node环境中输入module.paths查看):

3.1. 先在当前路径下,寻找 currentProject/node_modules/xxx

3.2 递归从下往上,到上级路径寻找,例如 ../node_modules/xxx

3.3 循环步骤3.2

3.4 在全局环境路径下寻找,例如 .node_modules/xxx

3.5 在用户目录下寻找,例如 users/Daming/.node_modules/xxx 或者 users/Daming/node_libraries/xxx

3.6 node安装目录下查找,例如 nodejs/lib/node/.node_modules/xxx

在项目中我们引入bar的时候,会在当前项目根目录的node_modules找bar,从projectRoot/node_modules/bar这个符号链接找到bar的真实文件projectRoot/node_modules/.pnpm/bar@1.0.0/node_modules/bar(硬链接即可看做真实存在的文件)。 接着bar依赖了foo,根据node的寻址机制,会从里到外找foo,最后在这个路径projectRoot/node_modules/.pnpm/bar@1.0.0/node_modules/foo找到了,这个是一个符号链接,会定位到projectRoot/node_modules/.pnpm/foo@1.0.0

pnpm优势

解决了幽灵依赖的问题

yarn和npm中的幽灵依赖:由于在yarn或npm中采用扁平化的安装包的方式,即所有的包(包括依赖的依赖)都被提升到了项目根目录中的node_modules中,那么项目中可以直接引用到不在package.json中声明的包,那么在包更新的时候,可能会导致问题。

而pnmp中的项目根目录的node_modules中只有项目直接依赖的包,所以不会有这个问题。

路径过长的问题

在 npm@3 之前,npm采用的嵌套结构管理包,node_modules结构是干净、可预测的,因为node_modules 中的每个依赖项都有自己的node_modules文件夹。但是这样的管理方式会导致路径过程超出window的限制。 在npm@3和yarn中,采用了扁平化结构管理包(解决了路径过长的问题,导致了幽灵依赖的问题)。

pnpm利用符号链接解决了这个问题。

继续上面的🌰

添加qar@2.0.0作为foo和bar的依赖项

node_modules
├── bar -> ./.pnpm/bar@1.0.0/node_modules/bar 符号链接
└── .pnpm
    ├── foo@1.0.0
    │   └── node_modules
    │       └── foo -> 硬链接 到 <store>/bar 
    │           ├── index.js
    │           └── package.json
    │       └── qar -> 符号链接 到 ../../qar@2.0.0/node_modules/qar
    └── bar@1.0.0
    │   └── node_modules
    │       └── bar -> 硬链接 到 <store>/bar
    │       │    ├── index.js
    │       │    └── package.json
    │	    └── foo -> 符号链接 到 .pnpm/foo@1.0.0  
    │       └── qar -> 符号链接 到 ../../qar@2.0.0/node_modules/qar 
    └── qar@2.0.0
            └── node_modules
                └── qar -> <store>/qar

可以看到我们目录深度没有改变,但却表达了嵌套的包依赖关系。

占用更小的内存,下载速度更快

使用 npm 时,依赖每次被不同的项目使用,都会重复安装一次。 而在使用 pnpm 时,依赖会被存储在内容可寻址的存储中,所以:

如果你用到了某依赖项的不同版本,只会将不同版本间有差异的文件添加到仓库。 例如,如果某个包有100个文件,而它的新版本只改变了其中1个文件。那么 pnpm update 时只会向存储中心额外添加1个新文件,而不会因为仅仅一个文件的改变复制整新版本包的内容。

所有文件都会存储在硬盘上的某一位置。 当软件包被被安装时,包里的文件会硬链接到这一位置,而不会占用额外的磁盘空间。 这允许你跨项目地共享同一版本的依赖。

因此,您在磁盘上节省了大量空间,这与项目和依赖项的数量成正比,并且安装速度要快得多!

参考

pnpm官网

基于符号链接的 node_modules 结构

都2022年了,pnpm快到碗里来!

关于现代包管理器的深度思考——为什么现在我更推荐 pnpm 而不是 npm/yarn?

Hard links and Symbolic links (硬链接 VS 符号链接)

彻底明白Linux硬链接和软链接