所以pnpm到底比npm&yarn好在哪?

npm && yarn的问题

幽灵依赖 稳定性风险

假设如下情况 项目里引入了一个第三方包 这个三方包依赖了100个其他包。 因为 npm 和 Yarn 使用的 扁平化 node_modules 结构。 所有的依赖都是平铺在node_modules中的。

所以此时node_modules目录已经有101个依赖包。如果我们使用了一个没有在package.json中声明但是存在于node_modules文件夹下的包 此时通过node的寻址规则 项目是可以正常解析到的。

这个不存在package.json中的包就是一个幽灵依赖

这是非常危险的情况 因为我们的依赖是不稳定的 如果这个第三方依赖包 升级了版本 从原来的100个依赖 变成了 10个依赖 其中90个可能都是找不到的 这时我们的项目再运行就会有问题了。

版本分身 磁盘占用

假设如下情况 我们直接在项目中引入了lodash@4.x.x 然后又依赖的其他的第三方库 这些第三方引入了lodash@3.x.xlodash@2.x.x等等 那么只有lodash@4.x.x会被平铺在最外层 其他的依然会嵌套在子包中

kotlin 复制代码
node_modules/
├── lodash@4.x.x
├── A/
    └── node_modules/
        └── lodash@2.x.x
├── B/
    └── node_modules/
        └── lodash@3.x.x
├── C/
    └── node_modules/
        └── lodash@2.x.x

同一个包的不同版本会被重复下载对此,这只是其中一个项目 如果我们电脑上有几十个几百个项目呢?会对磁盘存储造成浪费。

软链接

  • 软链接: 本质上是一个独立的文件,它包含指向目标文件或目录的路径。这意味着软链接存储的信息是指向目标文件或目录的路径名。
  • 多个软链接:你可以创建多个软链接,这些软链接可以指向相同或者不同的目标文件/目录。
  • 独立存在:软链接文件本身在文件系统中是独立存在的,如果目标文件被移动或删除,软链接会指向一个"断链"的路径,但它本身仍然存在。

symlink1 和 symlink2 都是独立的文件,它们内部包含指向 /path/to/original/file 的路径信息。这两个软链接可以独立存在,但它们都指向同一个目标文件。

硬链接

  • 硬链接: 直接指向文件数据块,硬链接和目标文件共享相同的数据块。
  • 等价关系 :硬链接和它指向的文件完全等价,没有原始文件和链接文件之分,所有硬链接共享同一数据块,对一个硬链接所做的任何修改都会反映到所有其他硬链接上。
  • 删除影响:只有所有指向文件数据块的硬链接都被删除时,文件的数据块才会在文件系统上被释放。

hardlink1 和 hardlink2 共享相同的数据块,所以它们在文件系统中是完全等价的文件。

pnpm

解决幽灵依赖

我们有一个 monorepo 项目,其中包含两个子项目 project-a 和 project-b,它们的依赖关系如下:

  • project-a 依赖 lodash
  • project-b 依赖 moment

在传统的 npm 或 Yarn 环境下,由于 node_modules 目录是扁平化的,当 npm install 或 yarn install 之后,某个子项目(比如 project-a)即使没有显式声明对 moment 的依赖,它也能访问到 moment。

安装后的目录如下:

go 复制代码
my-monorepo/
├── node_modules/
│   ├── lodash/
│   ├── moment/
├── packages/
│   ├── project-a/
│   │   ├── package.json
│   │   └── index.js
│   └── project-b/
│       ├── package.json
│       └── index.js
└── package.json

在 npm 或 Yarn 安装后,执行以下代码在 project-a 是可行的,但具有幽灵依赖的风险:

javascript 复制代码
const lodash = require('lodash');
console.log(lodash.isEmpty({}));

const moment = require('moment');
console.log(moment().format()); // 没有在 package.json 中声明,但可以访问

在传统的 npm 和 Yarn 中,因为 node_modules 目录结构是扁平化处理的,导致 project-a 可以访问到 project-b 的 moment 依赖。

使用 pnpm 安装依赖后的项目结构:

