You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
pnpm 首先将依赖安装到全局 store,然后通过 symbolic link 和 hard link 来组织目录结构,将全局的依赖链接到项目中,将项目的直接依赖链接到 node_modules 的顶层,所有的依赖则平铺于 node_modules/.pnpm 目录下,实现了所有项目的依赖共享 store 的全局依赖,解决了幽灵依赖和 NPM 分身的问题
symbolic link 与 hard link
链接是操作系统中文件共享的方式,其中 symbolic link 是符号链接,也称软链接,hard link 是硬链接,从在使用的角度看,二者没有什么区别,都支持读写,如果是可执行文件也可以直接执行,主要区别在于底层原理不太一样:
# symbolic ink
ln -s myfile mysymlink
# hard link
ln myfile myhardlink
pnpm 实现
在 pnpm 中,会将依赖安装到当前分区的 <home dir>/.pnpm-store 位置中,可以通过以下命令获得当前的 store 位置:
pnpm store path
然后利用 hard link 将所需的包从 node_modules/.pnpm 硬链接到 store 中,最后通过 symbolic link 将 node_modules 中的顶层依赖以及依赖的依赖符号链接到 node_modules/.pnpm 中,一个依赖 demo-foo@1.0.1 和 demo-baz@1.0.0 的例子,node_modules 结构如下:
functioncreateImportPackage(packageImportMethod?: 'auto'|'hardlink'|'copy'|'clone'|'clone-or-copy'){// this works in the following way:// - hardlink: hardlink the packages, no fallback// - clone: clone the packages, no fallback// - auto: try to clone or hardlink the packages, if it fails, fallback to copy// - copy: copy the packages, do not try to link them firstswitch(packageImportMethod??'auto'){case'clone':
packageImportMethodLogger.debug({method: 'clone'})returnclonePkgcase'hardlink':
packageImportMethodLogger.debug({method: 'hardlink'})returnhardlinkPkg.bind(null,linkOrCopy)case'auto': {returncreateAutoImporter()}case'clone-or-copy':
returncreateCloneOrCopyImporter()case'copy':
packageImportMethodLogger.debug({method: 'copy'})returncopyPkgdefault:
thrownewError(`Unknown package import method ${packageImportMethodasstring}`)}}
pnpm
作为当前比较流行的包管理器之一,主要特点是速度快、节省磁盘空间,本文将介绍pnpm
的底层实现,帮助你理解pnpm
的原理pnpm 简介
pnpm
的含义是performant npm
,意味着高性能npm
,从官网中提供的benchmarks
也可以看出在intall
、update
等场景时对于npm
、yarn
、yarn_pnp
有不错的性能优势:node_modules 的目录结构
嵌套结构
在
npm@2
的早期版本中,对应Node.js 4.x
及以前的版本,node_modules
在安装时是嵌套结构一个简单的例子,
demo-foo
和demo-baz
中均依赖demo-bar
,在同时安装demo-foo
和demo-baz
时会生成如下的node_modules
结构:这个时候的目录结构虽然比较清晰,但是每个依赖包都会有自己的
node_modules
,相同的依赖并没有复用,例如上面的相同依赖demo-bar
就被安装了两次另外一个问题是
windows
的最长路径限制,在复杂项目场景依赖层级较深时,依赖的路径往往会超出长度限制扁平结构
为了解决上述问题,
yarn
提出了扁平结构的设计,将所有的依赖在node_modules
中平铺,后来的npm v3
版本的实现也与之类似,因此使用yarn
或者npm@3+
安装上述的例子,将会得到如下扁平式的目录结构:另外这种方式对于相同依赖的不同版本,则只会将其中一个进行提升,剩余的版本则还是嵌套在对应的包中,例如我们上面的
demo-foo
中对于demo-bar
的依赖升级到v1.0.1
版本,则会得到下面的结构,具体哪个版本会提升到最顶层则取决于安装时的顺序(示例):扁平结构存在的问题
扁平化的方案并不完美,反而引入了一些新的问题:
幽灵依赖
幽灵依赖(Phantom dependencies)指的是没有显示声明在
package.json
中的依赖,却可以直接引用到对应的包,这个问题是由扁平化的结构产生的,会将依赖的依赖也至于node_modules
的顶层,也就可以在项目中直接引用到。当某一天这个子依赖不再是引用包的依赖时,项目中的引用则会出现问题。例如在包含
demo-foo
和demo-baz
的项目中可以得到如下依赖,此时demo-bar
作为依赖的依赖也出现在了node_modules
中:分身问题
NPM 分身(NPM doppelgangers)则指的是对于相同依赖的不同版本,由于
hoist
的机制,只会提升一个,其他版本则可能会被重复安装,还是上面的例子,当依赖的demo-bar
升级到v1.0.1
时,作为demo-foo
和demo-baz
依赖的v1.0.0
版本则以嵌套的形式被重复安装:pnpm 解题思路
pnpm
首先将依赖安装到全局store
,然后通过symbolic link
和hard link
来组织目录结构,将全局的依赖链接到项目中,将项目的直接依赖链接到node_modules
的顶层,所有的依赖则平铺于node_modules/.pnpm
目录下,实现了所有项目的依赖共享store
的全局依赖,解决了幽灵依赖和 NPM 分身的问题symbolic link 与 hard link
链接是操作系统中文件共享的方式,其中
symbolic link
是符号链接,也称软链接,hard link
是硬链接,从在使用的角度看,二者没有什么区别,都支持读写,如果是可执行文件也可以直接执行,主要区别在于底层原理不太一样:hard link
inode
(索引节点),源文件与硬链接指向同一个索引节点symbolic link
Windows
的快捷方式inode
值不一样,文件类型也不同,因此符号链接可以跨分区访问如何创建链接
pnpm 实现
在 pnpm 中,会将依赖安装到当前分区的
<home dir>/.pnpm-store
位置中,可以通过以下命令获得当前的store
位置:然后利用
hard link
将所需的包从node_modules/.pnpm
硬链接到store
中,最后通过symbolic link
将node_modules
中的顶层依赖以及依赖的依赖符号链接到node_modules/.pnpm
中,一个依赖demo-foo@1.0.1
和demo-baz@1.0.0
的例子,node_modules
结构如下:这里引用了官网的截图帮助你更好地理解
symbolic ink
与hard link
在项目结构中是如何组织的:pnpm 对于链接的实际应用,以下是相关源码:
其他能力
pnpm
目前可以脱离Node.js
的runtime
去安装使用,还可以通过pnpm env
来对Node.js
版本进行管理,类似nvm
,与npm/yarn
完整的功能比较详见:feature-comparisonpnpm 的局限性
symbolic 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 来手动设置包的引用方式其他工具
bun: https://github.com/oven-sh/bun
Zig
写的一个JS runtime
,bun
也提供了包管理工具,但是bun
会有一些兼容性问题Volt:https://github.com/dimensionhq/volt
Rust
写的Node.js
包管理器,速度极快,目前仍在 beta 阶段tnpm: https://github.com/cnpm/npminstall
未来展望
pnpm
很快,但是并不是所有的pnpm
命令都很快,例如pnpm run
比较慢,未来可能会使用Rust
来写一些子命令的 cli wrapper,参见这个 discussion参考
The text was updated successfully, but these errors were encountered: