什么是 PNPM?

作者: 涅槃快乐是金 | 来源:发表于2021-11-11 17:47 被阅读0次

    什么是PNPM?

    Fast, disk space efficient package manager

    本质上他是一个包管理工具,和npm/yarn没有区别,主要优势在于

    • 包安装速度极快
    • 磁盘空间利用效率高

    使用方法:

    npm i pnpm -g
    

    优势

    一、速度快

    二、高效利用磁盘空间

    硬链接(Hard Link)
    硬连接指通过索引节点来进行连接。在Linux的文件系统中,保存在磁盘分区中的文件不管是什么类型都给它分配一个编号,称为索引节点号(Inode Index)。在Linux中,多个文件名指向同一索引节点是存在的。一般这种连接就是硬连接。硬连接的作用是允许一个文件拥有多个有效路径名,这样用户就可以建立硬连接到重要文件,以防止“误删”的功能。其原因如上所述,因为对应该目录的索引节点有一个以上的连接。只删除一个连接并不影响索引节点本身和其它的连接,只有当最后一个连接被删除后,文件的数据块及目录的连接才会被释放。也就是说,文件真正删除的条件是与之相关的所有硬连接文件均被删除。
    
    软连接(Symbolic Link)
    另外一种连接称之为符号连接,也叫软连接。软链接文件有类似于Windows的快捷方式。它实际上是一个特殊的文件。在符号连接中,文件实际上是一个文本文件,其中包含的有另一文件的位置信息。
    

    pnpm 内部使用基于内容寻址的文件系统来存储磁盘上所有的文件:

    • 不会重复安装同一个包。使用npm/yarn 的时候,如果100个包依赖lodash ,那么就可能安装了100次lodash ,磁盘中就有100个地方写入了这部分代码。但是pnpm会只在一个地方写入这部分代码,后面使用会直接使用hard link
    • 即使一个包的不同版本,pnpm 也会极大程度地复用之前版本的代码。举个例子,比如 lodash 有 100 个文件,更新版本之后多了一个文件,那么磁盘当中并不会重新写入 101 个文件,而是保留原来的 100 个文件的 hardlink,仅仅写入那一个新增的文件

    三、支持monorepo

    monorepo 的宗旨就是用一个 git 仓库来管理多个子项目,所有的子项目都存放在根目录的packages目录下,那么一个子项目就代表一个package

    monorepo 管理工具lerna

    项目参考:babel

    四、安全

    之前在使用 npm/yarn 的时候,由于 node_module 的扁平结构,如果 A 依赖 B, B 依赖 C,那么 A 当中是可以直接使用 C 的,但问题是 A 当中并没有声明 C 这个依赖。因此会出现这种非法访问的情况。 但 pnpm 自创了一套依赖管理方式,很好地解决了这个问题,保证了安全性。

    依赖管理

    pnpm 会在全局的 store 目录里存储项目 node_modules 文件的 hard links 。因为这样一个机制,导致每次安装依赖的时候,如果是个相同的依赖,有好多项目都用到这个依赖,那么这个依赖实际上最优情况(即版本相同)只用安装一次。

    回归一下 node_modules 结构历史:

    第一阶段:npm@3 之前版本

    node_modules
    └─ foo
       ├─ index.js
       ├─ package.json
       └─ node_modules
          └─ bar
             ├─ index.js
             └─ package.json
    
    • 依赖树层级太深,会导致 Windows 上的目录路径过长问题
    • 相同包在不同的依赖项中需要时,会存在多个相同副本

    第二阶段:npm@3 版本,扁平化处理

    所有的依赖都被拍平到node_modules目录下,不再有很深层次的嵌套关系。这样在安装新的包时,根据 node require 机制,会不停往上级的node_modules当中去找,如果找到相同版本的包就不会重新安装,解决了大量包重复安装的问题,而且依赖层级也不会太深。

    node_modules
    ├─ foo
    |  ├─ index.js
    |  └─ package.json
    └─ bar
       ├─ index.js
       └─ package.json
    

    但还是存在一些问题

    • 依赖结构的不确定性
    • 扁平化算法本身的复杂性很高,耗时较长。
    • 项目中仍然可以非法访问没有声明过依赖的包

    这就是为什么会产生依赖结构的不确定问题,也是 lock 文件诞生的原因,无论是package-lock.json(npm 5.x才出现)还是yarn.lock,都是为了保证 install 之后都产生确定的node_modules结构。

    尽管如此,npm/yarn 本身还是存在扁平化算法复杂package 非法访问的问题,影响性能和安全。

    第三阶段:pnpm

    由于扁平化算法的极其复杂,以及会存在多项目间相同依赖副本的情况。pnpm 在尝试解决这些问题时,放弃了扁平化处理 node_modules 的方式。而是采用 硬链+软链 方式。

    node_modules
    ├─ .pnpm
    |  ├─ foo@1.0.0/node_modules/foo
    |  |  └─ index.js
    |  └─ bar@2.0.0/node_modules/bar
    ├─ foo -> .pnpm/foo@1.0.0/node_modules/foo
    └─ bar -> .pnpm/bar@2.0.0/node_modules/bar
    

    node_modules 根目录中的包只是一个符号链接。require('foo') 将执行 node_modules/.pnpm/foo@1.0.0/node_modules/foo/indexjs 中的文件(这里是硬链接),而不是 node_modules/foo/index.js 中的文件。

    这种布局结构的一大好处是只有真正在依赖项中(package.json dependences)的包才能访问

    举个🌰

    安装一个express 依赖,会在 node_modules 中形成这样两个目录结构:

    node_modules/express/...
    node_modules/.pnpm/express@4.17.1/node_modules/xxx
    

    其中第一个路径是 nodejs 正常寻找路径会去找的一个目录,如果去查看这个目录下的内容,会发现里面连个 node_modules 文件都没有:

    ▾ express
        ▸ lib
          History.md
          index.js
          LICENSE
          package.json
          Readme.md
    

    实际上这个文件只是个软连接,它会形成一个到第二个目录的一个软连接(类似于软件的快捷方式),这样 node 在找路径的时候,最终会找到 .pnpm 这个目录下的内容。

    其中这个 .pnpm 是个虚拟磁盘目录,然后 express 这个依赖的一些依赖会被平铺到 .pnpm/express@4.17.1/node_modules/ 这个目录下面,这样保证了依赖能够 require 到,同时也不会形成很深的依赖层级。

    在保证了 nodejs 能找到依赖路径的基础上,同时也很大程度上保证了依赖能很好的被放在一起。

    目前不适用的场景

    前面有提到关于 pnpm 的主要问题在于 symlink(软链接)在一些场景下会存在兼容的问题,可以参考作者在 nodejs 那边开的一个 discussion:https://github.com/nodejs/node/discussions/37509

    在里面作者提到了目前 nodejs 软连接不能适用的一些场景,希望 nodejs 能提供一种 link 方式而不是使用软连接,同时也提到了 pnpm 目前因为软连接而不能使用的场景:

    • Electron 应用无法使用 pnpm
    • 部署在 lambda 上的应用无法使用 pnpm

    一些 nodejs 基础库不支持 symlink 的情况导致使用 pnpm 无法正常工作,不过这些库在迭代更新之后也会支持这一特性。

    总结

    pnpm 方式的实现精髓

    1. 通过软链的形式,使得 require 可以正常引用;同时对非真正依赖的项目做隔离(避免引用依赖的混乱)
    2. .pnpm 的存在避免了循环引用和层级过深的问题(都在其第一层)
    3. 硬链使得不同项目相同依赖只存在一个副本,减少磁盘空间

    未来会做的一些事情

    脱离 nodejs

    具体可以参考 https://github.com/pnpm/pnpm/discussions/3434

    • 安装 pnpm 的, 可以基本上脱离掉 nodejs 这个 runtime 去进行安装使用。
    • 可以通过 pnpm 来使用不同版本的 nodejs 来去做依赖安装,类似于 nvm 提供的功能。

    目前该特性其实已经到了 beta 版本,可以参考 https://www.npmjs.com/package/@pnpm/beta 这个包。管理不同版本的 nodejs 功能可以参考 env 这个子命令: https://pnpm.io/cli/env

    使用 rust 写一些模块

    具体可以看 https://github.com/pnpm/pnpm/discussions/3419 这个 discussion 讨论的内容,大概就是作者希望给 pnpm 的一些子命令提供一些 rust 的 cli wrapper 来做提升性能使用。

    相关文章

      网友评论

        本文标题:什么是 PNPM?

        本文链接:https://www.haomeiwen.com/subject/jcyozltx.html