通过 pnpm 安装依赖包会发生什么

通过 pnpm 安装依赖包会发生什么

通过 pnpm 下载的包都是放在一个全局目录(.pnpm-store)下,默认是在 ${os.homedir}/v3/.pnpm-store,如果我们不确定在哪里,可以输入下面的命令手动配置:

pnpm set store-dir [dir] --global

比如:

pnpm set store-dir E:\pnpm\store --global 

如果我们此时随便安装一个包,比如 express 那么首先放在全局目录下,之后在项目中创建一个硬链接指向全局目录。

在一个项目中安装 express
请添加图片描述
在另一个项目安装 express
请添加图片描述

我们发现上面的打印的消息不一样,一个是 reused 0, downloaded 64,另一个是 reused 64, download 0。

当我们通过 pnpm 安装依赖包,会首先在全局目录下查看是否存在相同的版本的包,如果存在,就可以直接复用,创建一个硬链接指向全局目录中已经安装的包就行了(所以它叫 reused,重复使用嘛)。如果版本不同或者之前没有安装这个包,才会下载到全局目录中,然后在项目中创建一个硬链接指向全局目录。

如果我们查看项目中的 node_modules 目录,会发现存在以下比较奇怪的结构(前提是依赖包是通过 pnpm 安装的)

假设我们安装了一个 a@1.0.0 这个依赖包

node_modules
└── .pnpm└── a@1.0.0└── node_modules└── a  ->  <.pnpm-store>/a├── index.js└── package.json

我们看看这种目录里各个文件夹代表什么意思。

最外层的 node_modules 就是我们项目中的 node_modules,而 .pnpm 就是使用 pnpm 安装依赖包时会自动生成的一个目录,a@1.0.0 就是我们通过 pnpm 安装的依赖包名+版本号。这些都比较容易理解。令人困惑就是 a@1.0.0 中的结构。

前面讲到了我们通过 pnpm 安装依赖的包的时候,是先下载到全局目录(.pnpm-store)下的,然后在项目中通过硬链接到全局目录中的文件(也就是 a 目录下的index.js、package.json 文件是全局目录中的文件,硬链接只能链接文件),实现依赖包的复用。

那为什么在 a@1.0.0 以及 a 中加一个 node_modules 目录呢?

  1. 允许包本身导入自己:比如 a 可以通过 require('a/package.json') 或者 import * as package from "a/package.json" 导入自身的 package.json 文件。
  2. 避免循环符号链接:依赖以及需要依赖的包被放置在一个文件夹下。 对于 Node.js 来说,依赖是在包的内部 node_modules 中或在任何其它在父目录 node_modules 中是没有区别的。

在看一个复杂一点的例子:

node_modules
└── .pnpm├── a@1.0.0|     └── node_modules|            ├── a  ->  <.pnpm-store>/a|            |   ├── index.js|            |   └── package.json|            └── b  -> ../../b@1.0.0/node_modules/b|                ├── index.js|                └── package.json└── b@1.0.0└── node_modules└── b  ->  <.pnpm-store>/b├── index.js└── package.json

假如依赖包 a 中使用了依赖包 b,那么同样是跟依赖包 a 一样的操作,下载到全局目录中,然后在 .pnpm 生成一个依赖包名+版本号的目录(b@1.0.0),同时会将 node_modules/b 硬链接到全局目录中。

不过有点区别的是在 a@1.0.0 中的 node_modules 中也会创建一个目录符号链接指向 b@1.0.0/node_modules/b。此时我们在依赖包 a 中导入依赖包 b,Node 不会使用在 a@1.0.0/node_modules/b 中的 b,而是在它的实际位置 b@1.0.0/node_modules/b 中解析,也就是说“真实”文件其实是在 b@1.0.0/node_modules/b 中的(这种布局乍一看可能很奇怪,但它与 Node 的模块解析算法完全兼容! 解析模块时,Node 会忽略符号链接,直接找到符号链接的文件)。

注意,这里的真实并不是真实文件,这个“真实”文件是从全局目录中硬链接过来的,虽然从文件夹中查看它是存在内存大小的,但是实际上并不存在

