pnpm切换指南

5,815 阅读5分钟

什么是 pnpm 

performance npm  意为高性能的npm

为什么是pnpm

pnpm 解决了 npm 和 yarn 的什么问题

  1. 扁平化带来的问题

     - 幽灵依赖

        如果你的项目依赖 a, a 依赖 b,你是直接可以使用 b的,因为它也在你的 node_modules 下,如果哪天 a 被删除了,或者 a 的新版本没有用到 b ,就会导致错误

    - 分身依赖

        如果你的项目依赖 a,a 依赖 lodash@1.0.0,你的项目依赖 b,b 依赖 lodash@1.0.1, 那么你的node_modules结构是:     

    └── node_modules
        ├── a
        │   ├── index.js
        │   └── package.json
        ├── b
        │   ├── index.js
        │   ├── package.json
        │   └── node_modules
        │       └── lodash
        │           ├── index.js
        │           └── package.json(@1.0.1)
        └── lodash
            ├── index.js
            └── package.json(@1.0.0)

还是

    └── node_modules
        ├── a
        │   ├── index.js
        │   └── package.json
        │       └── lodash
        │           ├── index.js
        │           └── package.json(@1.0.0)
        ├── b
        │   ├── index.js
        │   └── package.json
        |
        └── lodash
            ├── index.js
            └── package.json(@1.0.1)

    答案是都有可能,取决于 a 和 b 在package.json 的位置决定,a 在上边的话,那么 a 下边的 lodash 就会被优先提升,b 在上边的话,b 下边的 lodash 就会被优先提升

    npm@5/yarn 引入了一个 lock 文件已解决这种不确定因素,这使得无论安装多少次都会产生相同的结构,这也是为什么lock文件应该始终包含在版本控制中并且不应该手动编辑的原因。但是扁平化算法的复杂度、幽灵依赖和性能问题仍未解决

    - 扁平化算法复杂

  1. 不同项目之间并不共享 node_modules,磁盘占用过大,安装缓慢

    npm/yarn 有一个缺点,就是占用了太多的磁盘空间, 如果你安装同一个包100次,100分的就会被储存在不同的node_modules文件夹下,这往往会占用大量的磁盘空间,可以用 npkill 来查看你的电脑有多少 node_modules 我自己查了一下有40多个g

pnpm是怎么解决这些问题的

我们可以先来看一下npm的发展史,npm经历了三次重大的更新,才逐渐形成了现在的形式,所以让我们一个一个地看

npm1 /2 嵌套式的node_modules

如果 a 依赖了 b,那么最简单的思考方式 b 应该放在 a 的 node_modules 里

  └── node_modules
      └── 
          ├── index.d.ts
          ├── package.json
          └── node_modules
              └── b
                  ├── index.js
                  └── package.json

如果 b 依赖了 c 按照理论我们应该把 c 放在 b 的 node_modules 下


      └── node_modules
      └── 
          ├── index.d.ts
          ├── package.json
          └── node_modules
              └── b
                  ├── index.js
                  ├── package.json
                  └── node_modules
                      └── c

同理如果 c 依赖 d 那么 d 应该放在 c 的 node_modules 下

这样产生的问题:

  1. 路径太长,很容易超过windows路径长度的限制。
  2. 大量相同版本的包被重复安装
  3. 不同共享相同的实例,例如,从不同的地方引入react,它将是不同的实例

npm3/yarn - 扁平化的node_modules

从npm3开始(也包括yarn),扁平化的node_modules一直被采用并使用到现在。

nodejs的依赖解析算法有一个规则,如果它在当前目录下没有找到node_modules,它将递归解析父目录下的node_modules,那么利用这一点把所有引用的包放在项目下node_modules中,就可以解决相同版本的包的不共享和过长的依赖路径问题,所以上边的例子结构会是这样

    └── node_modules
        ├── a
        │   ├── index.js
        │   └── package.json
        └── b
            ├── index.js
            └── package.json

但是又有了新的问题:

  1. 幽灵依赖
  2. 分身依赖
  3. 扁平化算法耗费时间

npm5.x/yarn - 带有lock文件的平铺式的node_modules

引入了 lock 文件用来解决安装时不稳定的结构,但是扁平化算法的复杂性、幽灵依赖问题并没解决

pnpm 基于符号连接的 node_modules

