如果你曾經在 node_modules 裡迷路,先別怪自己——那個資料夾本來就很有「黑箱」氣質。npm、Yarn、pnpm 都能幫你安裝套件,但它們對鎖定版本、整理依賴、支援 monorepo 的做法並不相同。這篇會用實際指令和可驗證的差異,帶你選出適合專案的工具;讀完至少不會再靠「同事說 pnpm 比較快」來做技術決策。咖啡可以續杯,依賴則要鎖好。

先搞懂:套件管理器在管理什麼

Node.js 專案通常用 package.json 描述名稱、腳本與依賴。套件管理器會依照這份描述下載套件,建立可供 Node.js 使用的依賴結構,並透過 lockfile 記錄實際解析出的版本。

這裡有一個重要區分:package.json 表示「我允許哪些版本範圍」,lockfile 則更接近「這次安裝實際拿了哪些版本」。因此,團隊協作或 CI 建置時,lockfile 通常應該一併提交。依賴沒有鎖定,就像咖啡沒有杯蓋:平常看起來沒事,走快一點就知道了。

npm:Node.js 生態系的預設起點

Node.js 官方說明將 npm 稱為 Node.js 的標準套件管理器;使用官方 Node.js 安裝程式時,npm 也會一併安裝。npm 不只負責安裝套件,也能執行 package.json 裡的 scripts。

npm 的基本操作

下面是最常見、也最容易從文件核對的流程:

npm init
npm install <package-name>
npm install --save-dev <package-name>
npm run <script-name>

新增或更新依賴時,npm 會產生或更新 package-lock.json。npm 文件指出,這份檔案描述實際產生的依賴樹,讓後續安裝更容易重現相同結果。

npm 適合什麼情境

  • 專案想採用 Node.js 生態系最常見的預設工具。
  • 團隊既有流程已經使用 package-lock.json
  • 你希望新成員只要安裝 Node.js,就能直接開始工作。

npm 的優點是文件、社群與第三方工具支援廣泛;代價則是不同專案的安裝結果與磁碟使用量,仍會受到依賴樹、npm 設定和 lockfile 影響。不要把「誰永遠最快」寫進規格書——那通常比 node_modules 還容易膨脹。

Yarn:從 Classic 到現代 Yarn

為什麼不能只說「Yarn」?

Yarn 有 Classic(1.x)與 Modern(Berry)兩條常被討論的使用經驗。它們的指令看起來相似,但設定檔、工作流程與安裝策略可能不同,所以比較時先確認團隊使用的版本與文件非常重要。

Yarn 使用 yarn.lock 記錄依賴解析結果。現代 Yarn 也把 workspaces、外掛與 Plug'n'Play(PnP)放在核心體驗裡,適合需要管理多個套件的 monorepo。

Plug'n'Play 不等於「所有專案都不用 node_modules」

PnP 是 Yarn 提供的安裝策略:它可以不建立傳統的 node_modules,改以 .pnp.cjs 等檔案描述套件如何解析。這能減少某些依賴樹的重複與解析成本,但工具必須理解 PnP;遇到尚未支援的工具時,Yarn 也提供 node-modules linker 作為相容選項。

換句話說,PnP 是設計選擇,不是魔法按鈕。啟用前要確認框架、測試工具、IDE 與部署環境都能配合。否則你會得到一個很現代的設定檔,以及一杯很傳統的除錯咖啡。

現代 Yarn 的例子

yarn init
yarn add <package-name>
yarn add --dev <package-name>
yarn run <script-name>

如果要在不把 CLI 長期加入全域環境的情況下執行工具,現代 Yarn 文件提供 yarn dlx;使用方式仍應以該工具自己的文件為準。

pnpm:用共享儲存與嚴格解析減少浪費

pnpm 的核心差異不只是「指令前面多一個 p」。它使用 content-addressable store 保存套件檔案;安裝到專案時,檔案會從共享儲存以 hard link 連結,再用 symlink 建立依賴圖。相同內容可以在多個專案之間共用,減少重複儲存。

pnpm 預設也不會把所有間接依賴都平鋪到專案根目錄的 node_modules。這有助於避免程式碼意外使用未宣告的依賴;但某些假設「所有套件都在最外層」的舊工具,可能需要調整設定。技術世界裡,嚴格不是難相處,是幫你早點發現問題。

