pnpm比npm、yarn好在哪里?

前言

pnpm对比npm/yarn的优点:

  • 更快速的依赖下载
  • 更高效的利用磁盘空间
  • 更优秀的依赖管理
    我们按照包管理工具的发展历史,从 npm2 开始讲起:

npm2

使用早期的npm1/2安装依赖,node_modules文件会以递归的形式呈现,严格按照package.json结构以及次级依赖的package.json将依赖安装到各自的node_modules中,直至次级依赖不再依赖其他模块

举例:

  1. foo -> bar,foo依赖于bar
node_modules
└─ foo
   ├─ index.js
   ├─ package.json
   └─ node_modules
      └─ bar
         ├─ index.js
         └─ package.json
  1. foo1 -> bar,foo2 -> bar,bar会被安装两次
node_modules
├─ foo1
│  ├─ index.js
│  ├─ package.json
│  └─ node_modules
│      └─ bar
│          ├─ index.js
│          └─ package.json
└─ foo2
   ├─ index.js
   ├─ package.json
   └─ node_modules
       └─ bar
           ├─ index.js
           └─ package.json
  1. 一些其他问题
    ● 依赖层级太深,会导致文件路径过长(windows 的文件路径最长是 260 多个字符,这样嵌套是会超过 windows 路径的长度限制的。)
    ● 这样的嵌套,重复的包被安装,导致node_modules文件体积巨大,占用过多的磁盘空间

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

npm3/yarn

yarn 是怎么解决依赖重复很多次,嵌套路径过长的问题的呢?
铺平。所有的依赖不再一层层嵌套了,而是全部在同一层,采用“扁平化”的方式去管理依赖,这样也就没有依赖重复多次的问题了,也就没有路径过长的问题了。

举例:

  1. foo1 -> bar,foo2 -> bar
node_modules
├─ bar
│  ├─ index.js
│  └─ package.json
├─ foo1
│  ├─ index.js
│  └─ package.json
└─ foo2
   ├─ index.js
   └─ package.json
  1. foo1 -> bar@1.0.0,foo2 -> bar@2.0.0
node_modules
├─ bar@1.0.0
│  ├─ index.js
│  └─ package.json
├─ foo1
│  ├─ index.js
│  └─ package.json
└─ foo2
   ├─ index.js
   └─ package.json
   └─ node_modules
       └─ bar@2.0.0
           ├─ index.js
           └─ package.json

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

使用扁平化的方案,解决了npm2中出现的问题,但是也带来一些问题:

  1. 幽灵依赖
    就是明明没有在dependencies中声明的依赖,但是却可以require进来。很容易理解,就是依赖都铺平了,那依赖的依赖也是可以找到的。

出现幽灵依赖是有隐患的,比如:因为没有显式依赖,万一有一天别的包不依赖这个包了,那你的代码也就不能跑了,因为你依赖这个包,但是现在不会被安装了。

  1. 浪费磁盘空间的问题
    上面提到的依赖包有多个版本的时候,只会提升一个,那其余版本的包不还是复制了很多次么,依然有浪费磁盘空间的问题。

那社区有没有解决这俩问题的思路呢?
当然有,这不是 pnpm 就出来了嘛。
那 pnpm 是怎么解决这俩问题的呢?

pnpm

回想下 npm3 和 yarn 为什么要做 node_modules 扁平化?不就是因为同样的依赖会复制多次,并且路径过长在 windows 下有问题么?
那如果不复制呢,比如通过 link。
首先介绍下 link,也就是软硬连接,这是操作系统提供的机制。

  1. 硬连接就是同一个文件的不同引用,
  2. 而软链接是新建一个文件,文件内容指向另一个路径。
  3. 当然,这俩链接使用起来是差不多的。

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

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

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

再把 node_modules 删掉,然后用 pnpm 重新装一遍,执行 pnpm install。
你会发现它打印了这样一句话:
预览
包是从全局 store 硬连接到虚拟 store 的,这里的虚拟 store 就是 node_modules/.pnpm。
只要是在同一台机器下,下次安装依赖的时候pnpm会先检查store目录,如果有你需要安装的依赖则会通过一个硬链接到你的项目中去,而不是重新安装依赖。这也就表明为什么pnpm性能这么突出了,最大程度的节省了时间消耗和磁盘空间。

