• 作者:老汪软件技巧
  • 发表时间:2024-11-14 00:02
  • 浏览量:

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.x和lodash@2.x.x等等 那么只有lodash@4.x.x会被平铺在最外层 其他的依然会嵌套在子包中

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,它们的依赖关系如下:

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

安装后的目录如下:

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是可行的,但具有幽灵依赖的风险:

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安装依赖后的项目结构:

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:

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的有效依赖范围之内。

节约磁盘空间

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

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


上一条查看详情 +HarmonyOS 如何实现传输中的数据加密
下一条 查看详情 +没有了