NPM、YARN和PNPM包管理器对比

24 年 3 月 5 日 星期二 (已编辑)
1904 字
10 分钟

作为前端或全栈开发者,你可能已经使用过 NPM、YARN 或 PNPM 这些包管理器中的一个或多个。 这些工具帮助我们管理项目依赖,包括各种库、框架和工具包。同时多种多样的包管理工具,在团队协作时产生了不少麻烦,不过让我们从每个 package manager的简要介绍开始,了解每个包管理器的特点,再来看如何在团队中协调包管理器。

NPM

NPM 代表 Node Package Manager。它是 JavaScript 的默认和最流行的包管理器。它由 Isaac Schlueter 于 2009 年创建,用于共享和重用Node.js项目的代码。从那时起,它已发展成为一个拥有超过 200 万个包的巨大存储库,可用于前端和后端开发。

它被 JavaScript 社区广泛使用和支持,拥有大量多样的软件包;具有一组丰富的功能和选项来自定义项目;一个内置的安全审计工具,可以检查依赖项中的漏洞。

YARN

YARN 代表 Yet Another Resource Negotiator。它是 JavaScript 的替代包管理器,由 Facebook、Google、Exponent 和 Tilde 于 2016 年创建。它旨在解决 NPM 的一些问题和限制,例如速度、可靠性和安全性。

在安装或更新软件包时,它比 NPM 更快、更高效;使用扁平的依赖关系结构,避免了包的重复和嵌套;支持从本地缓存离线安装软件包;具有更好的分辨率算法,可确保不同环境中的包版本一致且确定。

PNPM

PNPM 代表 Performant Node Package Manager。它是 Zoltan Kochan 于 2016 年创建的另一个 JavaScript 替代包管理器。它的设计比 NPM 和 YARN 更快、更轻便、更安全。

在安装或更新软件包时,它比 NPM 和 YARN 更快、更轻便;它支持严格的依赖关系隔离,以防止包访问未在其 package.json 文件中声明的模块;有一个内置的安全审计工具,可以检查依赖项中的漏洞。

功能对比

compare.png

这是PNPM官网的表格,让我们一个一个看:

工作空间

工作空间(Workspaces)是一种在单个仓库中管理多个项目或包的开发环境的解决方案。它允许我们在一个代码仓库(Monorepo)中管理多个相互关联的项目,同时共享配置和依赖。

主要特点:

  1. 代码共享,包之间可以共享代码,避免代码重复
  2. 共享依赖安装,集中管理依赖版本,减少重复安装,节省磁盘空间
  3. 统一的开发环境,统一的构建流程,简化版本控制

NPM 工作空间

从 v7 版本开始支持,配置方式:在根目录 package.json 中定义:

json
{
  "workspaces": ["packages/*"]
}
shell
# 安装依赖
npm install -w lodash
# 运行脚本
npm run test --workspace=package-a
# 批量操作
npm run test --workspaces

YARN 工作空间

从 v1 版本就开始支持,配置方式:在根目录 package.json 中定义:

json
{
  "private": true,
  "workspaces": ["packages/*"]
}
shell
# 安装依赖
yarn workspace package-a add lodash
# 运行脚本
yarn workspace package-a test
# 批量操作
yarn workspaces run test

PNPM 工作空间

从 v2 版本开始支持,配置方式:使用 pnpm-workspace.yaml 文件:

yaml
packages:
  - 'packages/*'
shell
# 安装依赖
pnpm -F package-a add lodash
# 运行脚本
pnpm -F package-a test
# 批量操作
pnpm -r test

Node Modules 隔离机制 与 依赖提升(Hoisting)

依赖隔离是指每个项目或包都有其独立的依赖环境,避免不同项目或包之间的依赖互相影响。这种机制对于确保项目的可靠性和一致性至关重要。

而依赖提升会将子依赖包从嵌套的 node_modules 目录提升到顶层 node_modules 目录,以减少依赖的重复安装和嵌套层级。

  • 减少磁盘空间使用
  • 减少依赖重复
  • 缩短模块查找路径

NPM/Yarn Classic (扁平化结构)

plaintext
project/
├── node_modules/
│   ├── express/
│   ├── lodash/
│   └── react/
├── package.json
└── package-lock.json

特点:

  • 所有依赖平铺在根 node_modules 目录
  • 可能导致依赖提升
  • 存在"幽灵依赖"问题(可以使用未声明的依赖)

Berry (Yarn v2+)

plaintext
.yarn/
├── cache/
└── unplugged/
node_modules/ #可选

特点:

  • 默认使用 Plug'n'Play (PnP)
  • 不依赖 node_modules
  • 更严格的依赖管理

PNPM (严格隔离)

plaintext
project/
├── node_modules/
│   ├── .pnpm/
│   │   ├── express@4.17.1/
│   │   ├── lodash@4.17.21/
│   │   └── react@17.0.2/
│   ├── express -> .pnpm/express@4.17.1/
│   ├── lodash -> .pnpm/lodash@4.17.21/
│   └── react -> .pnpm/react@17.0.2/
├── package.json
└── pnpm-lock.yaml

