该从什么角度思考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 的代码时就直接报错了。

总结

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

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

 


相关文章
|
11天前
|
分布式计算 资源调度 Hadoop
Spark Standalone与YARN的区别?
本文详细解析了 Apache Spark 的两种常见部署模式:Standalone 和 YARN。Standalone 模式自带轻量级集群管理服务,适合小规模集群;YARN 模式与 Hadoop 生态系统集成,适合大规模生产环境。文章通过示例代码展示了如何在两种模式下运行 Spark 应用程序,并总结了两者的优缺点,帮助读者根据需求选择合适的部署模式。
33 3
|
1月前
|
分布式计算 资源调度 Hadoop
Spark Standalone与YARN的区别?
【10月更文挑战第5天】随着大数据处理需求的增长,Apache Spark 成为了广泛采用的大数据处理框架。本文详细解析了 Spark Standalone 与 YARN 两种常见部署模式的区别,并通过示例代码展示了如何在不同模式下运行 Spark 应用程序。Standalone 模式自带轻量级集群管理,适合小规模集群或独立部署;YARN 则作为外部资源管理器,能够与 Hadoop 生态系统中的其他应用共享资源,更适合大规模生产环境。文章对比了两者的资源管理、部署灵活性、扩展性和集成能力,帮助读者根据需求选择合适的部署模式。
23 1
|
28天前
|
缓存 资源调度 JavaScript
npx与npm的差异解析,以及包管理器yarn与Node版本管理工具nvm的使用方法详解
npx与npm的差异解析,以及包管理器yarn与Node版本管理工具nvm的使用方法详解
30 0
|
3月前
|
资源调度 前端开发 JavaScript
谈后端人眼里的 nvm、yarn、pnpm……
虽然我是做后端的,但也时常关注前端,只是最近觉得前端的各种工具名称太眼花缭乱了,nvm、yarn、pnpm、taro……
27 4
|
5月前
|
资源调度 分布式计算 监控
Spark Standalone与YARN的区别?
【6月更文挑战第17天】Spark Standalone与YARN的区别?
326 57
|
5月前
|
资源调度 分布式计算 安全
YARN的FIFO调度器和Capacity Scheduler调度器在资源分配上有何区别?
【6月更文挑战第20天】YARN的FIFO调度器和Capacity Scheduler调度器在资源分配上有何区别?
71 11
|
5月前
|
资源调度
npm yarn 启动报错【已解决】
npm yarn 启动报错【已解决】
59 2
|
5月前
|
存储 缓存 资源调度
npm、yarn与pnpm详解
npm、yarn与pnpm详解
131 0
2071 verbose node v16.6.0 2072 verbose npm v7.19.1或者 no such file or directory, lstat ‘D:\wor
该博客文章提供了解决在使用npm版本7.19.1时出现的"no such file or directory"错误的具体方法,建议通过降级npm到6.14.8版本来解决问题,并确认了该方法可以成功安装node_modules。
2071 verbose node v16.6.0 2072 verbose npm v7.19.1或者 no such file or directory, lstat ‘D:\wor
|
3月前
|
缓存 JavaScript 前端开发
成功解决:npm 版本不支持node.js。【 npm v9.1.2 does not support Node.js v16.6.0.】
这篇文章介绍了如何解决npm版本与Node.js版本不兼容的问题,提供了查看当前npm和Node.js版本的步骤,以及如何根据Node.js版本选择合适的npm版本并进行升级的详细指导。
成功解决:npm 版本不支持node.js。【 npm v9.1.2 does not support Node.js v16.6.0.】