我们打开 node_modules 看一下:
在这里插入图片描述

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

展开 .pnpm 看一下:
预览

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

比如 .pnpm 下的 expresss,这些都是软链接,
在这里插入图片描述

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

举例说明:

  1. 项目依赖foo@1.0.0
node_modules
├─ .pnpm
│   └─ foo@1.0.0
│        └─ node_modules
│            └─ foo -> <store>/foo
│                ├─ index.js
│                └─ package.json
│
└─ foo
  1. 项目依赖了foo@1.0.0、bar@1.0.0,bar也依赖了foo@1.0.0
node_modules
├─foo -> ./.pnpm/foo@1.0.0/node_modules/foo
├─bar -> ./.pnpm/bar@1.0.0/node_modules/bar
└─.pnpm
    ├─ bar@1.0.0
    │   └─ node_modules
    │       ├─ foo -> ../../foo@1.0.0/node_modules/foo
    │       └─ bar -> <store>/bar
    └─ foo@1.0.0
        └─ node_modules
            └─ foo -> <store>/foo

为什么需要同软链接的方式去引用实际的依赖呢?其实这样设计的目的是解决“幽灵依赖”问题,只有声明过的依赖才会以软链接的形式出现在node_modules目录中,在实际项目中引用的是软链接,软链接指向的是 .pnpm 的真实依赖,所以在日常开发中不会引用到未在 package.json 声明的包。

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

预览

这就是 pnpm 的实现原理。
那么回过头来看一下,pnpm 为什么优秀呢?
首先,最大的优点是节省磁盘空间呀,一个包全局只保存一份,剩下的都是软硬连接,这得节省多少磁盘空间呀。
其次就是快,因为通过链接的方式而不是复制,自然会快。
这也是它所标榜的优点:

预览

相比 npm2 的优点就是不会进行同样依赖的多次复制。
相比 yarn 和 npm3+ 呢,那就是没有幽灵依赖,也不会有没有被提升的依赖依然复制多份的问题。
这就已经足够优秀了,对 yarn 和 npm 可以说是降维打击。
Workspace
现代前端工程中居多都是使用 Lerna 管理 monorepo 类型的项目,每个人都清楚它的作用,而 pnpm 也是对此进行了友好的支持。与 Lerna 不同的是 pnpm 使用特殊的包选择器语法限制命令,不像 Lerna 那样需要很长难记的命令去标识。

一个 monorepo 工程,目录中必须要拥有管理工作区的配置文件(pnpm.workspace.yaml),相比其它包管理工具的工作区文件其实都大同小异。

packages:
  # 所有在 packages/ 和 components/ 子目录下的 package
  - 'packages/**'
  - 'components/**'
  # 不包括在 test 文件夹下的 package
  - '!**/test/**'

