关于前端:pnpm-原理解析

9次阅读

共计 3814 个字符,预计需要花费 10 分钟才能阅读完成。

pnpm 作为以后比拟风行的包管理器之一,次要特点是速度快、节俭磁盘空间,本文将介绍 pnpm 的底层实现,帮忙你了解 pnpm 的原理

pnpm 简介

pnpm 的含意是 performant npm,意味着高性能 npm,从官网中提供的 benchmarks 也能够看出在 intallupdate 等场景时对于 npmyarnyarn_pnp 有不错的性能劣势:

node_modules 的目录构造

嵌套构造

[email protected] 的晚期版本中,对应 Node.js 4.x 及以前的版本,node_modules 在装置时是嵌套构造

一个简略的例子,demo-foodemo-baz 中均依赖 example-bar,在同时装置 demo-foodemo-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-foodemo-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 linkhard 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 linknode_modules 中的顶层依赖以及依赖的依赖符号链接到 node_modules/.pnpm 中,这里援用了官网的截图帮忙你更好地了解 symlbolic inkhard link 在我的项目构造中是如何组织的:

其余能力

pnpm 目前能够脱离 Node.jsruntime 去装置应用,还能够通过 pnpm env 来对 Node.js 版本进行治理,相似 nvm,与 npm/yarn 残缺的性能比拟详见:feature-comparison

以后不实用的场景

  1. 因为 symlbolic link 在一些场景下有兼容性问题,目前 Eletron 以及 labmda 部署的利用上无奈应用 pnpm,详见:discussion

能够通过在 .npmrcnode-linker=hoisted 能够创立一个没有符号链接的扁平的 node_modules,此时 pnpm 创立的目录构造将与 npm/yarn 相似

  1. 因为全局共用同一份 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
  • 残缺代码示例
正文完
 0