虽然以上的示例非常简单。 但是,无论依赖项的数量和依赖关系图的深度如何,布局都会保持这种结构。

在通过 pnpm 安装依赖包时,除了会在 .pnpm 中生成目录外,还是会 node_modules 中生成。

node_modules
├── .pnpm
|     └── a@1.0.0
└── a  -> .pnpm/a@1.0.0/node_modules/a├── index.js└── package.json

此时这个 a 同样是个目录符号链接,链接到 .pnpm/a@1.0.0/node_modules/a 中。因为 Node 需要在 node_modules 查找已安装依赖,否则会报错,提示找不到这个依赖,因此 node_modules 中也是需要存在安装的依赖包,只不过它是一个目录符号链接而已。

这种布局的一大好处是只有真正在依赖项中的包才能访问。使用平铺的 node_modules 结构,所有被提升的包都可以访问。

至于为什么说这是 pnpm 的优势,我们来实际安装一个依赖包看看:

以 express 为例,这是通过 pnpm 安装时生成的目录结构:

node_modules
├─ .pnpm
|    └── express@4.19.2
|          └── node_modules
|                 ├── ...  (还有很多依赖包,这里不展示)
|                 ├── express -> <.pnpm-store>/express
|                 |     ├── index.js
|                 |     └── package.json
|                 └── debug   -> ../../express@4.19.2/node_modules/express
|                       ├── node.js
|                       └── package.json
|
└── express   -> .pnpm/express@4.19.2/node_modules/express├── index.js└── package.json

这是通过 npm 安装生成的目录结构:

node_modules
├── ...  (还有很多依赖包,这里不展示)
├── debug
|     ├── node.js
|     └── package.json
└── express├── index.js└── package.json

乍一看好像 pnpm 更复杂,又有 .pnpm 目录,又有一堆目录符号链接,npm 看起来好像更简洁、干净。在我刚使用 pnpm,我也有这种感觉,但是 npm 这种的结构会导致一个非常愚蠢的问题!

那就是我们明明只安装了一个 express,为什么会在 node_modules 中可以获取到 express 中的依赖呢?由于在 node_modules 存在这些依赖,意味着我们是可以直接在项目中导入的!

import debug from 'debug';

因为 Node 不关注我们项目中的 package.json 定义的安装依赖,只要是在 node_modules 中就可以显示调用。

如果说我们确实在项目中使用 debug 依赖,那么这样直接使用确实可以工作,而且它甚至也能在生成环境中使用,但是我们可能没有考虑到一些情况:

  1. debug 更新了,移除了一些我们目前正在使用的特性,当 express 发布了新版本,我们通过 npm install 更新后会发现我们的项目即便没有任何更改也出现了问题。
  2. 还有一种可能是 express 突然不想使用 debug 了,将其从 dependencies 字段中移除后发布新版本,此时我们 npm install 更新后同样会出现问题。

而 pnpm 这种设计就确保了只有通过 pnpm 安装的依赖才会在 node_modules 生成对应的文件夹,不会像 npm 一样将某个依赖包中的依赖全部都放在 node_modules 中。

当然,npm 是修复了这个问题的,通过配置 npm c set install-strategy shallow 可以将直接安装的依赖才放在 node_modules 中,而依赖包中的依赖则是放在依赖包中的 node_modules 中。但是,我们有多少人知道并使用过这个配置?

比如:

node_modules
└── express├── node_modules|     ├── ...|     └── debug|           ├── node.js|           └── package.json├── index.js└── package.json

通过 npm 安装的依赖并不存在一个全局目录,只要安装的依赖都是放在 node_modules 中,如果我们有非常多项目都依赖了同一个依赖,那就意味着我们要对同一个依赖安装多次,非常占用内存。而 pnpm 则不同,它会放在一个全局目录中进行复用,在项目中的依赖都是一个硬链接而已,虽然在文件夹中查看 node_modules 目录它显示了占用内存,但实际上它并不占用,如果我们是 window 电脑,可以通过 fsutil hardlink list [filename] 查看该文件的硬链接数:

