likes
comments
collection
share

由浅入深 学习 pnpm

作者站长头像
站长
· 阅读数 15

Why pnpm

最近公司的项目迁移使用了pnpm这款包管理工具,正所谓知己知彼,百战不殆。就跟着这篇文章快速的学习一下pnpm

官网链接

指令快速学习

// 安装 pnpm
npm install -g pnpm
// or
yarn global add pnpm

// 查看版本
pnpm -v

// 查看帮助
pnpm -h

// 安装所有的包
pnpm install  // 别名  i

// 更新所有的包
pnpm update  // 别名  up

// 安装某一个包
pnpm add react      // dependencies
pnpm add eslint -D  // devDependencies

// 移除某一个包
pnpm remove react  // 别名  rm、un、uninstall

嗯嗯,已经学会了。这就去开发!

等等!这些和yarnnpm并没有什么区别呀,那我们为什么要使用 pnpm

Store

介绍

pnpm中有一个很重要的概念:store,这个单词我们很常见,在pnpm中,它的意思是一个全局的仓库,pnpm他会把包缓存到store中,我们要安装包的时候并不是优先联网下载的,是先读取的这个store

我们安装包到 node_modules的过程如下

由浅入深 学习 pnpm

安装包的时候会同时的去看 node_modules以及store

  • 两个都没有的时候会联网下载
  • store有, node_modules没有,会从store中复制过去
  • store没有, node_modules有,也会补充store

store有关的指令

// 读取 store的位置
pnpm config get store-dir   // 默认是当前磁盘下的.pnpm-store的文件夹中

// 设置 store的位置
pnpm config set store-dir "D:\env\.pnpm-store"

store的体现

// 创建文件夹
mkdir test-pnpm
// 进入
cd test-pnpm
// 初始化文件夹为一个npm项目
pnpm init   Or   npm init -y
// 安装 express
pnpm add express

执行规程如图

由浅入深 学习 pnpm

我们发现在 pnpm add完成的时候,他会告诉我们 store的地址,还有一个 Virtual store 的地址,这个就是当前目录下的node_modules/.pnpm,这里路径上有一个 .pnpm后面在讨论。

在最后输出了这样一句话

// 这里和截图不同是因为我多次下载了请忽略~

Progress: resolved 57, reused 0, downloaded 57, added 57, done

我们解析一下

  • resolved 57,在下载 express这一个包的时候,解析出来这个包以及它的依赖,总共有57项。
  • reused 0store中的包重复使用了0次,毕竟我们这是第一次下载,重用是0没毛病。
  • downloaded 57联网下载了 57 个包
  • added 57添加到 store 中包有57项

OK,具体这些怎么体现我们可以

  • 新建一个项目在安装 express 可以体现出 reused
  • 删掉 node_modules在下载可以体现 reused
  • 不删 node_modules,删掉 store文件夹可以体现 added
  • 删掉 node_modules,删掉 store文件夹可以体现 downloaded

小总结

那么到这里,我们可以了解到,pnpm是使用了一个 store 机制,让我们下载包的速度极大的提升了

pnpm 的 node_modules解析

pnpm的优异之处不仅仅是使用了store 加快了下载数据,还有它的node_modules也很有意思

Tip: 我们安装包是安装到当前项目的 node_modules 下,写代码 import/require 找包是node的机制,它找依赖是一层层的向上找node_modules,这里我们只去了解node_modules 即可,与上面的 store 完全无关

node_modules历史

npm2 的node_modules

最早版本:npm2

由浅入深 学习 pnpm

这个时候我们安装一下 express,node_modules是只有一个 express文件夹的

由浅入深 学习 pnpm

但是展开后我们就很容易发现问题了。

由浅入深 学习 pnpm

我们安装的 node_modules 中有express

express有它自己的 node_modules

express依赖的包也有它的 node_modules

依赖的包也有它依赖包的包。。。。反正就这样一直嵌套下去了

这里会有以下几个问题

  • 安装 A B两个包,这两个包都依赖了C,这个时候 C就会被安装两遍,即 公共依赖会被多次安装
  • windows 的文件路径最长是 260 多个字符,嵌套的文件结构一直嵌套下去肯定是不行的

