pnpm 取代 npm 的理由:更快、更省空間、更嚴格
pnpm 取代 npm 的理由:更快、更省空間、更嚴格
如果你每天都在用 npm,你一定遇過這些痛點:node_modules 吃掉幾 GB 硬碟空間、安裝速度慢、幽靈依賴(phantom dependencies)導致的奇怪 bug。pnpm 針對這些問題提出了一套完整的解決方案,而且幾乎完全相容 npm 的使用方式。
npm 的三大問題
問題一:磁碟空間浪費
假設你有 10 個專案,每個都用到 React:
project-a/node_modules/react/ # 一份 React
project-b/node_modules/react/ # 又一份 React(相同版本)
project-c/node_modules/react/ # 再一份 React
...npm 和 Yarn 在每個專案中都複製一份套件,浪費大量磁碟空間。
問題二:安裝速度慢
每次安裝都要從 npm registry 下載,即使你昨天才下載過相同的套件。
問題三:幽靈依賴(Phantom Dependencies)
your-project
└── node_modules/
├── express/
└── lodash/ ← express 的依賴,但你的程式碼直接 require('lodash')npm 將所有依賴提升(hoist)到 node_modules 頂層,你的程式碼可以直接 require 你沒有宣告的套件。這很危險——一旦 express 升級並移除 lodash,你的程式就炸了。
pnpm 的解決方案
全域內容可定址儲存(Content-Addressable Store)
pnpm 在你的系統中維護一個全域儲存(通常在 ~/.pnpm-store):
~/.pnpm-store/
└── v3/
└── files/
├── 00/ (hash 前兩位)
│ └── abc123... (檔案 hash)
├── 01/
└── ...每個套件的每個檔案只儲存一次。相同內容的不同套件版本,共用相同的底層檔案。
硬連結(Hard Links)
project-a/node_modules/react → ~/.pnpm-store/.../react/index.js (硬連結)
project-b/node_modules/react → ~/.pnpm-store/.../react/index.js (同一個檔案!)所有專案的 node_modules 都指向同一個全域儲存,不額外佔用磁碟空間。
符號連結(Symlinks)隔離依賴
project/node_modules/
├── .pnpm/ # 真實位置
│ ├── express@4.18.0/
│ │ └── node_modules/
│ │ ├── express/
│ │ └── lodash/ # express 的依賴在這裡
│ └── react@18.2.0/
│ └── node_modules/
│ └── react/
├── express -> .pnpm/express@4.18.0/node_modules/express
└── react -> .pnpm/react@18.2.0/node_modules/react頂層 node_modules 只有你在 package.json 中宣告的套件,杜絕幽靈依賴。
安裝 pnpm
# macOS / Linux
curl -fsSL https://get.pnpm.io/install.sh | sh
# 或使用 npm 安裝(諷刺)
npm install -g pnpm
# 確認安裝
pnpm --version基本使用
pnpm 的指令與 npm 幾乎相同:
# 安裝所有依賴
pnpm install
# 新增套件
pnpm add react vue
pnpm add -D typescript vite
# 移除套件
pnpm remove lodash
# 執行腳本
pnpm run dev
pnpm dev # 可省略 run
# 全域安裝
pnpm add -g typescript
# 查看過時套件
pnpm outdated
# 更新套件
pnpm updateMonorepo 支援(Workspaces)
pnpm 原生支援 monorepo,效率遠優於 npm workspaces:
# pnpm-workspace.yaml
packages:
- 'packages/*'
- 'apps/*'monorepo/
├── pnpm-workspace.yaml
├── package.json
├── packages/
│ ├── ui/
│ │ └── package.json { "name": "@company/ui" }
│ └── utils/
│ └── package.json { "name": "@company/utils" }
└── apps/
├── web/
│ └── package.json { "dependencies": { "@company/ui": "workspace:*" } }
└── admin/
└── package.json# 在特定 workspace 執行指令
pnpm --filter @company/ui build
pnpm --filter web dev
# 在所有 workspace 執行指令
pnpm -r build
pnpm -r --parallel test
# 安裝套件到特定 workspace
pnpm add react --filter web.npmrc 設定
# .npmrc
# 嚴格模式:禁止存取未宣告的依賴
hoist=false
# 或更嚴格:使用 node-linker
node-linker=isolated
# 自動安裝 peer dependencies
auto-install-peers=true
# 使用 workspace 中的本地套件
prefer-workspace-packages=true速度比較
以安裝一個典型的 Next.js 專案為例:
| 工具 | 冷安裝(無快取) | 暖安裝(有快取) |
|---|---|---|
| npm | ~60s | ~20s |
| Yarn | ~35s | ~10s |
| pnpm | ~15s | ~3s |
磁碟空間比較
10 個各自包含 React + Vue + lodash 的專案:
| 工具 | 總磁碟使用量 |
|---|---|
| npm | ~2.1 GB |
| Yarn | ~1.9 GB |
| pnpm | ~350 MB |
從 npm 遷移
# 刪除現有 node_modules 和 lock file
rm -rf node_modules
rm package-lock.json
# 用 pnpm 安裝
pnpm install
# 將 package-lock.json 換成 pnpm-lock.yaml
# 記得更新 .gitignore# .gitignore
node_modules/
# 保留 pnpm-lock.yaml(加入版本控制)CI/CD 設定
# GitHub Actions
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8
- name: Install dependencies
run: pnpm install --frozen-lockfile總結
pnpm 解決了 npm 多年來的三大問題:磁碟空間浪費、安裝速度慢和幽靈依賴。它的遷移成本極低,指令幾乎與 npm 相同。對於有多個 Node.js 專案的開發者,pnpm 的磁碟空間節省效果立竿見影。強烈建議所有新專案直接採用 pnpm,舊專案也值得遷移。
分享這篇文章