pnpm 的基本操作

pnpm init
pnpm add <package-name>
pnpm add --save-dev <package-name>
pnpm run <script-name>

pnpm 以 pnpm-lock.yaml 鎖定依賴。它也內建 workspace 支援,能在同一個儲存庫中管理多個套件;monorepo 專案通常會再搭配 pnpm-workspace.yaml 描述 workspace 範圍。

三者怎麼比較?

指令相似,但 lockfile 不能混用

三者都能完成初始化、安裝套件與執行 scripts,差別主要在 lockfile、依賴結構和額外功能:

  • npm 使用 package-lock.json
  • Yarn 使用 yarn.lock
  • pnpm 使用 pnpm-lock.yaml

同一個專案不要讓團隊成員各自用不同管理器並提交不同 lockfile。那不是「彈性」,而是讓 CI 每次抽不同牌。若要切換工具,請先決定要保留哪一份 lockfile,再用新工具重新安裝並在 CI 驗證。

磁碟空間與安裝速度

pnpm 的共享儲存設計,在多個專案共用相同依賴時通常有明顯空間優勢。npm、Yarn Classic 與現代 Yarn 的實際結果,則會受到快取、安裝策略、lockfile、網路與專案依賴組成影響。

因此,不能只拿別人的跑分就宣布冠軍。若速度是你的瓶頸,請用自己的專案、固定的 lockfile、相同的快取條件測量;官方 benchmark 只能當參考,不能代替你的 CI。

monorepo 與工作區

三者都能支援某種形式的多套件專案,但使用體驗不同:

  • npm 透過 workspaces 管理同一個專案中的多個套件。
  • Yarn 將 workspaces 視為現代工作流程的重要部分,並可搭配 PnP。
  • pnpm 內建 workspace 概念,並以 workspace: protocol 表達本地套件依賴。

若團隊大量依賴 workspace、需要控制依賴可見性,pnpm 或現代 Yarn 值得優先評估;若專案單純、工具相容性最重要,npm 往往已經足夠。

從本地依賴執行 CLI:npx、exec 與 dlx

這幾個命令常被混在一起,其實可以先記住「執行已安裝工具」與「暫時取得工具」是兩件事。

npx 是 npm 提供的命令,現在由 npm exec 的能力支援。它可以在本地專案依賴或指定的 npm 套件環境中執行 binary:

npx -- <package-name>

若套件已安裝在專案中,也可以執行它提供的 CLI:

npx jest

npm 文件建議在需要明確分隔 npm 選項與目標命令時使用 --。另外,npx 可能為了執行指定套件而從 registry 取得套件;在 CI 或安全敏感流程中,請明確指定版本並確認來源。

pnpm 對應的兩個常用命令是:

pnpm exec jest
pnpm dlx <package-name>

pnpm exec 用於專案環境中的 binary;pnpm dlx 則用於取得並執行一次性的 CLI。Yarn 的現代對應方式通常是:

yarn dlx <package-name>

這些命令都不是「免費安全通行證」。執行前仍要看套件名稱、版本與權限;AI 產生的安裝指令也一樣,先讀文件,再按 Enter,咖啡可以冷,供應鏈不能隨便。

我的選擇建議

你可以用下面的順序做決定:

  1. 先看專案既有 lockfile。 維護既有專案時,沿用原工具通常最省風險。
  2. 再看工具相容性。 特別是 Yarn PnP;若周邊工具不支援,改用 node-modules linker 或選擇其他策略。
  3. 如果是 monorepo,再比較 workspace 體驗。 不要只看單一套件的安裝速度。
  4. 最後才測量速度與空間。 使用自己的依賴、相同環境與固定 lockfile。

簡單說:npm 是穩妥的預設起點,現代 Yarn 適合重視工作區與可選 PnP 的團隊,pnpm 則適合在意共享儲存、嚴格依賴解析與 monorepo 的專案。沒有永遠的冠軍,只有符合需求的工具;真的選不出來,就先問問專案的 lockfile——它通常比會議更誠實。

參考資料