\nvm\store\v3\files\76\6d2e202dd5e520ac227e28e3c359cca183605c52b4e4c95c69825c929356cea772723a9af491a3662d3c26f7209e89cc3a7af76f75165c104492dc6728accc
\leo\pnpm\node_modules\.pnpm\express@4.19.2\node_modules\express\index.js
\$RECYCLE.BIN\S-1-5-21-2040100086-518969392-3969120953-1001\$RPTAE6E\.pnpm\express@4.19.2\node_modules\express\index.js

peerDependencies 的处理

上面的讲解都是基于依赖包内没有 peerDependencies 的情况,如果存在 peerDependencies ,会有不同处理:

如果一个依赖包中没有 peerDependencies,它先创建一个硬链接(b@1.0.0/node_modules/b),然后这个硬链接目录符号链接到其他依赖包中的 node_modules 中,比如前面介绍的例子:

node_modules
└── .pnpm├── a@1.0.0|     └── node_modules|            ├── a  ->  <.pnpm-store>/a|            |   ├── index.js|            |   └── package.json|            └── b  -> ../../b@1.0.0/node_modules/b|                ├── index.js|                └── package.json└── b@1.0.0└── node_modules└── b  ->  <.pnpm-store>/b├── index.js└── package.json

如果一个依赖包存在 peerDependencies,比如依赖包 a 中存在 b、c 两个 peerDependencies:

{"peerDependencies": {"b": "^1.0.0","c": "^1.0.0"}
}

在项目中我们导入了 foo、bar 两个依赖包,都需要 a 这个依赖包,而且这两个依赖包也同时导入了 b、c 两个依赖,但是版本不一样。

foo 需要 a@1.0.0、b@1.0.0、c@1.0.0,而 bar 需要 a@1.0.0、b@1.0.0、c@1.1.0。这时候 a 就会有多组依赖项:

node_modules
└── .pnpm├── a@1.0.0_b@1.0.0+c@1.0.0|     └── node_modules|            ├── a  ->  <.pnpm-store>/a|            ├── b  ->  ../../b@1.0.0|            └── c  ->  ../../c@1.0.0├── a@1.0.0_b@1.0.0+c@1.1.0|     └── node_modules|            ├── a  ->  <.pnpm-store>/a|            ├── b  ->  ../../b@1.0.0|            └── c  ->  ../../c@1.1.0├── b@1.0.0├── c@1.0.0└── c@1.1.0

可以看到本来只需要一个 a@1.0.0 就能搞定,但是因为 peerDependencies 得存在需要根据版本号生成两个依赖项组(a@1.0.0_b@1.0.0+c@1.0.0、a@1.0.0_b@1.0.0+c@1.1.0)。

如果依赖包 a@1.0.0 没有 peer 依赖,但是它依赖的 b@1.0.0 存在 peer 依赖 c@^1,在我们项目中存在 c@1.0.0 及 c@1.1.0,那么会形成如下的结构:

node_modules
└── .pnpm├── a@1.0.0_c@1.0.0|     └── node_modules|            ├── a  ->  <.pnpm-store>/a|            └── b  ->  ../../b@1.0.0_c@1.0.0├── a@1.0.0_c@1.1.0|     └── node_modules|            ├── a  ->  <.pnpm-store>/a|            └── b  ->  ../../b@1.0.0_c@1.1.0├── b@1.0.0_c@1.0.0|     └── node_modules|            ├── b  ->  <.pnpm-store>/b|            └── c  ->  ../../c@1.0.0├── b@1.0.0_c@1.1.0|     └── node_modules|            ├── b  ->  <.pnpm-store>/b|            └── c  ->  ../../c@1.1.0├── c@1.0.0└── c@1.1.0

url 链接,如果我们通过 npm config set registry <registry-url> 改变了 npm 源,那么我们在 .pnpm 目录中可能看到类似 fast-glob@https+++registry.npmmirror.com+fast-glob+-+fast-glob-3.3.2.tgz 这样的 @ 字符后边不是具体版本号的目录名,不用奇怪,就把他当作是版本号即可。因为这个依赖包不是通过从公共注册表中获取的,而是直接从自定义的 NPM 源或镜像获取的。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

若依 Vue3的前后端分离系统管理 创建 使用