特点:

  • 使用硬链接和符号链接
  • 严格的依赖隔离
  • 防止幽灵依赖
  • 节省磁盘空间
PNPM 的依赖隔离实现原理

除硬链接、符号链接等机制以外,还有内容寻址存储:

plaintext
.pnpm-store/
└── v3/
    ├── files/
    │   └── [hash1]
    │   └── [hash2]
    └── metadata/
  • 所有包都存储在全局 store 中
  • 使用内容哈希确保唯一性
  • 多个项目可以共享同一个 store

特性对比表

特性NPMYarn ClassicYarn BerryPNPM
依赖提升否(PnP)
幽灵依赖存在存在不存在不存在
磁盘空间较多较多较少最少
安装速度中等最快
依赖隔离最强

PNPM 副作用缓存(Side Effects Cache)

这是pnpm独有的功能之一,副作用指在包安装过程中,除了复制文件之外的额外操作:

js
// 常见的副作用类型
1. 原生模块编译
2. 安装脚本执行
3. 文件生成或修改
4. 环境配置修改
json
// package.json
{
  "name": "some-package",
  "scripts": {
    "install": "node-gyp rebuild", // 原生模块编译
    "postinstall": "node scripts/build.js", // 安装后脚本
    "prepare": "husky install" // 准备阶段脚本
  },
  // 声明有副作用的文件
  "sideEffects": ["*.css", "dist/*", "compiled/*"]
}

副作用缓存结构

js
.pnpm-store/
├── v3/
│   ├── files/           # 普通文件缓存
│   │   └── [hash]/     # 内容寻址存储
│   └── side-effects/    # 副作用缓存
│       └── [hash]/     # 副作用产物缓存
└── metadata.json       # 缓存元数据

.npmrc配置项

shell
# 启用副作用缓存
side-effects-cache=true

# 只读模式(CI环境推荐)
side-effects-cache-readonly=true

# 缓存目录配置
store-dir=.pnpm-store

# 最大缓存大小
cache-max-size=10GB

相关构建命令

shell

# 并行构建 同时处理多个包的安装 适用于 monorepo 项目
pnpm install --parallel

# 使用快速模式 跳过依赖解析 直接使用 lockfile
pnpm install --fast

# 跳过可选依赖 只安装必需的依赖
pnpm install --no-optional

# 强制重新构建
pnpm rebuild

# 清理缓存
pnpm store prune

CI/CD环境

yaml
# .gitlab-ci.yml 示例
cache:
  paths:
    - .pnpm-store/

install:
  script:
    - pnpm config set side-effects-cache true
    - pnpm config set side-effects-cache-readonly true
    - pnpm install --frozen-lockfile

开发环境

shell
# 推荐配置
pnpm config set side-effects-cache true
pnpm config set store-dir .pnpm-store

# 定期维护 清理缓存
pnpm store prune

Corepack 与团队包管理工具协调

Corepack 是 Node.js 官方提供的包管理器管理工具,用于确保团队使用一致的包管理器版本。

shell
# Node.js 16.13+ 内置了 Corepack
# 启用 Corepack
corepack enable

# 查看版本
corepack --version

# corepack会自动下载pnpm
pnpm install

json
// package.json
{
  "name": "my-project",
  "packageManager": "pnpm@7.5.0",
  "engines": {
    "node": ">=16.13.0",
    "pnpm": ">=7.0.0"
  }
}

还可以加上Git Hooks检测和限制团队成员使用的包管理工具

shell
# 安装 husky
pnpm add -D husky

# 创建 pre-commit hook
npx husky add .husky/pre-commit "node ./scripts/verify-package-manager.js"
js
// scripts/verify-package-manager.js
const validatePackageManager = () => {
  const userAgent = process.env.npm_config_user_agent;
  const requiredManager = 'pnpm@7.5.0';

  if (!userAgent?.startsWith('pnpm')) {
    console.error('请使用 pnpm 作为包管理器!');
    process.exit(1);
  }
};

validatePackageManager();

最后推荐volta,也可以帮助团队使用相同的环境,包括Node版本和包管理器版本等。

综合体验下来,PNPM确实稳定快速轻量,yarn2 Pnp结构实际使用时,VSCode经常抱错,遇到问题多半都是Pnp结构导致的,开发体验糟糕,所以后面都使用PNPM了。

文章标题:NPM、YARN和PNPM包管理器对比

文章作者:shirtiny

文章链接:https://kizamu.anror.com/posts/package-managers[复制]

最后修改时间:


商业转载请联系站长获得授权,非商业转载请注明本文出处及文章链接,您可以自由地在任何媒体以任何形式复制和分发作品,也可以修改和创作,但是分发衍生作品时必须采用相同的许可协议。
本文采用CC BY-NC-SA 4.0进行许可。