不用 Corepack 如何安裝與管理 pnpm
昨天早上在推特(X)上看到 @pnpmjs 的貼文說:
Please don't use corepack to install pnpm.
— pnpm (@pnpmjs) August 13, 2026
我看到時其實有點驚訝,Corepack 應該是蠻理想的安裝方式。
那麼如果不使用 Corepack,現在應該怎麼安裝與管理 pnpm?
Corepack 原本幫我們做了什麼?
先簡單說明,當初選擇 Corepack 作為安裝 pnpm 的方式的主要原因是:
不同專案可能使用不同版本的 pnpm,透過 Homebrew 安裝 pnpm 時,在不同版本之間切換並不方便。
而 Corepack 剛好解決了這個問題,詳細可以參考我之前寫的 用 Corepack 管理 pnpm 版本。
Corepack 最大的價值在於:
- 開發者不需要手動安裝指定版本的 pnpm。
- 不同專案可以指定不同 pnpm 版本。
- 團隊只要在
package.json中定義packageManager,團隊成員就能使用相同版本。
為什麼現在不再推薦 Corepack?
我在 用 Corepack 管理 pnpm 版本 也提過,社群討論中一個比較核心的問題,是 Node.js 本身正在把 Corepack 移出去。
Corepack 從 Node.js 14.19 / 16.9 開始提供,但一直都維持 Experimental 狀態。
而從 Node.js 25 開始,Corepack 已經不再隨 Node.js 一起發行,如果需要的話就得自己安裝。
等於想安裝 pnpm,我還得手動安裝 Corepack 才行,使用成本變高了。
另外,pnpm 的發布與啟動方式也正在改變。
本質上 Corepack 是一層位於使用者與套件管理工具之間的 proxy。
Corepack 安裝在 pnpm 位置上的其實是 JavaScript shim,因此每次呼叫 pnpm,都會先啟動 Node.js 執行 Corepack,之後才啟動真正的 pnpm。
pnpm 命令(實際指向 Corepack JavaScript shim)
↓
Node.js runtime
↓
Corepack 解析並準備專案指定版本
↓
真正的 pnpm CLIshim:一個放在真正程式前面的輕量代理入口。它可以保留原本的命令名稱,並在把控制權交給真正程式以前,先完成版本判斷等工作。
因此,每次執行 pnpm 都要額外付出啟動 Node.js 與執行 Corepack 的成本。這是啟動路徑的額外成本,不代表 Corepack 會再安裝一次專案依賴。
另一方面,透過 standalone script 安裝的 pnpm 11 已經是獨立執行檔(self-contained executable),啟動 pnpm 本身不需要系統預先安裝 Node.js;目前仍在 RC 階段的 pnpm 12,則更進一步改寫成 Rust 原生二進位執行檔(Rust native binary),並提供靜態連結的版本。
如果我們直接安裝 standalone pnpm,啟動路徑會變成:
pnpm 命令
↓
Shell 從 PATH 找到 self-contained pnpm executable
↓
直接啟動 pnpmpnpm 12 的 npm package 也不再包含 Corepack 所期待的 bin/pnpm.mjs,因此目前的 Corepack 根本無法安裝 pnpm 12。
也就是說,改變的不只是「pnpm 12 改用 Rust」,而是 pnpm 的安裝、啟動與版本管理正逐步不再依賴 Corepack。
沒有 Corepack 怎麼管理 pnpm 版本?
說到這裡,可以理解 Corepack 未來將不再是最佳安裝方案,但這仍沒有解決當初選擇 Corepack 的核心原因——pnpm 版本管理。
那現在該怎麼做呢?答案很簡單。
pnpm 本身就支援版本切換!
如果 package.json 有:
{
"packageManager": "[email protected]"
}實際上 pnpm 從 9.7.0 開始支援自行讀取 packageManager 並切換版本,但當時需要手動啟用。
從 pnpm 10 開始,這項功能預設開啟,因此只要系統上已經有 pnpm,就不再需要依靠 Corepack 管理專案使用的 pnpm 版本。
pnpm 在第一次執行時,就可以切換到專案指定的版本。此時一開始被 Shell 啟動的是已經安裝在系統上的 pnpm,接著才由它取得並把控制權交給專案指定版本。
換句話說,原本:
Corepack shim
↓
讀取 packageManager
↓
準備並啟動指定版本的 pnpm現在會變成:
系統上已安裝的 pnpm
↓
讀取 packageManager
↓
必要時取得指定版本
↓
啟動專案指定版本的 pnpm CLI原本的 packageManager 可以繼續使用
{
"packageManager": "[email protected]"
}只是版本管理的責任終於從 Corepack 回歸到 pnpm 自己身上,我想這是最理想的狀態。
CI 該怎麼處理?
如果是 GitHub Actions,現在也不需要特別啟用 Corepack。
pnpm 官方目前提供新的 pnpm/setup:
- uses: actions/checkout@v6
- name: Install pnpm and Node.js
uses: pnpm/setup@v2
with:
runtime: node@24
cache: true它會負責:
讀取 packageManager,決定 pnpm 版本
↓
安裝 pnpm
↓
安裝指定的 Node.js runtime
↓
還原 pnpm store cache
↓
執行 pnpm install
↓
工作結束後儲存 pnpm store cache這裡的 cache 指的是 pnpm store,不等於直接快取整份 node_modules;而 pnpm install 才是解析 lockfile 並建立專案依賴結構的步驟。
而且如果 package.json 已經有:
{
"packageManager": "[email protected]"
}pnpm/setup 可以直接從這裡取得 pnpm 版本,不需要在 workflow 再寫一次。
這點我覺得超級方便,不像以前還需要在 CI 裡面寫:
version: 11.10.0專案的 pnpm 版本能直接透過單一來源維護。
pnpm 也能管理 Node.js 版本
另外分享一個我覺得很棒的功能,透過上述的 CI 範例可以發現,pnpm 也能用來管理 Node.js Runtime。
過去我們通常會先安裝 Node.js,再使用隨 Node.js 提供的 npm 或 Corepack 取得 pnpm:
安裝 Node.js
↓
使用 npm 安裝 pnpm,或啟用 Corepack shim
↓
取得可執行的 pnpm 命令但 pnpm 本身其實提供了 pnpm runtime,可以直接安裝與切換 Node.js 版本。例如:
pnpm runtime set node 24 -g這個概念跟 nvm、fnm 很接近,pnpm 會下載 Node.js 24,並將它設定成目前使用的全域 Node.js 版本。
流程會變成像是:
一台沒有 Node.js 的電腦
↓
安裝 standalone pnpm
↓
執行 pnpm runtime set node 24 -g
↓
下載 Node.js 24 並建立全域 runtime 連結如果要切換成 Node.js 22,一樣可以使用:
pnpm runtime set node 22 -g當然,如果原本已經習慣使用 fnm、nvm 或 mise 管理 Node.js 的話,也不需要特別改成 pnpm runtime。
原因是:
- standalone pnpm 不需要預先安裝 Node.js,因此最適合從一個完全沒有 Node.js 的環境開始建立 runtime;其他安裝方式仍可能依賴系統既有的 Node.js。
- 無法自動根據專案設定變更 Node.js 版本。
- 管理指令仍不齊全。
這三點我相信未來都有機會被解決,只是現階段來說我認為影響都不小,還是先維持自己的開發習慣就好。
不過這項功能也顯現出 pnpm 未來除了是 Node.js 套件管理工具外,也可能會連同 JavaScript 環境本身都一起管理。
現在我推薦的 pnpm 用法
最後結合一下以上內容,來說說我現在如何使用 pnpm。
首先,終於可以用 Homebrew 安裝 pnpm 了(強迫症)。
電腦上必須先安裝過 Node.js。
brew install pnpm安裝完成後,重新載入或重新開啟 Terminal,再確認:
pnpm --version
# 11.21.0這時候找一個有定義 packageManager 的專案,再執行 pnpm -v,就會發現 pnpm 自動切換到專案定義的版本了!
指定安裝版本
當 pnpm 已經安裝完成,且目前不在一個 pin 住 pnpm 版本的專案中,則可以用 self-update 更新系統上的 pnpm:
pnpm self-update而新專案第一次指定版本時,可以直接在 package.json 加入 packageManager:
{
"packageManager": "[email protected]"
}這個欄位應該和 lockfile 一起提交,讓本機與 CI 都從同一個地方取得 pnpm 版本。
想更新目前專案指定的 pnpm 版本,一樣可以使用:
pnpm self-update要注意的是,如果目前位於一個已經透過 packageManager pin 住 pnpm 版本的專案中,pnpm self-update 更新的是 package.json 裡的版本設定,不會修改全域 pnpm。
下次執行 pnpm 命令時,pnpm 才會依照新的設定下載並切換版本。
結語
對軟體開發者來說,同時使用一堆 runtime manager 與 package manager 雖然已經見怪不怪,但我仍很期待它們有一天能被統整起來,進一步提升開發體驗。
之前我在文章最後也寫到:
總覺得 pnpm 這種工具更應該直接獨立出來安裝和管理,而不是依賴在 Node.js 中。
現在看來,pnpm 的確正在往這個方向走。
而且從這次可以發現,pnpm 正逐漸降低啟動自身時對 Node.js 與 Corepack 的依賴,同時反過來具備管理 Node.js Runtime 的能力。
對於目前許多專案都依賴 pnpm 來安裝與建立 monorepo 的我來說,能看到 pnpm 越來越方便是一件好事。
當然讓我最開心的,肯定是終於能擺脫 Corepack 這個不確定因素,終於能回歸用 Homebrew 管理 pnpm 🤣
參考資料
討論區
歡迎在下方分享你的想法,或是給我一個讚!