pnpm小记

143 阅读4分钟

npm/yarn

介绍下npm/yarn在依赖管理方面存在的缺陷, 方便后续了解pnpm

早期的npm

使用早期的npm安装依赖, node_modules 文件夹会以递归的形式呈现,严格按照 package.json 结构以及次级依赖的 package.json 结构将依赖安装到它们各自的 node_modules 中,直到次级依赖不再依赖其它模块

就像下面这样

node_modules
└─ A
   ├─ index.js
   ├─ package.json
   └─ node_modules
      └─ B
         ├─ index.js
         └─ package.json

假设项目中的两个依赖项依赖了相同的依赖项

node_modules
├─ A
│  ├─ index.js
│  ├─ package.json
│  └─ node_modules
│      └─ C
│          ├─ index.js
│          └─ package.json
└─ B
   ├─ index.js
   ├─ package.json
   └─ node_modules
       └─ C
           ├─ index.js
           └─ package.json

真实项目中问题会更加严重, 相同的包被重复安装, 占用空间巨大

npm v3 / yarn

npm v3和yarn 采用了新的项目依赖管理方式, 实现了扁平化安装

继续拿上面的例子举例, A@1.0.0和B@1.0.0都依赖了C@1.0.1

node_modules
├─ A@1.0.0
│  ├─ index.js
│  └─ package.json
├─ B@1.0.0
│  ├─ index.js
│  └─ package.json
└─ C@1.0.1
   ├─ index.js
   └─ package.json

包C被提升到了顶层,这里需要注意的是,多个版本的包只能有一个被提升上来,其余版本的包会嵌套安装到各自的依赖当中(类似npm2的结构)。

上面的方式, 虽然解决了嵌套过深和重复安装的问题, 但是也有很多问题

例如:

A@1.0.0 依赖 C@1.0.1, B@1.0.0 依赖 C@1.0.2,那么生成的 node_modules 结构什么样的呢?

node_modules
├── A@1.0.0
├── B@1.0.0
    └── node_modules
        └── C@1.0.2
├── C@1.0.1

// 还是下面这种
node_modules
├── A@1.0.0
    └── node_modules
        └── C@1.0.1
├── B@1.0.0
├── C@1.0.2

其实是都有可能,这就依赖于 A 和 B 在 package.json中的位置。

npm v5 扁平 + lock

为了解决 node_modules 结构的不确定性,于是在 v5 版本中默认会生成 package-lock.json文件 。

我们来看看只安装 swiper 这个库对应的 package-lock.json 文件。

// package.json
{
  "name": "test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "license": "ISC",
  "dependencies": {
    "swiper": "^8.0.7"
  }
}
// package-lock.json
{
  "name": "test",
  "version": "1.0.0",
  "lockfileVersion": 1,
  "requires": true,
  "dependencies": {
    "dom7": {
      "version": "4.0.4",
      "resolved": "https://registry.npmjs.org/dom7/-/dom7-4.0.4.tgz",
      "integrity": "sha512-DSSgBzQ4rJWQp1u6o+3FVwMNnT5bzQbMb+o31TjYYeRi05uAcpF8koxdfzeoe5ElzPmua7W7N28YJhF7iEKqIw==",
      "requires": {
        "ssr-window": "^4.0.0"
      }
    },
    "ssr-window": {
      "version": "4.0.2",
      "resolved": "https://registry.npmjs.org/ssr-window/-/ssr-window-4.0.2.tgz",
      "integrity": "sha512-ISv/Ch+ig7SOtw7G2+qkwfVASzazUnvlDTwypdLoPoySv+6MqlOV10VwPSE6EWkGjhW50lUmghPmpYZXMu/+AQ=="
    },
    "swiper": {
      "version": "8.0.7",
      "resolved": "https://registry.npmjs.org/swiper/-/swiper-8.0.7.tgz",
      "integrity": "sha512-GHjDfxSZdupfU7LrSVOpaNaT7R1D2zxopPGBFz1UOXOtsYvVJLg0k6NvkTAD7qn0ASl5pTti82qoYwvYvIkg4g==",
      "requires": {
        "dom7": "^4.0.4",
        "ssr-window": "^4.0.2"
      }
    }
  }
}