RuoYi 若依官方网站 |后台管理系统|权限管理系统|快速开发框架|企业管理系统|开源框架|微服务框架|前后端分离框架|开源后台系统|RuoYi|RuoYi-Vue|RuoYi-Cloud|RuoYi框架|RuoYi开源|RuoYi视频|若依视频|RuoYi开发文档|若依开发文档|Java开源框架|Java|SpringBoot|SrpingBoot2.0…

IP/TCP/UDP协议的关键知识点

导语&#xff1a;网络协议是理解网络情况的基础&#xff0c;当遇到网络问题时&#xff0c;首先可以从网络协议入手&#xff0c;熟悉的网络协议可以有效帮助小伙伴们排查或者说定位大概的问题方面。本文整理了目前最常用的网络通信协议&#xff0c;相信对小伙伴们肯定都有帮助。…

cookie实战案例-自动登录网站

在写爬虫的时候&#xff0c;要伪装成真实用户请求。可能需要大量的IP地址&#xff0c;那么大量的IP地址从哪里来呢&#xff1f;这里就需要用代理IP来解决了&#xff0c;有的网站专门通过提供代理IP池服务作为主要的经营业务&#xff0c;只要注册相关网站开通对应套餐就可以了。…

Java笔试面试题AI答之JDBC(1)

文章目录 1. 什么是JDBC&#xff1f;2. 驱动(Driver)在JDBC中的角色&#xff1f;3. JDBC PreparedStatement比Statement有什么优势&#xff1f;1. 预编译和性能提升2. 参数化查询和安全性3. 更好的可读性和可维护性4. 支持批量操作5. 缓存机制&#xff08;特定数据库环境&#…

2024 高教社杯 数学建模国赛 (A题)深度剖析|“板凳龙” 闹元宵|数学建模完整代码+建模过程全解全析

当大家面临着复杂的数学建模问题时&#xff0c;你是否曾经感到茫然无措&#xff1f;作为2022年美国大学生数学建模比赛的O奖得主&#xff0c;我为大家提供了一套优秀的解题思路&#xff0c;让你轻松应对各种难题&#xff01; CS团队倾注了大量时间和心血&#xff0c;深入挖掘解…

2024 年高教社杯全国大学生数学建模竞赛题目-D 题 反潜航空深弹命中概率问题

应用深水炸弹&#xff08;简称深弹&#xff09;反潜&#xff0c;曾是二战时期反潜的重要手段&#xff0c;而随着现代军事技术 的发展&#xff0c;鱼雷已成为现代反潜作战的主要武器。但是&#xff0c;在海峡或浅海等海底地形较为复杂的 海域&#xff0c;由于价格低、抗干扰能力…

读懂以太坊源码(4)-详细解析节点配置文件geth.toml

要读懂以太坊源码&#xff0c;先熟悉配置文件的每个配置项也是非常有必要的&#xff0c;以下代码是以太坊主网配置文件(geth.toml)的完整内容&#xff0c;后面是对每个配置项的说明&#xff1a; [Eth] NetworkId 0 SyncMode "snap" EthDiscoveryURLs [] SnapDisc…

14 C语言实现平衡二叉树

//LL型失衡 右旋 //RR型失衡 左旋 //RL型失衡 先右旋 再左旋 //LR型失衡 先左旋 再右旋 #include "stdio.h" #include "stdlib.h"typedef int ElemType; typedef struct node {ElemType data;int height;struct node *left;struct node *right; } Node;Nod…

SpringBoot生成ETH和ERON钱包

首先大家需要先引入相关依赖包&#xff0c;这个maven里面是没有的&#xff0c;需要我们自行导入才可以。在项目路径下面创建lib&#xff0c;将所有需要使用的包导入即可。给大家一个包的下载链接&#xff1a;https://download.csdn.net/download/qq_38935605/89715772 因为放在…

scrapy 爬取微博(一)【最新超详细解析】:创建微博爬取工程

本项目属于个人学习记录&#xff0c;爬取的数据会于12小时内销毁&#xff0c;且不可用于商用。 1 初始化环境 首先我们需要有python环境&#xff0c;先安装一下python&#xff0c;然后配置环境变量&#xff0c;这边给出windows的配置&#xff1a; 我这边的安装目录是D:\pyt…

