用 Corepack 管理 pnpm 版本

用 Corepack 管理 pnpm 版本

開發筆記

最近幫新人設定環境時,才發現目前 pnpm 預設安裝的版本都是 v9,而目前大多專案仍是使用 v8。

考慮到舊的專案可能不適合升級 pnpm 版本,總要有可以降 pnpm 版本的方式。

目前在 Mac 上安裝 pnpm 我主要都用 Homebrew 安裝,但這個方式無法指定 pnpm 版本來安裝。

研究了一陣子後,我覺得最理想的方案大概是使用 Corepack…

什麼是 Corepack

Corepack 是一個套件管理工具的管理工具(超繞嘴)。

簡單來說,就是可以管理如 yarnpnpm 版本的工具。

目前 Corepack 預設內建在 v16.13 以上版本的 Node.js 中,如果使用 Homebrew 安裝 Node.js,則會需要另外安裝 Corepack。

可以透過 npm -g list 檢查目前 Node.js 中是否有 Corepack:

$ npm -g list
 
/Users/noahchen/.nvm/versions/node/v20.16.0/lib
├── [email protected]
└── [email protected]

由於 Corepack 目前還算是實驗性工具,所以如果想要使用則需要手動啟用 Corepack:

$ corepack enable

由於 Corepack 是跟著 Node 版本,因此透過 nvmfnm 切換到第一次使用的 Node 版本,會需要重新啟動 Corepack。

在 Corepack 中使用 pnpm

在 Corepack 啟用後,就可以直接透過 Corepack 使用 pnpm:

$ corepack enable pnpm

接下來只要在 package.json 裡面加上 packageManager 這個屬性,Corepack 就會幫你自動選擇對應的 pnpm 版本:

package.json
{
  "packageManager": "[email protected]"
}

也就是說,只要在每個專案中寫好 packageManager,就可以確保其他開發者也使用相同版本的 pnpm 啦!

切換 pnpm 版本

可以透過 corepack use 指令來指定 Corepack 預設的 pnpm 版本:

# 安裝最新版的 pnpm
$ corepack use pnpm@latest
 
# 安裝 v8 版本的 pnpm
$ corepack use pnpm@latest-8

使用這個指令後,會自動在 package.json 中加上 packageManager 這個屬性,並自動執行安裝:

{
  // 透過 Corepack use 預設還會添加 sha512 的 hash 值
  // 移除也不影響切換版本,但官方建議將其保留作為安全措施
  "packageManager": "[email protected]+sha512.499434c9d8fdd1a2...."
}

packageManager 這個屬性後,使用其他的套件管理工具的指令會出現錯誤,需要修改 package.json 中的 packageManager 來指定正確的管理工具和版本:

$ pnpm -v
 
/Users/noahchen/.nvm/versions/node/v18.20.4/lib/node_modules/corepack/dist/lib/corepack.cjs:23509
  throw new UsageError(`This project is configured to use ${result.spec.name} because ${result.target} has a "packageManager" field`);
        ^
 
  UsageError: This project is configured to use yarn because /Users/noahchen/Documents/Github/noah4520/package.json has a "packageManager" field
    at Engine.findProjectSpec (/Users/noahchen/.nvm/versions/node/v18.20.4/lib/node_modules/corepack/dist/lib/corepack.cjs:23509:21)
    at async Engine.executePackageManagerRequest (/Users/noahchen/.nvm/versions/node/v18.20.4/lib/node_modules/corepack/dist/lib/corepack.cjs:23539:24)
    at async Object.runMain (/Users/noahchen/.nvm/versions/node/v18.20.4/lib/node_modules/corepack/dist/lib/corepack.cjs:24232:5) {
  clipanion: { type: 'usage' }
}
 
Node.js v18.20.4

另外,Corepack 是跟著 Node 版本的,所以如果調整了 Node 版本,但又沒有 packageManager,那就會發現 pnpm 指令失效:

$ pnpm -v
 
zsh: command not found: pnpm

這時候重新啟動 Corepack 使用 pnpm 即可:

$ corepack enable pnpm

指定預設使用的 pnpm 版本

當設定新專案時,很可能會發現 Corepack 使用的 pnpm 預設版本和想使用的版本不一樣。

在知道如何設定預設版本之前,要先了解 corepack prepare 這個指令。

corepack prepare 可以幫你預先下載指定版本的 pnpm 到本機上:

$ corepack prepare [email protected]
Preparing [email protected]...

而我們只要在這個指令後面加上 --activate 參數,就能指定 pnpm 的預設版本了!!

當專案中沒有 packageManager 這個屬性,但使用了 pnpm 的指令,Corepack 會使用你指定的 pnpm 版本:

$ corepack prepare [email protected] --activate
Preparing [email protected]...
 
$ pnpm -v
 
! The local project doesn't define a 'packageManager' field. Corepack will now add one referencing [email protected]+sha512.faf344af2d6ca65c4c5c8c2224ea77a81a5e8859cbc4e06b1511ddce2f0151512431dd19e6aff31f2c6a8f5f2aced9bd2273e1fed7dd4de1868984059d2c4247.
! For more details about this field, consult the documentation at https://nodejs.org/api/packages.html#packagemanager
 
9.7.1

若執行了 corepack prepare 或只是單純想手動清除 Corepack 的 local 版本,可以使用 corepack cache clean 指令:

$ corepack cache clean

再執行 pnpm -v 時,因為下載的版本已經被刪除,所以 Corepack 會詢問是否要下載 pnpm 版本:

$ pnpm -v
 
! Corepack is about to download https://registry.npmjs.org/pnpm/-/pnpm-9.9.0.tgz
? Do you want to continue? [Y/n]

結語

搞懂 Corepack 後,會發現這東西真的很方便,只要指定好 packageManager 就不需要再擔心不同專案間的 pnpm 版本問題。

但撰寫這篇文章時,也剛好看到國外的文章在討論 Node.js 團隊要移除 Corepack 的計劃。

我個人是完全支持的,畢竟使用依賴在 Node.js 中的工具來管理額外安裝的套件版本,總覺得有點彆扭,總覺得 pnpm 這種工具更應該直接獨立出來安裝和管理,而不是依賴在 Node.js 中。

目前 pnpm 也陸續有針對版本管理的討論跟實驗性功能,相信未來應該會有比 Corepack 更好的管理方案。