一些常用于管理 monorepo 的命令:
● 精确选择一个 repo <@scope/package>,或选择一组 repo <@scope/*>,再或者相对路径选择。

pnpm dev --filter @byted-ehi/basic-list
pnpm dev --filter apps/*
pnpm dev --filter ./apps/admin-order-manage

● 选择一个 repo 以及所属 repo 的依赖项,例如:会运行 basic-list 下的所有依赖的 dev。这个命令的意思是在 @byted-ehi/basic-list 包的所有子目录以及这些子目录中的所有文件中执行开发脚本。

pnpm dev --filter @byted-ehi/basic-list...

● 只选择某个 repo 的依赖项,与上面的区别是不包含 repo。例如:会运行 repo 下所有依赖的 dev,不包含repo 本身。这个命令的意思是在 @byted-ehi/basic-list 包的所有子目录中执行开发脚本。

pnpm dev --filter @byted-ehi/basic-list^...

● 选择指定目录下的所有 repo。

pnpm dev --filter ./apps

总结
pnpm 最近经常会听到,可以说是爆火。本文我们梳理了下它爆火的原因:
npm2 是通过嵌套的方式管理 node_modules 的,会有同样的依赖复制多次的问题。
npm3+ 和 yarn 是通过铺平的扁平化的方式来管理 node_modules,解决了嵌套方式的部分问题,但是引入了幽灵依赖的问题,并且同名的包只会提升一个版本的,其余的版本依然会复制多次。
pnpm 则是用了另一种方式,不再是复制了,而是都从全局 store 硬连接到 node_modules/.pnpm,然后之间通过软链接来组织依赖关系。
这样不但节省磁盘空间,也没有幽灵依赖问题,安装速度还快,从机制上来说完胜 npm 和 yarn。
pnpm 就是凭借这个对 npm 和 yarn 降维打击的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/505904.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

简单了解波 Mono-repo Multi-repo(Poly-repo)

Mono-repo 和 Multi-repo 是软件开发中代码管理的两个不同策略。Mono-repo & Multi-repo 孰优孰劣是个老生常谈得话题了&#xff0c;这里就不 PK 了&#xff0c;“略微”看下两者区别。 当我们使用 Git 作为版本控制系统管理项目的代码时&#xff0c;那么 monorepo 与 mul…

模拟游戏《幸福工厂》好玩吗?《幸福工厂》怎么在mac电脑上打开?

关于《幸福工厂》这款游戏是否好玩&#xff0c;普遍的玩家反馈和评价表明&#xff0c;《幸福工厂》&#xff08;Satisfactory&#xff09;因其深度的工厂建造模拟、自由度极高的探索以及精美的图形表现而受到许多玩家的喜爱。它允许玩家在一个开放的世界中规划并建立复杂的生产…

02-JDK新特性-Lambda表达式

JDK新特性 Lambda表达式 什么是Lambda表达式 Lambda表达式是一个匿名代码块&#xff0c;用于简单的传递一段代码片段。 Lambda表达式标准格式 格式&#xff1a;(形式参数) -> {代码块} 形式参数 如果有多个参数&#xff0c;参数只见用逗号隔开&#xff1b;如果没有&…

leetcode90. 子集 II

去重逻辑&#xff1a; 关键是画出递归树&#xff01;当我们即将进入第二个2的递归的时候&#xff0c;发现isVisit数组是100&#xff0c;也就是说这俩重复的数是False&#xff0c;并且这俩在nums值相同&#xff0c;所以写出去重逻辑&#xff01; class Solution { public:vector…

Mock.js的基本使用

mock顾名思义&#xff0c;就是模拟的意思&#xff0c;它模拟什么呢&#xff1f;假设我们在开发的过程中&#xff0c;我们需要使用到接口&#xff0c;但是后端接口并没有完善&#xff0c;那么我们就可以使用到mock.js&#xff0c;它可以随机生成数据&#xff0c;拦截AJAX请求&am…

mysql执行脚本导入表和数据后中文注释乱码解决

本人在使用不同版本下进行操作时&#xff0c;就会出现中文乱码的问题。例如我本地安装mysql8&#xff0c;服务器安装的是mysql5&#xff0c;然后本地连接服务器的mysql后&#xff0c;执行SQL脚本之后发现中文全部乱码 使用工具查看&#xff0c;注释也都是乱码 解决方案 本地…

vue 视频添加水印

1.需求背景 其实腾讯云点播的api也支持视频水印&#xff0c;但是只有单个水印&#xff0c;大概效果是这样子的&#xff0c;不满足我们的需求&#xff0c;我们的需求是需要视频中都是水印。 腾讯云点播水印 项目需求的水印&#xff08;主要是防录屏,最后的实现效果是这样&…

AcWing 4405. 统计子矩阵:做题笔记

目录 暴力思路 代码 前缀和双指针 代码 解释 推荐博客 这道题的主要思路就是枚举所有的子矩阵&#xff0c;判断符合条件的子矩阵的个数。 暴力思路 我服了&#xff0c;其实我最开始没有想到 &#xff1a;枚举所有的子矩阵 这样一个很有总结性的要点。 我是想着哦我先…

重读Java设计模式: 深入探讨建造者模式,构建复杂对象的优雅解决方案

引言 在软件开发中&#xff0c;有时需要构建具有复杂结构的对象&#xff0c;如果直接使用构造函数或者 setter 方法逐个设置对象的属性&#xff0c;会导致代码变得冗长、难以维护&#xff0c;并且容易出错。为了解决这个问题&#xff0c;我们可以使用建造者模式。 一、建造者…

Jamba LLM模型:破解大型上下文窗口挑战的AI新星

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

使用Postman进行websocket接口测试

因为最近要搞关于基于AI的文本接口测试.需要用到websocket协议,于是看了一下发现postman也可以测而且很方便 位置 File->New->WebSocket 可以看到不止WebSocket还支持其他的各种协议 使用 首先先点击connect进行连接 连接成功之后可以选择多种文本格式添加请求参数 每…

打开DICOM文件需要注意到的点

DICOM图片用来存储医学信息 我一般处理的是图像信息&#xff0c;总结一下踩过的坑 打开DICOM文件需要注意到的点 DICOM图片使用python进行打开一定要注意窗口问题&#xff0c;dicom文件里面存储了很多其他的附加信息&#xff0c;不仅仅是图片&#xff0c;其中最重要的一个条就…

力扣刷题Days29-128.最长连续数列(js)

目录 1&#xff0c;题目 2&#xff0c;代码 2.1自己实现 2.2哈希表 3&#xff0c;学习与收获 枚举思想&#xff1a; 遍历的核心逻辑 碎碎念 本题 先是想到利用数组排序&#xff0c;从而简化遍历处理逻辑&#xff0c;再在提交错误提醒的情况下&#xff0c;考虑到数组中存…

Tab切换(Html+JavaScript+Css)

1.CSS样式 <style>* {margin: 0;padding: 0;}.tab {width: 590px;height: 340px;margin: 20px;border: 1px solid #e4e4e4;margin-left: 300px;}.tab-nav {width: 100%;height: 60px;line-height: 60px;display: flex;justify-content: space-between;}.tab-nav h3 {font…

Zeppelin安装

Zeppelin是一个基于Web的开源数据分析可视化工具&#xff0c;它提供了一个交互式的笔记本界面&#xff0c;用于在大数据环境中进行数据探索、数据分析、数据可视化和协作。Zeppelin的主要特点包括多语言支持、可视化功能、数据共享和协作&#xff0c;以及扩展性。它支持多种编程…

C++ 数组 结构编程题

一 求100以内的所有素数 /* * 需要标记2~100 之间的数是否处理 * 用数组&#xff0c;初始为0 表示都是素数&#xff0c;如果 判断为合数则置为1过用 */ #include<stdio.h> #include<math.h> int main() {const int n 100;int isPrim[n 1] { 0 };int i, j;for (…