pnpm 尝试解决的是 npm@1的问题,而不是使依赖扁平化,

我们拿 express 来做一个例子,当我们执行  pnpm add express,我们得到了一个这样的 node_modules 目录     

node_modules
├── express -> .pnpm/express@4.17.2/node_modules/express
└── mime-types -> .pnpm/mime-types@2.1.34/node_modules/mime-types

可以发现,我们的node_modules 只有一个 express, 我们只安装了 pnpm 那么它使我们的程序唯一能访问的包,接下来我们点开 express

node_modules/express
├── History.md
├── LICENSE
├── Readme.md
├── index.js
├── lib
└── package.json

我们发现其实并没有 node_modules,其实诀窍在于它其实是一个软链而已,并且当 nodejs/webpack(默认) 解析的时候其实是解析它们的真实位置

此时我们可以点开 .pnpm 文件,发现了如下的结构

├── accepts@1.3.8\
├── array-flatten@1.1.1\
├── body-parser@1.19.1\
├── bytes@3.1.1\
├── content-disposition@0.5.4\
......

我们发现了一堆 name@version/的文件,我们可以点开真实位置的express 

image.png

我们发现 其实真实位置下的 express 也并没有 node_modues

其实 expreess 的 node_modules 和它处于同级目录,

node_modules/.pnpm/express@4.17.2/node_modules/* 就是 node_modules/.pnpm/express@4.17.2/node_modules/express 的 node_modules,

此外这些文件也都是软链,

node_modules/.pnpm/express@4.17.2/node_modules/\
├── accepts -> ../../accepts@1.3.8/node_modules/accepts\
├── array-flatten -> ../../array-flatten@1.1.1/node_modules/array-flatten\
├── body-parser -> ../../body-parser@1.19.1/node_modules/body-parser\
├── content-disposition -> ../../content-disposition@0.5.4/node_modules/content-disposition\
├── content-type -> ../../content-type@1.0.4/node_modules/content-type\
├── cookie -> ../../cookie@0.4.1/node_modules/cookie\
├── cookie-signature -> ../../cookie-signature@1.0.6/node_modules/cookie-signature\
├── debug -> ../../debug@2.6.9/node_modules/debug\
├── depd -> ../../depd@1.1.2/node_modules/depd\
├── encodeurl -> ../../encodeurl@1.0.2/node_modules/encodeurl\
├── escape-html -> ../../escape-html@1.0.3/node_modules/escape-html\
├── etag -> ../../etag@1.8.1/node_modules/etag \
├── express\
├── finalhandler -> ../../finalhandler@1.1.2/node_modules/finalhandler\
........

指向了外层加版本版本号下对于的真实文件

我们可以发现这种实现方法巧妙的避开了之前讲的那些问题

  1. 幽灵依赖
  2. 分身依赖
  3. 嵌套结构大量包重复安装
  4. 路径太长 此外,不止这些,上边还讲到:不同项目之间并不共享 node_modules,磁盘占用过大,pnpm 同样解决掉了

这里是文档的截图:

image.png

  如何切换

分为三个步骤: 1.

rm -rf node_modules 删除原本的node_modules
pnpm import package-lock.json/yarn.lock   (把 lock 文件转换为 pnpm )

  1. 把所有 script 相关的 都换为 pnpm

  2. 跑一下 build/dev  这时候 项目中的幽灵依赖自会暴露出来,解决一下       此外

  3. 如果你的项目是  monorepo 那么你可能需要定义 pnpm-workspace.yaml

  4. 如果你的某些依赖项 由于历史原因 依赖于扁平化的 node_modules 可以使用 shamefully-hois 配置它

  5. 如果你的项目 不适用于 软链,那么 可以使用 node-linker: hoisted 来获得扁平化并且没有软链的 node_modules

  6. 如果你切换了 registery 在是呀 pnpm 安装全局包会收到  ERR_PNPM_REGISTRIES_MISMATCH  报错需要执行一下:

    pnpm install -g
    pnpm install -g pnpm

     github.com/pnpm/pnpm/i…

  1. 匹配路径问题,比如在webpack里,原本的路径可能不管用了,因为现在真实的路径是在 node_modules/.pnpm/* 下边的

  2. 当你需要在 node_modules 内改东西,可能会影响到所有你的电脑内所有你用到的地方 :)

参考:www.pnpm.cn/