Node 项目强制使用 pnpm

前言

在团队协作中,不同的开发者可能使用不同的包管理器(npm、yarn、pnpm)。

这会导致以下问题:

  1. 锁文件冲突package-lock.jsonyarn.lockpnpm-lock.yaml 混乱
  2. 依赖安装不一致:不同包管理器的解析策略不同,可能导致依赖树差异
  3. 幽灵依赖:npm/yarn 的扁平化 node_modules 结构容易产生幽灵依赖
  4. 磁盘空间浪费:重复的依赖包占用大量磁盘空间

pnpm 通过硬链接和符号链接的方式,完美解决了这些问题。

本文将介绍几种在 Node 项目中强制使用 pnpm 的方法。

实现

packageManager 字段

package.json 中添加 packageManager 字段,这是 Node.js 官方支持的方式。

以下配置会告诉 Node.js 和 Corepack 应该使用哪个包管理器及其版本:

1
2
3
{
"packageManager": "pnpm@11.0.8"
}

获取当前 pnpm 版本:

1
pnpm --version

也可以指定版本范围:

1
2
3
{
"packageManager": "pnpm@>=8.0.0"
}

从 Node.js 16.13 开始,Corepack 内置在 Node.js 中。

启用 Corepack 后,它会自动使用 packageManager 字段指定的包管理器:

1
2
3
4
5
6
# 启用 Corepack
corepack enable

# 现在运行 npm/yarn 会自动使用 pnpm
npm install # 实际执行 pnpm install
yarn install # 实际执行 pnpm install

preinstall 脚本

package.json 中添加 preinstall 生命周期脚本,在依赖安装前检查包管理器。

基础版本:

1
2
3
4
5
{
"scripts": {
"preinstall": "node -e \"if(!process.env.npm_execpath.includes('pnpm')){console.error('\\x1b[31m','请使用 pnpm 安装依赖');process.exit(1);}\""
}
}

更健壮的版本:

1
2
3
4
5
{
"scripts": {
"preinstall": "node scripts/check-pnpm.js"
}
}

创建 scripts/check-pnpm.js 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const { execSync } = require('child_process');

try {
// 检查是否使用 pnpm
const userAgent = process.env.npm_config_user_agent || '';
const execPath = process.env.npm_execpath || '';

if (!userAgent.includes('pnpm') && !execPath.includes('pnpm')) {
console.error('\x1b[31m', '错误: 请使用 pnpm 安装依赖!');
console.error('\x1b[33m', '安装 pnpm: npm install -g pnpm');
console.error('\x1b[33m', '然后运行: pnpm install');
console.error('\x1b[0m');
process.exit(1);
}
} catch (error) {
// 忽略错误,允许在 CI 环境中跳过检查
if (!process.env.CI) {
console.error('检查包管理器时出错:', error);
}
}

.npmrc 配置

在项目根目录创建 .npmrc 文件,配置严格模式。

基础配置:

1
2
engine-strict=true
package-manager-strict=true

完整配置:

1
2
3
4
5
6
7
8
9
10
11
12
# 强制使用指定的包管理器
package-manager-strict=true

# 强制执行 engines 字段
engine-strict=true

# 禁用自动安装 peer 依赖
auto-install-peers=false

# 设置 pnpm 特定配置
shamefully-hoist=false
strict-peer-dependencies=true

only-allow 工具

only-allow 是一个专门用于限制包管理器的工具。

安装:

1
pnpm add -D only-allow

配置:

1
2
3
4
5
{
"scripts": {
"preinstall": "npx only-allow pnpm"
}
}

验证

测试 preinstall 脚本:

1
2
3
4
5
6
7
8
# 使用 pnpm 安装(应该成功)
pnpm install

# 使用 npm 安装(应该失败)
npm install

# 使用 yarn 安装(应该失败)
yarn install

测试 Corepack:

1
2
3
4
5
# 启用 Corepack
corepack enable

# 现在 npm 命令会自动使用 pnpm
npm install # 实际执行 pnpm install

注意事项

版本锁定

建议锁定 pnpm 的具体版本,而不是使用 latest

1
2
3
{
"packageManager": "pnpm@11.0.8"
}

CI/CD 环境

在 CI/CD 环境中,确保安装了正确版本的 pnpm:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# GitHub Actions 示例
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 11.0.8

- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '20'
cache: 'pnpm'

- name: Install dependencies
run: pnpm install

团队协作

在项目 README 中明确说明需要使用 pnpm:

1
2
3
4
5
6
7
8
9
## 开发环境

本项目使用 pnpm 作为包管理器,请先安装 pnpm:

npm install -g pnpm

然后安装依赖:

pnpm install

IDE 配置

确保 IDE 使用 pnpm 作为默认包管理器:

  • VS Code:在设置中搜索 npm.packageManager,设置为 pnpm
  • WebStorm:在设置中搜索 Package Manager,选择 pnpm

总结

强制使用 pnpm 可以带来以下好处:

  1. 一致性:团队使用相同的包管理器,避免环境差异
  2. 性能:pnpm 的安装速度更快,磁盘占用更少
  3. 安全性:严格的依赖管理,避免幽灵依赖
  4. 可维护性:统一的锁文件,减少合并冲突

推荐使用 packageManager 字段 + preinstall 脚本的组合方案,既符合官方标准,又有足够的约束力。