# npm
2010年随Node.js发布的npm,首次为JavaScript引入了标准的包管理系统。其核心创新是**package.json文件**,通过语义化版本规范定义了依赖声明标准。这一设计解决了手动管理依赖时的版本混乱问题,为模块化开发提供了基础设施。
## 第一阶段:嵌套依赖架构
早期版本采用树状嵌套的依赖结构。每个包将其依赖安装在自身的node_modules目录中,形成深层嵌套。这种设计保证了依赖隔离,但导致了几个严重问题:
- 路径深度问题:依赖树深度可达十几层,触发Windows 260字符路径限制
- 空间极度浪费:相同包在不同层级重复安装,项目体积指数级膨胀
- 安装性能低下:大量递归操作导致I/O效率极低
## 第二阶段:扁平化依赖重构
2015年发布的npm v3引入扁平化依赖安装策略。该方案将**可共享的依赖提升到顶层node_modules**,减少嵌套深度。
主要解决方案为:
- 依赖提升算法:将兼容版本的依赖包提升至顶层
- 冲突处理:版本冲突时,低版本保持嵌套结构
- 确定性牺牲:提升顺序影响最终结构
但同时也引入了新问题:
- 幻影依赖:未声明但被提升的包意外可用
- 依赖不确定性:相同package.json产生不同目录结构
- 模块解析复杂化:Node.js模块解析算法与扁平结构不匹配
# yarn
2016年发布的Yarn针对npm核心痛点提出了系统性解决方案:
- 确定性版本锁定:yarn.lock文件记录精确版本和依赖树
- 并行下载优化:多线程并发下载提升网络利用率
- 离线缓存机制:全局缓存支持离线安装
- 完整性校验:checksum验证确保包完整性
Yarn的成功迫使npm进行重大改进。npm v5版本借鉴了Yarn的核心设计,增加了package-lock.json和缓存优化,形成了技术竞争的良性循环。
# pnpm
2017年发布的pnpm从存储层面重新设计了包管理模型。其核心是**基于内容寻址的全局存储与符号链接架构**。
实现原理:
- 内容可寻址存储:包按内容哈希存储,全局唯一
- 硬链接复用:相同包文件在所有项目间共享
- 符号链接树:通过软链接构建符合Node.js解析规则的依赖树
- 严格依赖隔离:每个包只能访问其声明的依赖
npm/yarn的node_modules目录:
```cmd
项目A: node_modules/lodash@4.17.21
项目B: node_modules/lodash@4.17.21 # 重复存储
项目C: node_modules/lodash@4.17.21 # 重复存储
```
pnpm的node_modules目录:
```cmd
全局存储: .pnpm-store/lodash@4.17.21
项目A: 硬链接 → 全局存储
项目B: 硬链接 → 全局存储
项目C: 硬链接 → 全局存储
```
npm/Yarn采用扁平化结构,包可访问非声明依赖,pnpm采用符号链接嵌套结构,包仅可访问声明依赖。
pnpm的符号链接架构在Monorepo场景中表现优异。跨包依赖通过本地文件系统链接实现,避免了重复安装。Workspaces功能通过优化符号链接策略,确保开发环境与生产环境一致性。
## 简单使用
```cmd
# 通过 npm 安装
npm install -g pnpm
# 检查版本
pnpm --version
# 安装所有依赖(根据 package.json)
pnpm install
# 安装生产依赖
pnpm add
# 安装开发依赖
pnpm add -D
# 移除依赖
pnpm remove
# 查看所有配置
pnpm config list
# 设置存储路径
pnpm config set store-dir ~/.pnpm-store
# 使用淘宝镜像
pnpm config set registry https://registry.npmmirror.com/
```
::: tip
npm确立了基本范式,Yarn解决了确定性问题,pnpm重构了存储模型
:::