所以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个可能都是找不到的 这时我们的项目再运行就会有问题了。

版本分身 磁盘占用

假设如下情况 我们直接在项目中引入了[email protected] 然后又依赖的其他的第三方库 这些第三方引入了[email protected][email protected]等等 那么只有[email protected]会被平铺在最外层 其他的依然会嵌套在子包中

kotlin 复制代码
node_modules/
├── [email protected]
├── A/
    └── node_modules/
        └── [email protected]
├── B/
    └── node_modules/
        └── [email protected]
├── C/
    └── node_modules/
        └── [email protected]

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

软链接

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

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/
│   │   ├── [email protected]/
│   │   │   └── node_modules/
│   │   │       └── lodash/
│   │   ├── [email protected]/
│   │   │   └── node_modules/
│   │   │       └── moment/
│   ├── lodash -> .pnpm/[email protected]/node_modules/lodash
│   ├── moment -> .pnpm/[email protected]/node_modules/moment
├── packages/
│   ├── project-a/
│   │   ├── node_modules
│   │   │   └── lodash -> ../../../node_modules/.pnpm/[email protected]/node_modules/lodash
│   │   └── index.js
│   └── project-b/
│       ├── node_modules
│       │   └── moment -> ../../../node_modules/.pnpm/[email protected]/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/[email protected]/node_modules/lodash (软链接)
│   ├── project-b/
│   │   ├── package.json
│   │   └── node_modules/
│   │       └── lodash -> ../../.pnpm/[email protected]/node_modules/lodash (软链接)
├── node_modules/
│   ├── .pnpm/
│   │   ├── [email protected]/
│   │   │   └── node_modules/
│   │   │       └── lodash/ 【这个目录中的内容是硬链接 指向全局中的lodash】
│   └── lodash -> .pnpm/[email protected]/node_modules/lodash (软链接)
└── pnpm-workspace.yaml

根 node_modules & 子包的 node_modules 目录

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

.pnpm 目录

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

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

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

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

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

相关推荐
Y.O.U..1 小时前
美团AI面试总结
网络·面试·职场和发展
姑苏洛言3 小时前
30天搭建消防安全培训小程序
前端
左钦杨4 小时前
Nuxt2 vue 给特定的页面 body 设置 background 不影响其他页面
前端·javascript·vue.js
yechaoa4 小时前
【揭秘大厂】技术专项落地全流程
android·前端·后端
MurphyChen4 小时前
🤯 一行代码,优雅的终结 React Context 嵌套地狱!
前端·react.js
逛逛GitHub4 小时前
推荐 10 个受欢迎的 OCR 开源项目
前端·后端·github
ylfhpy4 小时前
Java面试黄金宝典1
java·开发语言·算法·面试·职场和发展
_xaboy4 小时前
开源 FormCreate 表单设计器配置组件的多语言
前端·vue.js·低代码·开源·可视化表单设计器
uglyduckling04124 小时前
小程序构建NPM失败
前端·小程序·npm
草原上唱山歌4 小时前
C/C++都有哪些开源的Web框架?
前端·c++·开源