当前位置: 首页 > Web前端 > HTML

pnpm的原理分析

时间:2023-03-28 15:09:48 HTML

pnpm作为目前比较流行的包管理器之一,主要特点是速度快,节省磁盘空间。本文将介绍pnpm的底层实现,帮助大家理解pnpm的原理。pnpm简介pnpm的意思是performantnpm,意思是高性能的npm,从官网提供的benchmarks也可以看出npm、yarn、yarn_pnp在intall、update等场景下都有很好的性能优势:node_modules的嵌套目录结构在npm@2早期版本,对应Node.js4.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中,后面npmv3版本的实现也类似,所以使用yarn或者npm@3+来安装上面的例子,你将得到以下平面目录结构: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扁平化结构的问题扁平化解决方案并不完美,但引入了一些新问题:Phantomdependencies幻影依赖(Phantomdependencies)是指没有在package.json中显式声明,但可以直接引用对应包的依赖。这个问题是flat结构导致的,dependencies的依赖也会包含在项目中的最顶层node_modules也可以直接引用。当有一天这个子依赖不再是引用包的依赖时,项目中的引用就会出现问题。NPMdoppelgangers指的是同一依赖项的不同版本。由于提升机制,只会提升一个,其他版本可能会重复安装,还是上面的例子,当依赖的demo-bar的依赖升级到v1.0.1时,v1.0.0版本就是依赖的demo-foo和demo-baz会嵌套形式重复安装: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.jsonpnpm解题思路pnpm首先将依赖安装到全局store,然后通过符号链接和硬链接组织目录结构,将全局依赖链接到项目中,将项目的直接依赖链接起来项目到node_modules顶层,所有依赖都布局在node_modules/.pnpm目录下,实现了所有项目依赖的全局依赖和共享存储,解决了幽灵依赖和npm克隆问题。符号链接和硬链接链接是操作系统中的文件共享方式,符号链接是符号链接,也称为软链接,硬链接是硬链接。从使用的角度来看,两者没有区别,都支持读写。如果是可执行文件,也可以直接执行。主要区别在于底层原理不同:硬链接不创建新的inode(索引节点),源文件和硬链接指向同一个索引节点。硬链接不支持目录,只支持文件级别,不支持跨分区删除源文件。所有硬链接后,文件实际上被删除了。源文件的路径存放在符号链接symboliclink中,指向源文件。类似于Windows快捷方式符号链接支持目录和文件,区别于源文件。inode值不同,文件类型也不同,所以可以跨分区访问符号链接。删除源文件后,符号链接仍然存在,但无法通过它访问源文件。如何创建链接#symbolicinkln-smyfilemysymlink#hardlinklnmyfilemysymlinkpnpm在pnpm中,会把依赖安装到当前分区的/.pnpm-store位置,可以获取当前store位置通过以下命令:pnpmstorepath然后使用硬链接从中下载所需的包node_modules/.pnpm硬链接到store,最后通过符号链接将node_modules中的顶层依赖和依赖依赖符号链接到node_modules/.pnpm,一个依赖demo-foo@1.0.1和demo-baz@1.0.0例如node_modules结构如下:node_modules└─.pnpm└─demo-bar@1.0.0└─node_modules└─demo-bar->/demo-bar└─demo-bar@1.0.1└─node_modules└─demo-bar->/demo-bar└─demo-baz@1.0.0└─node_modules├─demo-bar->../../demo-bar@1.0.0/node_modules/demo-bar└─demo-baz->/demo-baz└─demo-foo@1.0.1└─node_modules├─demo-bar->../../demo-bar@1.0.1/node_modules/demo-bar└─demo-foo->/demo-foo└─demo-baz->./pnpm/demo-baz@1.0.0/node_modules/demo-baz└─演示富->。/pnpm/demo-baz@1.0.1/node_modules/demo-foo下面是官网截图,帮助大家更好的理解symbolicink和hardlink在项目结构中是如何组织的:pnpm目前可以分离的其他能力节点。js运行时安装使用,也可以通过pnpmenv管理Node.js版本,类似nvm,相比npm/yarn功能更全详情请参考:feature-comparisonpnpm的局限性由于符号链接在某些场景下存在兼容性问题,目前pnpm无法用于Eletron和labmda部署的应用。详情请参考:讨论可以通过在.npmrc中设置node-linker=hoisted创建一个没有符号链接的平面node_modules。这时候pnpm创建的目录结构会和npm/yarn类似。因为同一个store是全局共享的,当需要修改node_modules中的内容时,会直接影响到全局store中对应的内容。其他项目也会影响这个问题。其实最推荐的方式是clone(copy-on-write),它使用的是copy-on-write。默认情况下,多个引用指向同一个文件。只有在用户需要修改的时候才复制,这样不会影响其他引用读取源文件的内容,但并不是所有的操作系??统都支持。默认情况下,pnpm将尝试使用克隆。如果它不支持它,它将回退到使用硬链接。也可以在npmrc中通过指定package-import-method来手动设置包的引用方式提供包管理工具,但是bun会和Volt存在一些兼容性问题:https://github.com/dimensionh...使用Rust编写的Node.js包管理器,速度极快,目前还在测试阶段。展望未来,pnpm会非常快,但并不是所有的pnpm命令都非常快,例如pnpmrun就比较慢,将来可能会使用Rust为一些子命令编写cli包装器,请参阅此讨论参考扁平化的node_modules并不是唯一的方式Symlinkednode_modules结构完整的示例代码可以参考:blog-samples/pnpm