关于SPI通信失败的一种情况(CRC校验不匹配的问题)

问题 该项目中&#xff0c;使用外置的ADC芯片采集电压电流&#xff0c;主控MCU通过SPI与ADC芯片通信。调试时&#xff0c;SPI通信一直失败&#xff0c;与之前成功的项目对比&#xff0c;发现是SPI配置的问题。 void MX_SPI2_Init(void) {/* USER CODE BEGIN SPI2_Init 0 *//*…

WIFI贴项目到底是不是“骗局”呢?由我来揭秘!

各位亲爱的朋友们&#xff0c;大家好&#xff01;我是你们的老朋友鲸天科技千千&#xff0c;一直在这片互联网的热土上耕耘。相信你们对我都不会陌生&#xff0c;因为我常常分享一些互联网上的新奇项目和实用技巧。如果你对我的内容感兴趣&#xff0c;别忘了点个关注哦&#xf…

【案例67】Npart批量启动服务卡顿严重分析过程

问题现象 通过Npart启动NC服务&#xff0c;发现只启动一个&#xff0c;大概3min左右即可启动成功。但是批量启动服务需要几十分钟才可以把服务启动成功&#xff0c;启动卡在获取“wenjian”图标处。 绕过Npart直接写脚本并行启动相关服务&#xff0c;发现也需要30min 问题分析…

数组与贪心算法——605、121、122、561、455、575(5简1中)

605. 种花问题&#xff08;简单&#xff09; 假设有一个很长的花坛&#xff0c;一部分地块种植了花&#xff0c;另一部分却没有。可是&#xff0c;花不能种植在相邻的地块上&#xff0c;它们会争夺水源&#xff0c;两者都会死去。 给你一个整数数组 flowerbed 表示花坛&#xf…

网络传输加密及openssl使用样例(客户端服务器)

文章目录 背景常用加密方式SSLOpenSSL主要功能 库结构 交互流程证书生成生成 RSA 私钥私钥的主要组成部分私钥的格式 创建自签名证书: 签发证书服务器端代码客户端代码常见错误版本问题证书问题证书格式 背景 网络传输中为保证数据安全&#xff0c;通常需要加密 常用加密方式…

1.初识ChatGPT:AI聊天机器人的革命(1/10)

引言 在当今的数字化世界中&#xff0c;人工智能&#xff08;AI&#xff09;正以其独特的方式重塑我们的生活和工作。其中&#xff0c;AI聊天机器人作为人机交互的前沿技术&#xff0c;已经成为企业与客户沟通、提供个性化服务的重要工具。这些机器人通过模拟人类的对话方式&a…

【Unity3D优化】优化内置shader的内存占用

一、性能分析 监控项目线上的崩溃情况&#xff0c;绝大多数崩溃都是因为低端设备&#xff0c;运行时内存不足&#xff0c;在运行过程中申请开辟新的内存时Crash了。因此&#xff0c;不定期继续优化内存占用。 性能分析首先主要靠Unity3d的Memory Profiler监控一些可追踪到的内存…

Java 方法的定义

目录 1.Java的方法类似于其他语言的函数&#xff0c;是一段用来完成特定功能的代码片段。 2.方法包含一个方法头和方法体&#xff0c;下面是一个方法的所有部分&#xff1a; &#xff08;1&#xff09;修饰符&#xff1a;可选。告诉编译器如何调用该方法&#xff0c;定义了该…

基于微信小程序的挂号管理系统-小程序端

微信小程序端系统功能实现 登录功能 系统登录功能中&#xff0c;用户只需在登录界面输入正确的用户名和密码&#xff0c;即可快速进入系统。登录功能还采用了先进的加密技术&#xff0c;保障用户信息的安全性&#xff0c;让用户能够放心使用。 注册功能 系统注册功中&#xf…

Vue项目“npm run serve”总卡住的问题 已解决

Vue项目“npm run serve”总卡住的问题 已解决 概述 如果卡住进度在51% 直接看这篇 https://blog.csdn.net/qq_34419312/article/details/141681307?spm1001.2014.3001.5501 在使用Vue.js进行项目开发时&#xff0c;npm run serve命令是我们常用的启动本地开发服务器的方式…