字数 890,阅读大约需 5 分钟
现代的很多项目都会使用到pnpm进行包管理,今天了解了一下npm以及pnpm之间的关系,做一份笔记记录。
一、什么是npm?
npm (Node Package Manager) 是 Node.js 默认的包管理工具,也是目前世界上最大的软件注册表。
它充当着开发人员与代码库之间的桥梁,我们可以通过 package.json(清单文件)声明项目所需的依赖,npm 会从远程 Registry 下载代码包,并将其存放在项目的 node_modules 目录下供项目调用。
Info
在早期版本中,它主要负责简单的下载和解压工作,是一个典型的依赖搬运工。
二、为什么需要pnpm进行包管理?
这是由于npm具有包管理的局限性,即每创建一个项目,npm都会在该项目中新建一个代码库,项目与项目之间相互独立,就会存在多个相同的依赖代码包在同一台设备上,造成内存的浪费。
如果你电脑上有 100 个项目,每个项目都依赖 Vue 3,npm 会在磁盘上物理存储 100 份完全一样的 Vue 3 代码。这不仅占用了大量磁盘空间,也导致了大量的重复 I/O 操作,拖慢了安装速度。
并且npm v3 引入了扁平化依赖结构 (Flat Installation)。这种结构虽然解决了路径问题,但引入了严重的幽灵依赖隐患。
三、pnpm横空出世
pnpm (Performant NPM) 通过引入内容寻址存储 (Content-addressable store) 和 硬链接/符号链接 技术,解决了上述问题。
核心机制:全局存储与链接
- 1. 全局统一存储:pnpm 在磁盘上维护一个全局仓库(Store)。所有项目下载的依赖都存放在这里。
- 2. 硬链接 (Hard Links):当你在项目中安装依赖时,pnpm 不会复制文件,而是创建一个指向全局仓库中对应文件的硬链接。
- • 效果:100 个项目使用同一个依赖,磁盘上只有 1 份物理文件。安装速度飞快。
- 3. 非扁平化结构:pnpm 的
node_modules 结构是严格嵌套的,通过符号链接 (Symlinks) 组织,仅将你在 package.json 中声明的依赖暴露在根目录。
npm & pnpm Management mechanism四、浅谈幽灵依赖
什么是幽灵依赖?
幽灵依赖(Phantom Dependencies),指的是项目中使用了某个包,该包存在于 node_modules 中并能被正常运行,但并没有在项目的 package.json 中显式声明。
这就造成了开发者在被动使用该依赖,一旦依赖的某个包不再依赖这个幽灵依赖,在更新后就会出现找不到依赖包的错误。
为什么会出现幽灵依赖?
这归咎于 npm 的扁平化机制 (Hoisting)。
假设你的项目依赖了 Package A,而 Package A 依赖了 Package B。
- • npm 的做法:为了减少层级,npm 会尝试把
Package B 提升(Hoist)到 node_modules 的根目录下,与 Package A 并列。 - • 结果:Node.js 的模块解析规则允许你在代码中直接
import B from 'B',因为 B 就在根目录下。尽管你从未在 package.json 里写过 B。
pnpm是如何解决的?
pnpm默认采用严格模式,在上述例子中,pnpm的node_modules根目录中之后包含PackgeA。Package B会被放在正确的层级中,因此就强迫我们必须显示地依赖Package B,从而根除幽灵依赖的隐患。