PNPM的原理实现

1.介绍

pnpm 是一个快速、节省磁盘空间的 JavaScript 包管理器,通过硬链接 共享依赖和软链接组织 node_modules 结构,比 npm 和 yarn 更高效。核心思想:是多个项目共用一个相同的依赖。

2.看看效果

我们通过创建一个vue项目看看依赖包是如何被创建和引用的。

sh 复制代码
# 使用pnpm创建Vue项目 
pnpm create vue@latest my-vue-project 
# 进入项目目录
cd my-vue-project
# 安装依赖
pnpm install 

输出的node_modules下的内容

展开的 node_modules/.pnpm

我们可以看到 node_modules/.pnpm/vue@3.5.22

我们主要关注node_modules 下的 .pnpm/vue@3.5.22vue

  • .pnpm/vue@3.5.22 (硬链接 连接到总仓库)
  • vue 软链接 (链接到上面硬链接,解决导入时候的路径问题)

pnpm 依赖目录结构示意图

perl 复制代码
# 全局存储区(store)
~/.pnpm-store/v3/
└── vue@3.5.22/             # 存放实际包内容(只保存一份)
    ├── package.json
    ├── index.js
    └── ...

# 项目 A
projectA/
└── node_modules/
    ├── .pnpm/                  # pnpm 的内部目录(存放硬链接)
    │   └── vue@3.5.22/     
    │       └── node_modules/
    │           └── vue/     # 硬链接 → ~/.pnpm-store/v3/vue@3.5.22/*
    │               ├── package.json
    │               ├── index.js
    │               └── ...
    │
    └── vue  --->  .pnpm/vue@3.5.22/node_modules/vue
                      (符号链接 symlink)

# 项目 B
projectB/
└── node_modules/
    ├── .pnpm/                  # 这里同样是硬链接
    │   └── vue@3.5.22/
    │       └── node_modules/
    │           └── vue/     # 硬链接 → ~/.pnpm-store/v3/vue@3.5.22/*
    │
    └── vue  --->  .pnpm/vue@3.5.22/node_modules/vue

1. pnpm 的依赖存储机制

pnpm 在本地会维护一个全局的包存储目录(store),默认路径大概是:

javascript 复制代码
~/.pnpm-store/v3
  • store 里保存了所有版本的包内容 (比如 vuevite),是按内容寻址的(基于内容哈希),避免重复下载和存储。
  • 当不同项目依赖同一个版本的包时,pnpm 不会再复制,而是通过 硬链接 来复用。

2. 硬链接 (hard link)

硬链接是 文件系统级别 的"多入口"。同一个物理文件可以有多个路径引用:

  • 当你在项目里运行 pnpm install,pnpm 会把包的实际文件 硬链接 到项目的 node_modules/.pnpm 目录。
  • 这样每个项目都像有一份完整的包副本,但实际上磁盘上只有一份数据(inode 相同,引用计数增加)。
  • 优点:不同项目、不同 node_modules 共享相同的包内容,不会浪费磁盘空间。

例子(假设 lodash 已经存在于 store):

bash 复制代码
projectA/node_modules/.pnpm/vue@3.5.22/node_modules/vue/   # 硬链接
projectB/node_modules/.pnpm/vue@3.5.22/node_modules/vue/   # 硬链接

这两个路径下的文件,实际上都指向 ~/.pnpm-store/v3/.../vue@3.5.22 里的真实内容。


3. 符号链接 (symlink)

硬链接解决了内容复用问题,但 Node.js 的 require()/import 是依赖于 目录结构解析 的,必须能在 node_modules 里按照规范找到依赖。

所以 pnpm 还需要用 符号链接 来构建可解析的依赖树。

比如一个包 foo 依赖 bar

bash 复制代码
project/node_modules/foo       # symlink → node_modules/.pnpm/foo@1.0.0/node_modules/foo
project/node_modules/bar       # symlink → node_modules/.pnpm/bar@2.0.0/node_modules/bar

最终形成的目录结构:

  • node_modules/.pnpm/... 目录下:存放硬链接的真实文件。
  • node_modules/ 根目录:存放符号链接,指向 .pnpm 里的硬链接目录。

这样:

  1. 硬链接保证了磁盘利用率和速度。
  2. 符号链接保证了 require('foo') 能正常解析。

4. 优势总结

  • 节省磁盘空间:相同版本的依赖只存储一次。
  • 快速安装:安装时只是做链接,不需要复制大文件。
  • 隔离性 :不同项目依然有自己的 node_modules 目录,不会像 npm link 那样污染全局。

3.linux中的 硬链接 和 软链接

核心概念

索引节点(inode)