bash 复制代码
my-monorepo/
├── node_modules/
│   ├── .pnpm/
│   │   ├── lodash@4.17.21/
│   │   │   └── node_modules/
│   │   │       └── lodash/
│   │   ├── moment@2.29.1/
│   │   │   └── node_modules/
│   │   │       └── moment/
│   ├── lodash -> .pnpm/lodash@4.17.21/node_modules/lodash
│   ├── moment -> .pnpm/moment@2.29.1/node_modules/moment
├── packages/
│   ├── project-a/
│   │   ├── node_modules
│   │   │   └── lodash -> ../../../node_modules/.pnpm/lodash@4.17.21/node_modules/lodash
│   │   └── index.js
│   └── project-b/
│       ├── node_modules
│       │   └── moment -> ../../../node_modules/.pnpm/moment@2.29.1/node_modules/moment
│       └── index.js
└── package.json

在 pnpm 这种结构下,每个子项目只能访问其 node_modules 下的包,这些包是通过软链接指向 .pnpm 目录中的实际位置。

project-a 只能访问到其明确声明的依赖 lodash,无法访问 moment:

javascript 复制代码
const lodash = require('lodash');
console.log(lodash.isEmpty({}));

try {
  const moment = require('moment');
  console.log(moment().format());
} catch (error) {
  console.error('moment is not a declared dependency in project-a');
}

执行上述代码时将会抛出错误,因为 moment 存在于 project-b 中,而不是 project-a 的有效依赖范围之内。

  • 独立的 node_modules 结构:每个项目的 node_modules 目录不仅仅是一个平铺的目录结构,而是一个通过软链接高效管理的嵌套目录。
  • 明确的依赖管理:安装依赖时,pnpm 将所有的包存储在 .pnpm 目录,并严格根据项目的 package.json 中定义的依赖关系,通过软链接进行访问。这确保了每个包只能访问其显式声明的依赖。

节约磁盘空间

在使用 pnpm 时,pnpm 在用户主目录下维护一个全局缓存目录(例如:~/.local/share/pnpm/store),存储所有包的实际数据

bash 复制代码
my-monorepo/
├── packages/
│   ├── project-a/
│   │   ├── package.json
│   │   └── node_modules/
│   │       └── lodash -> ../../.pnpm/lodash@4.17.21/node_modules/lodash (软链接)
│   ├── project-b/
│   │   ├── package.json
│   │   └── node_modules/
│   │       └── lodash -> ../../.pnpm/lodash@4.17.21/node_modules/lodash (软链接)
├── node_modules/
│   ├── .pnpm/
│   │   ├── lodash@4.17.21/
│   │   │   └── node_modules/
│   │   │       └── lodash/ 【这个目录中的内容是硬链接 指向全局中的lodash】
│   └── lodash -> .pnpm/lodash@4.17.21/node_modules/lodash (软链接)
└── pnpm-workspace.yaml

根 node_modules & 子包的 node_modules 目录

里面的内容实际都是软链接,指向 .pnpm 目录中的实际包文件

.pnpm 目录

.pnpm 目录是 pnpm 用来存储实际包文件的地方。每一个包版本有一个独立的目录,包括它的所有文件。

.pnpm 目录中的包文件使用了硬链接机制,从全局存储中引用实际文件。这些包文件存储在全局缓存中,而 .pnpm 目录中的文件只是硬链接到全局缓存的位置。

其他的部分都是使用的软链接引用的项目地址。

通过这种方式可以 大大减少了磁盘占用 提高了资源利用率

在多项目环境中,pnpm 的机制确保依赖管理更严谨、资源利用更高效,从而为开发者提供了一个更稳定、高效的依赖管理工具。

相关推荐
hackeroink1 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
LCG元3 小时前
【面试问题】JIT 是什么?和 JVM 什么关系?
面试·职场和发展
迷雾漫步者3 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-3 小时前
验证码机制
前端·后端
燃先生._.4 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖5 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235245 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240256 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar6 小时前
纯前端实现更新检测
开发语言·前端·javascript