用 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 prepare 這個指令可以幫你預先下載指定版本的 pnpm:

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

如果在指令後面加上 --activate 參數,當專案中沒有 packageManager 這個屬性,但使用了 pnpm 的指令,Corepack 會直接預設幫你使用 corepack prepare --activate 所指定的 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 的 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 更好的管理方案。