• 该从什么角度思考npm、yarn与pnpm的区别


    随着 pnpm 越来越火爆,相信大家听到的次数也越来越多,今天我们也来一起学习一下大力推崇的 pnpm 到底有什么神奇之处?


    目录

    一、npm/yarn install 原理

    二、npm

    三、yarn

    四、pnpm

    五、更好的实用性

    总结


    一、npm/yarn install 原理

      npm/yarn install 时发生了什么?主要分为两个部分,首先是:包如何到达 node_modules 当中,其次是:node_modules 内部是如何管理依赖的;

      执行命令以后,首先会构建依赖树,然后针对每个节点下的包,会经历四个步骤:

    1. 将依赖包的版本区间解析为某个具体的版本号
    2. 下载对应依赖的 tar 包到本地离线镜像
    3. 将依赖从离线镜像解压到本地缓存
    4. 将依赖从缓存拷贝到当前目录的 node_modules 目录

    然后,对应的包就会到达项目的 node_modules 中。 

    那么,这些依赖在 node_modules 当中是什么样的目录结构呢?也就是我们上面输的 依赖树是什么样子呢?这个要看下面详细讲解,不同的版本是不一样的!

    二、npm

    我们按照 npm 包管理工具的发展历史,从 npm2 开始讲起:

       用 node 版本管理工具把 node 版本降到 4,那 npm 版本 就是 2.x

     然后我们新建一个Demo,执行下 npm init -y,快速创建一个 package.json

     然后执行 npm install express ,那么 express 包和它的依赖都会被下载下来:

    展开 express,它也有 node_modules 

     再展开几层,我们会发现每个依赖都有自己的 node_modules

     

     也就是 npm2 的 node_modules 是嵌套的

    node_modules
    └─ express
       ├─ index.js
       ├─ package.json
       └─ node_modules
          └─ accepts
             ├─ index.js
             └─ package.json

    这种结构就是我们上面所说的 依赖树

    现在的 accepts 当中又有依赖,然后就会继续嵌套下去。试想一下这样的设计存在什么问题呢?

    1. 依赖层级太深,导致致命的问题是 Windows 的文件路径最长是 260 多个字符,这样嵌套是会超过 Windows 路径的长度限制的。
    2. 大量重复的包被安装,文件体积超大。比如跟 express 统计目录下有一个 foo,两者都依赖于同一个版本的 Lodash,那么 Lodash 会分别在两者的 node_modules 中被安装,也就是重复安装,会占据比较大的磁盘空间。
    3. 模块实例不能共享。比如 React 有一些内部变量,在两个不同包引入的 React 不是同一个模块实例,因此无法共享内部变量,导致一些不可预知的 bug。

    当时 npm 还没解决,社区就出来新的解决方案了,就是 yarn:↓ 

    三、yarn

      yarn 是怎么解决依赖重复很多次,嵌套路径过长的问题呢?

      铺平,也就是说所有的依赖不再一层一层嵌套了,而是全部在同一层,这样也就没有依赖重复多次的问题了,也不会存在路径过长的问题了;

    我们把 node_modules 删了,用 yarn 再重新安装下,执行 yarn add express

    这时候 node_modules 就是这样了:

    全部铺平在了一层,展开下面的包大部分是没有二层 node_modules 的:

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


    但是多展开几个依赖包,大家会发现,为什么还会有嵌套呢?

            因为一个包是可能有多个版本的,提升只能提升一个,所以后面再遇到相同包的不同版本,依然还是用嵌套的方式。 


     npm 后来升级到 3 之后,也是采用这种铺平的方案了,和 yarn 很类似。当然,yarn 还实现了 yarn.lock 来锁定版本,这个功能 npm 也实现了。

    但是 yarn 和 npm 都采用铺平了的方案,这种方案就没有问题了吗?

    并不是,它照样还是存在诸多问题的,梳理一下:

    1. 依赖结构的不确定性
    2. 扁平化算法本身的复杂性很高,耗时较长。
    3. 幽灵依赖( 通俗一点的说:可以非法访问没有声明过依赖的包 )

    后来两个都好理解,但是第一点的 不确定性 该如何理解呢? 

    假如现在项目依赖两个包 Barry 和 Lishen,这两个包的依赖又是这样的:

     那么 npm / yarn install 的时候,通过扁平化处理之后究竟是怎么样子?

     究竟是这样?

     还是这样呢?

     答案是:都有可能,取决于 Barry Lishen package.json 中的位置,如果 Barry 声明在前则是前面的结构,否则就是后面的结构。这就是为什么依赖会产生依赖结构的 不确定 问题,也就是前面说的 lock 文件诞生的原因,无论是 package-lock.json (npm 5.X 以后才有的,也就是npm3)还是 yarn.lock ,都是为了保证 install 之后都产生确定的 node_modules 结构。


     那 pnpm 是怎么解决这俩问题的呢?

    四、pnpm

    回想下 npm3 yarn 为什么要做 node_modules 扁平化?不就是因为同样的依赖会复制多次,并且路径过长在 windows 下有问题么?

    那如果不复制呢,比如通过 link

    首先介绍下 link,也就是软硬连接,这是操作系统提供的机制,硬连接就是同一个文件的不同引用,而软链接是新建一个文件,文件内容指向另一个路径。当然,这俩链接使用起来是差不多的。

    如果不复制文件,只在全局仓库保存一份 npm 包的内容,其余的地方都 link 过去呢?

    这样不会有复制多次的磁盘空间浪费,而且也不会有路径过长的问题。因为路径过长的限制本质上是不能有太深的目录层级,现在都是各个位置的目录的 link,并不是同一个目录,所以也不会有长度限制。

    没错,pnpm 就是通过这种思路来实现的。

    再把 node_modules 删掉,然后用 pnpm 重新装一遍,执行 pnpm install。

    你会发现它打印了这样一句话:

     包是从全局 store 硬连接到虚拟 store 的,这里的虚拟 store 就是 node_modules/.pnpm

    我们打开 node_modules 看一下:

    确实不是扁平化的了,依赖了 express,那 node_modules 下就只有 express,没有幽灵依赖。

    展开 .pnpm 看一下:

    所有的依赖都在这里铺平了,都是从全局 store 硬连接过来的,然后包和包之前的依赖关系是通过软链接组织的。 .pnpm/node_modules 下的 expresss,这些都是软链接。

    也就是说,所有的依赖都是从全局 store 硬连接到了 .pnpm/node_modules 下,然后之间通过软链接来相互依赖。

     官方给了一张原理图,配合着看一下就明白了:pnpm官方地址

     这就是 pnpm 的实现原理

    那么回过头来看一下,pnpm 为什么优秀呢?

    首先,最大的优点是节省磁盘空间呀,一个包全局只保存一份,剩下的都是软硬连接,这得节省多少磁盘空间呀。

    其次就是,因为通过链接的方式而不是复制,自然会

     

    五、更好的实用性

      说了这么多,估计你会觉得 pnpm 挺复杂的,是不是用起来成本很高呢?

      恰好相反,pnpm 使用起来十分简单,如果你之前有 npm/yarn 的使用经验,甚至可以无缝迁移到 pnpm 上来。

      仅仅是这些吗?下面还有更重要的就是项目管理方式的支持

    个人感觉:pnpm 可以更好的支持 Multirepo 项目过渡到  Monorepo 项目,原因如下:

    如果 A 依赖 X,B 依赖 X,还有一个 C,它不依赖 X,但它代码里面用到了 X。由于依赖提升的存在,npm/yarn 会把 X 放到根目录的 node_modules 中,这样 C 在本地是能够跑起来的,因为根据 node 的包加载机制,它能够加载到 monorepo 项目根目录下的 node_modules 中的 X。但试想一下,一旦 C 单独发包出去,用户单独安装 C,那么就找不到 X 了,执行到引用 X 的代码时就直接报错了。

     

    总结


    当然,今天只是深入的学习,那种包管理方式更适合大家具体情况还是要看我们自己的项目,可能因人而异,因项目而异,因业务而异~~~

    欢迎大家一起讨论学习~~~

     

  • 相关阅读:
    x86_64 ubuntu22.04 源码编译WebKit-7615.3.12.11.3
    实际并行workers数量不等于postgresql.conf中设置的max_parallel_workers_per_gather数量
    靶场上新:Openfire身份认证绕过
    SNMP 协议解析(一)
    MQTT协议------上
    docker内更新显卡cuda cudnn
    分析实时云渲染在小程序中可行性应用
    langchain教程-(1)Prompt模板
    【优化组合】基于遗传算法求解不同投资比例的收益附matlab代码
    深度学习笔记(四)——循环神经网络(Recurrent Neural Network, RNN)
  • 原文地址:https://blog.csdn.net/weixin_56650035/article/details/126842623