C++ MFC

C是一种静态数据类型检查的、支持多重编程范式的程序设计语言&#xff0c;支持过程化程序设计、数据抽象、面向对象程序设计、制作图标等泛型程序设计的多种程序设计风格。 MFC(Microsoft Foundation Classes)&#xff0c;是一个微软公司提供的类库&#xff0c;以C类的形式封装…

JAVAEE之CSS

1.CSS 是什么&#xff1f; 层叠样式表 (Cascading Style Sheets). CSS 能够对网页中元素位置的排版进行像素级精确控制, 实现美化页面的效果. 能够做到页面的样式和结构分离. 1.1 CSS和HTML的区别 CSS&#xff0c;全称为层叠样式表(Cascading Style Sheets)&#xff0c;是…

【Spring Boot 源码学习】ConditionEvaluationReport 日志记录上下文初始化器

《Spring Boot 源码学习系列》 ConditionEvaluationReport 日志记录上下文初始化器 一、引言二、往期内容三、主要内容3.1 源码初识3.2 ConditionEvaluationReport 监听器3.3 onApplicationEvent 方法3.4 条件评估报告的打印展示 四、总结 一、引言 上篇博文《共享 MetadataRe…

redis和数据库数据不一直问题,缓存常见的三大问题

文章目录 数据一致性缓存常见问题缓存穿透缓存击穿缓存雪崩 数据一致性 1 思路 查询数据的时候&#xff0c;如果缓存未命中&#xff0c;则查询数据库&#xff0c;将数据写入缓存设置超时时间修改数据时&#xff0c;先修改数据库&#xff0c;在删除缓存。 2 代码实现 修改更…