會開始使用 pnpm,其實沒有什麼特別複雜的原因。就是因為我開始有使用 monorepo 的需求,然後剛好 Turborepo 官網的安裝範例第一個選擇就是 pnpm,所以我就開始嘗試了。
這篇文章會分享我從 npm 轉換到 pnpm 的完整歷程,包括適應過程、實際使用體驗、與其他工具的比較,以及一些踩坑經驗。如果你也在考慮是否要切換套件管理工具,希望這篇真實的經驗分享能給你一些參考。
在深入討論 pnpm 之前,我想先聊聊為什麼會需要 monorepo。這其實是很多開發者都會遇到的問題。
當專案開始成長,就會發現自己需要管理多個相關的專案:可能是多個前端應用、共享的 UI 元件庫、共用的工具函式庫、型別等等,或是前後端分離架構下的多個服務。如果這些專案分散在不同的 repository,會遇到很多問題:
Monorepo 解決了這些問題,讓你可以把相關的專案放在同一個 repository 中,共享依賴、共用程式碼,並且可以統一管理建置和部署流程。
但 monorepo 也帶來了新的挑戰:我們需要一個能夠有效管理多個套件、處理 workspace 依賴關係的套件管理工具。這就是為什麼我開始尋找 npm 以外的選擇。
切換到 pnpm 後,一開始真的不太適應,主要是太容易漏打 p 了。
因為 npm install、npm run dev、npm run build 已是我的肌肉記憶。
但說真的,除此之外全是優點 😂
開始使用 monorepo 時,會發現自己需要頻繁地重新安裝 node_modules。特別是在以下幾種情況:
node_modules 重新安裝在這些場景下,pnpm 的速度優勢就非常明顯了。
根據 pnpm 官方的效能測試數據,在冷安裝(cold install)的情況下,pnpm 比 npm 快約 65%。這個數字可能看起來不是特別驚人,但當你實際使用時,感受會非常明顯。
以我自己的經驗來說,一個中等規模的 monorepo 專案,使用 npm 安裝可能需要 2-3 分鐘,但使用 pnpm 通常只需要 30-60 秒。這個差異在 CI/CD 流程中尤其重要,因為每次部署都會執行安裝步驟,累積下來可以節省大量的時間和成本。
更讓我印象深刻的是,當你需要在不同分支之間切換時,pnpm 的優勢更加明顯。因為 pnpm 使用內容可尋址儲存(content-addressable storage)和硬連結(hard links),如果兩個分支使用的依賴版本相同,pnpm 可以幾乎瞬間完成安裝,因為它只需要建立連結,而不需要重新下載套件。
除了效能之外,pnpm 對 monorepo 的支援是我選擇它的主要原因。
pnpm 的 workspace 功能非常直觀。基本上你只需要:
pnpm-workspace.yaml 檔案name 欄位正確設定就這樣,pnpm 就能自動處理 workspace 之間的依賴關係。
# pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"當你在 workspace 中的某個 package 需要使用另一個 package 時,只需要在 package.json 中使用 workspace: 協議:
{
"dependencies": {
"@my-org/shared-utils": "workspace:*"
}
}pnpm 會自動處理版本對應,當你發布套件時,它會自動將 workspace:* 替換為實際的版本號。這個設計非常優雅,不需要手動維護版本號,也不會出現版本不一致的問題。
當然,對於沒寫過 Node.js package 的人來說,還是需要經過一段學習 bundle package 的過程。但這不是 pnpm 的問題,而是 monorepo 本身就需要這些知識。pnpm 只是讓這個過程變得更加順暢。
在使用 pnpm 的過程中,我也上網研究了一下 2025、2026 年大家都怎麼選擇套件管理工具。簡單來說,如果有需要 monorepo,那一般會選擇 pnpm 或 Yarn,因為這兩者有 workspace 的概念,對 monorepo 非常友好。
另外,為什麼我沒有去試用 Yarn 呢?
一來是我在網路上找比較時,有看到 pnpm 比 Yarn 快的數據。根據 2026 年的套件管理工具比較,pnpm 在冷安裝和快取安裝的場景下都表現得比 Yarn 更好。雖然差距可能不是特別大就是了。
另外還有這個 👇
但更主要的原因是:我懶。pnpm 用著沒什麼問題,就懶得換了。
反正工具是為了解決問題,而不是為了追求最新潮的技術。如果一個工具已經能滿足需求,而且使用體驗良好,那就沒有必要為了「試試看」而切換到另一個工具。我絕對不是在為我的懶找藉口 🥹
不過,我也聽說 Yarn 3(Berry)有非常優秀的插件架構和 PnP(Plug'n'Play)功能,可以完全消除 node_modules 目錄,節省更多磁碟空間。但對我來說,pnpm 已經足夠好了,而且它的學習曲線更平緩,社群支援也很完善。
想要了解更多各種 Package Manager的效能測試,可以參考 pnpm 官方的 Benchmarks of JavaScript Package Managers。
如果你也在考慮從 npm 切換到 pnpm,好消息是:遷移過程相當簡單。
基本上只需要兩個步驟:
package-lock.json(如果有的話)pnpm installpnpm 會自動讀取 package.json 並安裝所有依賴,同時產生 pnpm-lock.yaml 檔案。
不過,在這個過程中,pnpm 可能會出現一些警告和錯誤。但好消息是,這些錯誤訊息通常都很詳細,而且很容易解決。根據我的經驗,最常見的問題包括:
這些問題通常都可以透過調整 package.json 或更新套件版本來解決。如果真的遇到困難,直接把錯誤訊息貼給 AI 也能很輕鬆地除錯。
在遷移過程中,你可能會遇到一個有趣的問題:pnpm install 成功了,但是執行 pnpm dev 或 pnpm build 時卻出現錯誤。
這多半是因為專案存在幽靈依賴(phantom dependencies)的問題。
這看起來像是個問題,但實際上這是個功能,不是 bug。幽靈依賴會導致以下問題:
package-a 更新並移除對 package-b 的依賴時,你的程式碼會突然壞掉package-b,導致難以預測的行為所以,當 pnpm 找出這些幽靈依賴時,你應該把它們正式加入到 package.json 中。這雖然需要一些額外的工作,但長期來看,這會讓你的專案更加穩定和可維護。
對我來說,這個過程也算是清理了一些技術債,讓專案的依賴關係更加清晰。
"幽靈依賴" 就是你的程式碼使用了某個套件,但這個套件並沒有直接列在你的 package.json 的 dependencies 中。在 npm 的架構下,由於依賴會被提升(hoisting)到 node_modules 的根目錄,你可能會意外地使用到某個子依賴的套件,而沒有意識到它並不是你的直接依賴。
例如,假設你安裝了 package-a,而 package-a 依賴 package-b。在 npm 的架構下,package-b 可能會被提升到 node_modules 的根目錄,讓你可以直接 import 它,即使你沒有在 package.json 中聲明對 package-b 的依賴。
pnpm 使用更嚴格的依賴管理方式,它不會提升依賴,所以如果你嘗試使用一個沒有在 package.json 中聲明的套件,pnpm 會直接報錯。
在探索套件管理工具的過程中,我也注意到了 Bun。Bun 的野心很大,能做的事情很多:它可以管理 Node 套件、執行測試、執行編譯,甚至取代 Node.js 直接作為 TypeScript runtime,號稱速度是 npm 的「30 倍」。
根據 2026 年的比較數據,Bun 在冷安裝的速度上確實是最快的,可以在 3.5 秒內完成 npm 需要 35.5 秒的工作。這個效能提升非常驚人。
但為什麼我沒有選擇 Bun 呢?主要有兩個原因:
雖然 Bun 也有 workspace 功能,但根據 Better Stack 的比較文章,截至 2025 年,pnpm 在大型 monorepo 的磁碟效率上仍然是最佳的選擇。Bun 的 workspace 支援雖然已經相當完善,但在大型專案的場景下,pnpm 的內容可尋址儲存和硬連結機制仍然有優勢。
更重要的是,Bun 的 monorepo 工具生態系統還不如 pnpm 成熟。許多 monorepo 工具(如 Turborepo、Nx)對 pnpm 的支援更加完善,這讓 pnpm 在 monorepo 場景下更有優勢。
Bun 不是 Node.js,它是一個全新的 JavaScript runtime。雖然 Bun 努力保持與 Node.js 的相容性,但在某些場景下仍然會遇到問題。
我有小小的玩一下 Bun(配合 ElysiaJS),說真的我還沒有特別感覺到它快的優勢,因為我的 side-project 專案太小了。而且因為它其實不是 Node.js,所以會缺一些東西,特別是一些依賴 Node.js 特定 API 的套件。
對於生產環境的專案來說,相容性比速度更重要。如果一個工具雖然快,但會導致某些套件無法正常運作,那這個速度優勢就沒有意義了。
雖然我沒有用 Bun 來管理 monorepo 的專案依賴,但我發現了一個有趣的用法:可以在 monorepo 底下的 app 或 package 使用 Bun 去編譯或跑 dev。
例如,你可以在某個 package 中使用:
bun src/index.ts然後最後 runtime 繼續用 Node.js。因為 pnpm 管理套件的方式對 Bun 來說是沒有問題的,而且 Bun 也可以編譯 TypeScript,這樣就能在開發時享受 Bun 所帶來的速度優勢。
不過,這個方法有一些限制:
所以這個方法比較適合個人專案或小團隊。
除了前面提到的效能和 monorepo 支援之外,pnpm 還有一些其他的優勢:
pnpm 使用內容可尋址儲存(content-addressable storage)和硬連結(hard links),這意味著相同的套件只會儲存一份,所有專案都會連結到這個共享的儲存位置。這可以大幅減少磁碟空間的使用,特別是在你有多個專案的情況下。
根據 pnpm 官方的說明,pnpm 可以節省數 GB 的磁碟空間,這對於開發者來說是個不小的優勢。
pnpm 在安全性方面也有一些改進。例如,pnpm 可以移除 postinstall scripts(一個主要的攻擊向量),並且引入了 minimumReleaseAge 功能,可以延遲新版本一天或更長時間,讓你有時間檢查新版本是否有問題。
如前所述,pnpm 的嚴格依賴管理可以防止幽靈依賴,讓你的專案更加穩定。雖然這可能會在遷移時帶來一些額外的工作,但長期來看,這會讓你的專案更加可維護。
我認為以下情況特別適合使用 pnpm:
但如果你只是維護一個簡單的小專案,而且沒有 monorepo 的需求,那麼繼續使用 npm 也沒什麼問題。但我還是私心建議趕快改用 pnpm,反正遷移超簡單 😌
最後,我想說的是:工具是手段,不是目的。無論你選擇 npm、Yarn、pnpm 還是 Bun,最重要還是達成目標。如果你已經找到適合的工具,那就專注在解決實際問題上,不用為了換而換。
對我來說,pnpm 就是那個適合我的工具。它解決了我的 monorepo 需求,提升了我的開發效率,而且使用體驗良好。這就是我選擇 pnpm 的原因。
希望這篇經驗分享能幫助你做出更好的工具選擇決策。如果你也在使用 pnpm,或者有其他套件管理工具的經驗,歡迎分享你的想法!