所以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 的机制确保依赖管理更严谨、资源利用更高效,从而为开发者提供了一个更稳定、高效的依赖管理工具。

相关推荐
秋雨凉人心几秒前
uniapp 设置安全区域
前端·javascript·vue.js·uni-app
柳问星32 分钟前
parallel-wait-run, 一个并行运行多个 npm scripts 的小工具
前端·javascript·npm
One_Blanks33 分钟前
网络安全-HTML基础
前端·安全·html
豆包MarsCode1 小时前
使用 Vue 配合豆包MarsCode 实现“小恐龙酷跑“小游戏
开发语言·前端·javascript·vue.js·html
四喜花露水1 小时前
vue2.x elementui 固定顶部、左侧菜单与面包屑,自适应 iframe 页面布局
前端·elementui·vue
看山还是山,看水还是。1 小时前
Redis 命令
前端·数据库·redis·bootstrap
会飞的郁金香1 小时前
第8章 利用CSS制作导航菜单
前端·css
凉风听雪1 小时前
免费HTML模板和CSS样式网站汇总
前端·javascript·css·html
不二人生1 小时前
SQL面试题——飞猪SQL面试 重点用户
数据库·sql·面试
。小二1 小时前
前端js用canvas合成图片并转file对象
开发语言·前端·javascript·canva可画