package-lock.json文件记录了每一个包的版本 和其所依赖的其他包版本, 下一次安装的时候可以通过这个文件来安装. 由 package-lock.json 文件和 package.json 文件能确保始终得到一致的 node_modules 目录结构

npm和yarn共有问题

NPM分身

举个例子:

A@1.0.0 --> X@1.0.0和Y@1.0.0 B@1.0.0 --> X@2.0.0和Y@2.0.0 C@1.0.0 --> X@1.0.0和Y@2.0.0 D@1.0.0 --> X@2.0.0和Y@1.0.0

安装后

- X@1.0.0
- Y@1.0.0

- A@1.0.0
- B@1.0.0
    - X@2.0.0
    - Y@2.0.0
- C@1.0.0
    - Y@2.0.0
- D@1.0.0
    - X@2.0.0

X@2.0.0和Y@2.0.0还是会被安装多次, 存在性能问题

幽灵依赖

X@1.0.0 没有在package.json被依赖, 但是我们却可以引用这个包, 引发这个现象的原因一般是因为 node_modules 结构所导致的。

pnpm

"图片自定义高度" height="" width=""

使用 pnpm 安装,pnpm 会将依赖存储在位于 ~/.pnpm-store 目录下。只要你在同一机器下,下次安装依赖的时候 pnpm 会先检查 store 目录,如果有你需要的依赖则会通过一个硬链接丢到到你的项目中去,而不是重新安装依赖。

node modules中会存在一个.pnpm目录, 以平铺的形式储存着所有的包,正常的包都可以在这种命名模式的文件夹中被找到 (.pnpm/+@/node_modules/)

"图片自定义高度" height="" width=""

"图片自定义高度" height="" width=""

Linux 中包括两种链接:

  1. 硬链接(hard link)
  2. 软链接(soft link),软链接又称为符号链接(symbolic link)

inode

每一个文件都有一个唯一的 inode,它包含文件的元信息,在访问文件时,对应的元信息会被 copy 到内存去实现文件的访问。

hard link

硬链接可以理解为是一个相互的指针,创建的 hardlink 指向源文件的 inode,系统并不为它重新分配 inode。

硬链接不管有多少个,都指向的是同一个 inode 节点,这意味着当你修改源文件或者链接文件的时候,都会做同步的修改。

每新建一个 hardlink 会把节点连接数增加,只要节点的链接数非零,文件就一直存在,不管你删除的是源文件还是 hradlink。只要有一个存在,文件就存在

soft link

软链接可以理解为是一个单向指针,是一个独立的文件且拥有独立的 inode,永远指向源文件,这就类比于 Windows 系统的快捷方式。

举个例子

项目依赖 B@1.0.0, B@1.0.0依赖A@1.0.0

node_modules

└── .pnpm

    ├── A@1.0.0

    │   └── node_modules

    │       └── A --> <store>/A

    │           ├── index.js

    │           └── package.json

    └── B@1.0.0

        └── node_modules

            └── B --> <store>/B

                ├── index.js

                └── package.json

B@1.0.0依赖A@1.0.0

node_modules

└── .pnpm

    ├── A@1.0.0

    │   └── node_modules

    │       └── A --> <store>/A

    └── B@1.0.0

        └── node_modules

            ├── B --> <store>/B

            └── A -> ../../A@1.0.0/node_modules/A

最后,pnpm会通过软链链接项目直接的依赖

node_modules

├── B -> ./.pnpm/B@1.0.0/node_modules/B

└── .pnpm

    ├── A@1.0.0

    │   └── node_modules

    │       └── A --> <store>/A

    └── B@1.0.0

        └── node_modules

            ├── B --> <store>/B

            └── A -> ../../bar@1.0.0/node_modules/A