在理解链接之前,需要先了解 ​inode​:

  • 每个文件都有一个唯一的 inode 编号
  • inode 存储文件的元数据(权限、所有者、大小、时间戳等)
  • inode 指向文件数据在磁盘上的实际存储位置
bash 复制代码
# 查看文件的 inode 号
ls -i filename.txt
# 输出: 123456 filename.txt

硬链接(Hard Link)

硬链接是多个文件名指向同一个 inode 的机制。所有硬链接都是平等的,没有原始和副本之分。

创建硬链接

bash 复制代码
# 创建硬链接
ln original.txt hardlink.txt

# 查看链接情况
ls -li
# 输出示例:
# 123456 -rw-r--r-- 2 user group 1024 Jan 1 12:00 original.txt
# 123456 -rw-r--r-- 2 user group 1024 Jan 1 12:00 hardlink.txt
# ↑ 相同的inode ↑链接计数

硬链接的特点

1. ​相同的 inode 编号
bash 复制代码
# 查看 inode
ls -i original.txt hardlink.txt
# 输出: 123456 original.txt  123456 hardlink.txt
2. ​共享链接计数
bash 复制代码
# 查看链接计数
ls -l
# 输出: -rw-r--r-- 2 user group 1024 Jan 1 12:00 original.txt
#       -rw-r--r-- 2 user group 1024 Jan 1 12:00 hardlink.txt
# 这里的 "2" 表示有2个硬链接指向这个inode
3. ​删除行为
bash 复制代码
# 删除一个硬链接不会影响其他链接
rm original.txt
ls -l hardlink.txt
# hardlink.txt 仍然存在且内容完整

# 只有当链接计数为0时,文件数据才会被真正删除
4. ​限制
bash 复制代码
# 不能创建目录的硬链接
ln dir1 dir2_hardlink  # 错误: 不允许的操作

# 不能跨文件系统创建硬链接
ln /mnt/disk1/file.txt /mnt/disk2/hardlink.txt  # 错误: 无效的跨设备链接

什么是软链接

软链接是一个特殊的文件,包含指向另一个文件或目录的路径。类似于 Windows 的快捷方式。

创建软链接

bash 复制代码
# 创建软链接
ln -s original.txt softlink.txt

# 查看链接情况
ls -li
# 输出示例:
# 123456 -rw-r--r-- 1 user group 1024 Jan 1 12:00 original.txt
# 789012 lrwxrwxrwx 1 user group   12 Jan 1 12:01 softlink.txt -> original.txt
# ↑ 不同的inode   ↑链接类型为'l'

软链接的特点

1. ​不同的 inode 编号
bash 复制代码
# 查看 inode
ls -i original.txt softlink.txt
# 输出: 123456 original.txt  789012 softlink.txt
2. ​文件类型和权限
bash 复制代码
ls -l softlink.txt
# 输出: lrwxrwxrwx 1 user group 12 Jan 1 12:01 softlink.txt -> original.txt
# ↑ 'l' 表示链接文件,权限通常是 777(但实际权限由目标文件决定)
3. ​删除行为
bash 复制代码
# 删除原始文件后,软链接成为"悬空链接"
rm original.txt
ls -l softlink.txt
# 输出: lrwxrwxrwx 1 user group 12 Jan 1 12:01 softlink.txt -> original.txt
# 但访问它会报错: No such file or directory

# 删除软链接不影响原始文件
rm softlink.txt
ls -l original.txt  # 原始文件仍然存在
4. ​灵活性
bash 复制代码
# 可以链接到目录
ln -s /path/to/dir symlink_to_dir

# 可以跨文件系统链接
ln -s /mnt/disk1/file.txt /mnt/disk2/symlink.txt  # 允许

# 可以创建相对路径链接
ln -s ../other-dir/file.txt relative_link.txt
相关推荐
jason_yang20 小时前
Workspace搭建Vue3+组件分离的Monorepo项目
git·npm·前端工程化
三十_1 天前
私有 npm 仓库实践:Verdaccio 保姆级搭建教程与最佳实践
前端·npm
web打印社区1 天前
最简单的 Web 打印方案:用 5 分钟上手 web-print-pdf(npm 包)
前端·pdf·npm
寒山李白2 天前
npm镜像源配置指南
前端·npm·node.js
沙白猿2 天前
npm启动项目报错“无法加载文件……”
前端·npm·node.js
Bruce-li__2 天前
前端开发利器:nvm、npm与pnpm全面解析与TypeScript/JavaScript选择指南
javascript·typescript·npm
Dontla2 天前
npx命令介绍(Node Package Execute)(允许开发者直接执行来自npm注册表的包中的二进制文件,而无需全局安装)临时使用
前端·npm·node.js
张人玉2 天前
npm和pnpm命令大全
前端·npm·node.js
杨晓风-linda2 天前
npm玩转技巧
前端·npm·node.js