A diagram to show how pnpm works

Pnpm may be alternative of npm、yarn and lerna. But do you know how pnpm works?

pnpm-change.png

该图已被 pnpm 官网收录

幽灵依赖问题#

仓库依赖的 NPM 包,及其依赖包的依赖,平铺在 **.pnpm **这个隐藏的磁盘目录。只有仓库依赖的包会被列在当前仓库的 node_modules 下,其他包则无法被 resolve 到。

比如仓库依赖 bar@1.0.0,bar 包依赖 foo@1.0.0。这两个包会平铺在 .pnpm,当前仓库的 node_modules 只列出项目依赖的包的符号链接(注意箭头方向)。

依赖地狱问题#

依赖会平铺在 .pnpm 目录。

Doppelgagers 问题#

从上图可以看清楚,.pnpm 目录下依赖 bar 的结构是:

$ tree -L 2
.
└── node_modules
    ├── foo -> ../../foo@1.0.0/node_modules/foo
    └── bar

也就是 NPM 包 bar 的依赖都在自身 node_modules 下可以 reslove 到。依赖的各影分身都只会下载一份(更快),也不会被错误 reslove。


以下是更新内容:如果是首次使用 pnpm 的话,也许会发现实际的结构和上图有些出入,我觉得这些出入可以统称为「面向社区生态的妥协」。而其中主要的,和这四个参数有关。​

面向社区生态的妥协#

如果这个配置开启,你会发现 .pnpm 目录下有一个 node_modules 目录,这个目录下的包全部软链到 .pnpm 下的包。

$ cd node_modules/.pnpm && tree -L 3

.pnpm
    ├── bar@1.0.0
    ├── lodash@2.0.0
    └── node_modules
        ├── lodash@2.0.0 -> ../lodash@2.0.0/node_modules/lodash
        └── bar@1.0.0

这是因为 pnpm 默认情况下,这个 hoist 配置是开启的,称之为“半严格” 的设计:项目无法 reslove 自己没有依赖的包,但是项目的依赖可以 reslove 到其未被依赖的包。

比如项目的直接依赖 bar 其实并没有依赖 lodash@2.0.0 这个包,但是仍然可以被 reslove 到,就是因为 .pnpm 下这个 node_modules 目录类似 NPM V3 平铺了所有依赖包,根据 Node 的查找算法,是可以找到这个依赖的。这就是 pnpm 的妥协策略之一

这个配置就比较好理解了。hoist-pattern 配置为 *,就是和 hoist 为 true 表达的含义一样。

当你的项目依赖了 eslint 或 babel,使用 pnpm 安装,在项目的 node_modules 下,你会发现它们的影子。

tree node_modules -L 1

node_modules
  ├── @babel
  ├── @eslint
  ├── @types
  ├── @typescript-eslint
  ├── eslint
  ├── eslint-config-ali
  ├── eslint-import-resolver-node
  ├── eslint-module-utils
  ├── eslint-plugin-import
  ├── eslint-plugin-jsx-plus

这时候,有可能就怀疑自己了。正常来说,只依赖了 eslint,不应该包含 eslint 的其他 plugin,因为我并没有直接依赖它们。但事实上,eslint 在依赖的问题上本身就 存在 bug 的。而 pnpm 默认将其 hoist 至项目的根 node_modules 下(public-hoist-pattern 的默认配置是 ['types', 'eslint', '@prettier/plugin-*', 'prettier-plugin-'])。这是 pnpm 的第二个妥协策略

第三个妥协策略就是 shamefully-hoist,设置为 true 时,就把依赖的依赖,以及依赖都提升至项目根目录 node_modules 下,类似 NPM V3 的结构。

portrait

Have a weekly visit of

Howl's Moving Castle

Get emails from me about web development, tech, and early access to new articles. I will only send emails when new content is posted.

Subscribe Now!