当时 npm 还没解决,社区就出来新的解决方案了,就是 yarn

yarn 的node_modules

yarn 解决嵌套的思路是将嵌套依赖打散到同一层,这样也就没有依赖重复多次的问题了,路径过长的问题了。

使用yarn 安装express的文件夹结构如下

由浅入深 学习 pnpm

这样的打散做法很好,但不是绝对的

比如

我们安装 A B

A依赖 1.0.0 版本的C

B依赖 2.0.0 版本的C

这个时候项目 node_modules 目录里面会有 A B C 包,C 包的版本是取决于安装过程先解析的是A还是B

。如果先解析的是A,则项目node_modules 目录中的C包版本是1.0.0,在B包中依然会存在 node_modules文件夹 ,在B包的node_modules 中依赖 2.0.0 版本的C

如果先解析的B,同理。

这样感觉也很合理,但是有一个致命的问题,叫幽灵依赖。

我们依赖的是 A B,但是我们在代码中是可以写 require("C")的,也就是打散的过程中,将不是我们的依赖也打散到node_modules中了,导致我们可以引入到C

并且,并没有实质性的解决我们重复依赖导致包被多次安装,占用重复的空间。

还是上面的例子,打散的是1.0.0C包,如果还有一个D包依赖2.0.0C包,则 2.0.0C包在 安装B包和D 包的过程中会被安装两次!

pnpm 的node_modules

我们来看一下 pnpm 的做法

由浅入深 学习 pnpm

首先 node_modules 中只有一个 express 目录,但是我们发现后面多了一个链接符号,也就是说它真正的位置是不在这里的,这里只是一个软连接,保证我们在写 require("express") 的时候不会报错,这样的作法就解决了幽灵依赖的问题。

这个时候我们发现有一个 .pnpm的文件夹,这个文件夹的秘密就太多了。

由浅入深 学习 pnpm

我们一点点来解密这个 .pnpm 目录

  1. pnpm的思路是将所有包,以及包依赖的包,等嵌套依赖的包,全部打散到 .pnpm目录中,为了解决重复安装相同版本的包的问题,他将包的版本也写到了文件名中。
  2. 我们安装的express,在项目的node_modules目录中它是一个软连接,链接到了 .pnpm 目录中 .pnpm/express@4.18.1/node_modules/express 目录
    1. 注意一下这个文件结构, .pnpm 中 是带版本号的 文件夹,这个目录中会有一个 node_modules,在这里会有一个包名的文件夹,这个文件夹中才是真正源代码所在的目录
  3. 那我们发现,.pnpm/express@4.18.1/node_modules/这个目录下,express目录是真实源代码所在地,跟他同级还有很多的软连接。这个软连接表示的是 express这个包依赖的包
    1. express这个包依赖的包,现在是一个软连接,那他真实的路径在哪呢?
    2. 可以发现,他被打散到 .pnpm 中了,会有一个 包名@版本号 的文件夹,比如 express依赖的 accepts这个包,他真实的源代码是在.pnpm/accepts@1.3.8/node_modules/accepts文件夹下

那我们就发现了

  • pnpm会把所有的依赖打散到 .pnpm目录下,会带上版本号
  • 在这个带版本号的文件夹下有一个 node_modules
  • 这个 node_modules中只有两种情况,一个是包名文件夹,这里是真实的源代码,包依赖的包会在当前node_modules下以软连接的方式,连接到 .pnpm/包名@版本/node_modules/包名这个目录中
test-pnpm\node_modules\express  // 这个是软连接,实际的地址如下

test-pnpm\node_modules\.pnpm\express@4.18.1\node_modules\express


// express 依赖的包 会在 下面这个目录里面生成 软连接
test-pnpm\node_modules\.pnpm\express@4.18.1\node_modules\依赖包名

// 对应的真实地址是
test-pnpm\node_modules\.pnpm\依赖包名@版本\node_modules\依赖包名

总结

pnpm的最大特点有两个

  1. 使用 store 全局的缓存了包,能在安装包的时候联网下载也能从 项目的 node_modules 进行备份
  2. .pnpm 文件夹,创建 包名@版本号文件夹,以及软连接的方式,杜绝了一个版本的包被多次安装的问题,monoreport类型的项目更加能体现出它的优势