pnpm
作为以后比拟风行的包管理器之一,次要特点是速度快、节俭磁盘空间,本文将介绍 pnpm
的底层实现,帮忙你了解 pnpm
的原理
pnpm 简介
pnpm
的含意是 performant npm
,意味着高性能 npm
,从官网中提供的 benchmarks
也能够看出在 intall
、update
等场景时对于 npm
、yarn
、yarn_pnp
有不错的性能劣势:
node_modules 的目录构造
嵌套构造
在 [email protected]
的晚期版本中,对应 Node.js 4.x
及以前的版本,node_modules
在装置时是嵌套构造
一个简略的例子,demo-foo
和 demo-baz
中均依赖 example-bar
,在同时装置 demo-foo
和 demo-baz
时会生成如下的 node_modules
构造:
node_modules
└─ demo-foo
├─ index.js
├─ package.json
└─ node_modules
└─ demo-bar
├─ index.js
└─ package.json
└─ demo-baz
├─ index.js
├─ package.json
└─ node_modules
└─ demo-bar
├─ index.js
└─ package.json
这个时候的目录构造尽管比拟清晰,然而每个依赖包都会有本人的 node_modules
,雷同的依赖并没有复用,例如下面的雷同依赖 demo-bar
就被装置了两次
另外一个问题是 windows
的最长门路限度,在简单我的项目场景依赖层级较深时,依赖的门路往往会超出长度限度
扁平构造
为了解决上述问题,yarn
提出了扁平构造的设计,将所有的依赖在 node_modules
中平铺,起初的 npm v3
版本的实现也与之相似,因而应用 yarn
或者 [email protected]+
装置上述的例子,将会失去如下扁平式的目录构造:
node_modules
└─ demo-bar
├─ index.js
└─ package.json
└─ demo-baz
├─ index.js
└─ package.json
└─ demo-foo
├─ index.js
└─ package.json
另外这种形式对于雷同依赖的不同版本,则只会将其中一个进行晋升,残余的版本则还是嵌套在对应的包中,例如咱们下面的 demo-foo
中对于 demo-bar
的依赖降级到 v1.0.1
版本,则会失去上面的构造,具体哪个版本会晋升到最顶层则取决于装置时的程序(示例):
node_modules
└─ demo-bar
├─ index.js
└─ package.json
└─ demo-baz
├─ index.js
├─ package.json
└─ node_modules
└─ demo-bar
├─ index.js
└─ package.json
└─ demo-foo
├─ index.js
├─ package.json
└─ node_modules
└─ demo-bar
├─ index.js
└─ package.json
扁平构造存在的问题
扁平化的计划并不完满,反而引入了一些新的问题:
幽灵依赖
幽灵依赖(Phantom dependencies)指的是没有显示申明在 package.json
中的依赖,却能够间接援用到对应的包,这个问题是由扁平化的构造产生的,会将依赖的依赖也至于 node_modules
的顶层,也就能够在我的项目中间接援用到。当某一天这个子依赖不再是援用包的依赖时,我的项目中的援用则会呈现问题
分身问题
NPM 分身(NPM doppelgangers)则指的是对于雷同依赖的不同版本,因为 hosit
的机制,只会晋升一个,其余版本则可能会被反复装置,还是下面的例子,当依赖的 demo-bar
的依赖降级到 v1.0.1
时,作为 demo-foo
和 demo-baz
依赖的 v1.0.0
版本则以嵌套的模式被反复装置:
node_modules
└─ demo-bar // v1.0.1
├─ index.js
└─ package.json
└─ demo-baz
├─ index.js
├─ package.json
└─ node_modules
└─ demo-bar // v1.0.0
├─ index.js
└─ package.json
└─ demo-foo
├─ index.js
├─ package.json
└─ node_modules
└─ demo-bar // v1.0.0
├─ index.js
└─ package.json
pnpm 解题思路
pnpm
首先将依赖装置到全局 store
,而后通过 symlbolic link
和 hard link
来组织目录构造,将全局的依赖链接到我的项目中,将我的项目的间接依赖链接到 node_modules
的顶层,所有的依赖则平铺于 node_modules/.pnpm
目录下,实现了所有我的项目的依赖共享 store
的全局依赖,解决了幽灵依赖和 NPM 分身的问题
一个依赖 [email protected]
和 [email protected]
的例子,node_modules
构造如下:
node_modules
└─ .pnpm
└─ [email protected]
└─ node_modules
└─ demo-bar -> <store>/demo-bar
└─ [email protected]
└─ node_modules
└─ demo-bar -> <store>/demo-bar
└─ [email protected]
└─ node_modules
├─ demo-bar -> ../../[email protected]/node_modules/demo-bar
└─ demo-baz -> <store>/demo-baz
└─ [email protected]
└─ node_modules
├─ demo-bar -> ../../[email protected]/node_modules/demo-bar
└─ demo-foo -> <store>/demo-foo
└─ demo-baz -> ./pnpm/[email protected]/node_modules/demo-baz
└─ demo-foo -> ./pnpm/[email protected]/node_modules/demo-foo
symlbolic link 与 hard link
链接是操作系统中文件共享的形式,其中 symlbolic link
是符号链接,也称软链接,hard link
是硬链接,从在应用的角度看,二者没有什么区别,都反对读写,如果是可执行文件也能够间接执行,次要区别在于底层原理不太一样:
hard link
- 硬链接不会新建
inode
(索引节点),源文件与硬链接指向同一个索引节点 - 硬链接不反对目录,只反对文件级别,也不反对跨分区
- 删除源文件和所有硬链接之后,文件才真正被删除
symlbolic link
- 符号链接中存储的是源文件的门路,指向源文件,相似于
Windows
的快捷方式 - 符号链接反对目录与文件,它与源文件是不同的文件,
inode
值不一样,文件类型也不同,因而符号链接能够跨分区拜访 - 删除源文件后,符号链接仍然存在,然而无奈通过它拜访到源文件
如何创立链接
# symlbolic ink
ln -s myfile mysymlink
# hard link
ln myfile mysymlink
pnpm 实现
在 pnpm 中,会将依赖装置到以后分区的 <home dir>/.pnpm-store
地位中,能够通过以下命令取得以后的 store
地位:
pnpm store path
而后利用 hard link
将所需的包从 node_modules/.pnpm
硬链接到 store
中,最初通过 symlbolic link
将 node_modules
中的顶层依赖以及依赖的依赖符号链接到 node_modules/.pnpm
中,这里援用了官网的截图帮忙你更好地了解 symlbolic ink
与 hard link
在我的项目构造中是如何组织的:
其余能力
pnpm
目前能够脱离 Node.js
的 runtime
去装置应用,还能够通过 pnpm env
来对 Node.js
版本进行治理,相似 nvm
,与 npm/yarn
残缺的性能比拟详见:feature-comparison
以后不实用的场景
- 因为
symlbolic link
在一些场景下有兼容性问题,目前Eletron
以及labmda
部署的利用上无奈应用pnpm
,详见:discussion
能够通过在 .npmrc
中 node-linker=hoisted
能够创立一个没有符号链接的扁平的 node_modules
,此时 pnpm
创立的目录构造将与 npm/yarn
相似
- 因为全局共用同一份
store
,因而当须要批改node_modules
内的内容时,会间接影响全局store
中对应的内容,对其余我的项目也会造成影响
对于这个问题,其实最举荐的形式是 clone
(copy-on-write),应用写入时复制,默认多个援用指向同一个文件,只有当用户须要批改的时候才进行复制,这样就不会影响其余援用对于源文件内容的读取
然而并不是所有的操作系统都反对,pnpm
默认会尝试应用 clone
,如果不反对,则会退回至应用 hard link
,你也能够通过在 npmrc
中指定 package-import-method 来手动设置包的援用形式
参考
- Flat node_modules is not the only way
- Symlinked node_modules structure
- 残缺代码示例