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

简介: 该从什么角度思考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

20210527153548522.png

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

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

20210527153548522.png

展开 express,它也有 node_modules

20210527153548522.png

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

20210527153548522.png

也就是 npm2 的 node_modules 是嵌套的

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

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

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


依赖层级太深,导致致命的问题是 Windows 的文件路径最长是 260 多个字符,这样嵌套是会超过 Windows 路径的长度限制的。

大量重复的包被安装,文件体积超大。比如跟 express 统计目录下有一个 foo,两者都依赖于同一个版本的 Lodash,那么 Lodash 会分别在两者的 node_modules 中被安装,也就是重复安装,会占据比较大的磁盘空间。

模块实例不能共享。比如 React 有一些内部变量,在两个不同包引入的 React 不是同一个模块实例,因此无法共享内部变量,导致一些不可预知的 bug。当时 npm 还没解决,社区就出来新的解决方案了,就是 yarn:↓

三、yarn

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


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


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


这时候 node_modules 就是这样了:

20210527153548522.png

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


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


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


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


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


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


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


依赖结构的不确定性。

扁平化算法本身的复杂性很高,耗时较长。

幽灵依赖( 通俗一点的说:可以非法访问没有声明过依赖的包 )

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


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

d485e3947a7d406cab87b32fbd04b5b7.png

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

究竟是这样?

20210527153548522.png

还是这样呢?

20210527153548522.png

答案是:都有可能,取决于 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。


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

20210527153548522.png

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

我们打开 node_modules 看一下:

20210527153548522.png

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

展开 .pnpm 看一下:

20210527153548522.png

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


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


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

01ebd755782e4c909dad0843d3544acf.jpeg

这就是 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 的代码时就直接报错了。

总结

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

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

 


相关文章
|
2天前
|
存储 资源调度 JavaScript
一文带你了解PNPM以及 npm,yarn,pnpm区别
一文带你了解PNPM以及 npm,yarn,pnpm区别
|
1月前
|
分布式计算 资源调度 Hadoop
Spark Standalone与YARN的区别?
本文详细解析了 Apache Spark 的两种常见部署模式:Standalone 和 YARN。Standalone 模式自带轻量级集群管理服务,适合小规模集群;YARN 模式与 Hadoop 生态系统集成,适合大规模生产环境。文章通过示例代码展示了如何在两种模式下运行 Spark 应用程序,并总结了两者的优缺点,帮助读者根据需求选择合适的部署模式。
64 3
|
2月前
|
分布式计算 资源调度 Hadoop
Spark Standalone与YARN的区别?
【10月更文挑战第5天】随着大数据处理需求的增长,Apache Spark 成为了广泛采用的大数据处理框架。本文详细解析了 Spark Standalone 与 YARN 两种常见部署模式的区别,并通过示例代码展示了如何在不同模式下运行 Spark 应用程序。Standalone 模式自带轻量级集群管理,适合小规模集群或独立部署;YARN 则作为外部资源管理器,能够与 Hadoop 生态系统中的其他应用共享资源,更适合大规模生产环境。文章对比了两者的资源管理、部署灵活性、扩展性和集成能力,帮助读者根据需求选择合适的部署模式。
34 1
|
2月前
|
缓存 资源调度 JavaScript
npx与npm的差异解析,以及包管理器yarn与Node版本管理工具nvm的使用方法详解
npx与npm的差异解析,以及包管理器yarn与Node版本管理工具nvm的使用方法详解
63 0
|
4月前
|
资源调度 前端开发 JavaScript
谈后端人眼里的 nvm、yarn、pnpm……
虽然我是做后端的,但也时常关注前端,只是最近觉得前端的各种工具名称太眼花缭乱了,nvm、yarn、pnpm、taro……
36 4
|
6月前
|
资源调度 分布式计算 安全
YARN的FIFO调度器和Capacity Scheduler调度器在资源分配上有何区别?
【6月更文挑战第20天】YARN的FIFO调度器和Capacity Scheduler调度器在资源分配上有何区别?
84 11
|
6月前
|
存储 缓存 资源调度
npm、yarn与pnpm详解
npm、yarn与pnpm详解
146 0
|
3月前
|
资源调度 分布式计算 Hadoop
YARN(Hadoop操作系统)的架构
本文详细解释了YARN(Hadoop操作系统)的架构,包括其主要组件如ResourceManager、NodeManager和ApplicationMaster的作用以及它们如何协同工作来管理Hadoop集群中的资源和调度作业。
155 3
YARN(Hadoop操作系统)的架构
|
3月前
|
资源调度 分布式计算 Hadoop
使用YARN命令管理Hadoop作业
本文介绍了如何使用YARN命令来管理Hadoop作业,包括查看作业列表、检查作业状态、杀死作业、获取作业日志以及检查节点和队列状态等操作。
69 1
使用YARN命令管理Hadoop作业
|
4月前
|
资源调度 分布式计算 算法
【揭秘Yarn调度秘籍】打破资源分配的枷锁,Hadoop Yarn权重调度全攻略!
【8月更文挑战第24天】在大数据处理领域,Hadoop Yarn 是一种关键的作业调度与集群资源管理工具。它支持多种调度器以适应不同需求,默认采用FIFO调度器,但可通过引入基于权重的调度算法来提高资源利用率。该算法根据作业或用户的权重值决定资源分配比例,权重高的可获得更多计算资源,特别适合多用户共享环境。管理员需在Yarn配置文件中启用特定调度器(如CapacityScheduler),并通过设置队列权重来实现资源的动态调整。合理配置权重有助于避免资源浪费,确保集群高效运行,满足不同用户需求。
64 3

相关实验场景

更多

推荐镜像

更多