過去切換 Node.js 版本的標準答案是 nvm。Vite+(指令是 vp)把這件事收進同一個工具:vp env 管 Node.js 與套件管理器的版本,同一個 vp 還接手檢查、格式化、測試。
這個站用的是 vite-plus 1.0.0-rc.0。這篇先講它收了哪些事,再講一個我們實際遇到、而且工具自己沒有報錯的坑:.node-version 寫好、也 pin 成功了,實際跑的卻是另一個版本。
一、vp 一個指令收了哪些事
vp --help 列出的指令裡,這個站實際在用的是下面幾組:
| 以前要裝的東西 | 現在的指令 | 這個站有沒有在用 |
|---|---|---|
| nvm 之類的 Node.js 版本管理 | vp env | 有,見下一節 |
| ESLint | vp lint | 有,--deny-warnings |
| Prettier | vp fmt | 有 |
| 格式、檢查、型別三件事分開跑 | vp check | 有,一次跑完 |
| Vitest | vp test | 有 |
| 安裝依賴 | vp install | 沒有,仍用 pnpm |
vp staged(對暫存檔跑檢查)與 vp hooks(管理 Git hook)在指令列表裡也有,我們這篇不評價,因為還沒有拿它們取代過別的東西。
底層是哪些工具,vp toolchain 會列出來。這個站目前的結果:
[email protected]
├── depends on @voidzero-dev/[email protected]
│ ├── bundles [email protected]
│ │ └── uses [email protected]
│ │ ├── compiles [email protected]
├── depends on [email protected]
├── depends on [email protected]
├── depends on [email protected]
也就是說 vp lint 背後是 oxlint、vp fmt 背後是 oxfmt。
二、用 .node-version 釘住 Node.js 版本
在專案根目錄跑:
vp env pin 22.19.0
實測會做兩件事:寫出內容為 22.19.0 的 .node-version,並且把這個版本預先下載好(不想下載可加 --no-install)。之後在這個目錄裡跑 vp node -v,預期得到 v22.19.0。
在一個乾淨的環境裡,這確實成立。問題出在不乾淨的環境。
三、坑:vp env use 的覆寫會蓋過所有專案的 pin
症狀
專案裡 .node-version 是 22.19.0,vp env pin 也顯示成功,但:
vp node -v
# v24.21.0
vp env doctor 的最後一行是 All checks passed。
為什麼直覺會猜錯
直覺有兩個:懷疑 .node-version 沒被讀到,或懷疑 Vite+ 當下是「優先用系統 Node.js」的模式。我們兩個都先查了,都不是主因:
- 在
vp env off node(優先用系統)的模式下,vp node -v仍然用了 pin 的版本。 - 換成全新的
VP_HOME(Vite+ 存放自己資料的目錄),同一個.node-version立刻生效。
差別在於,原本的環境裡曾經跑過 vp env use。
根因
vp env use 24.21.0 的說明寫的是「Activate ... for this shell session」,容易讓人以為只影響當下這個終端機。以一般執行檔呼叫 vp 時,實測並不是:它在 Vite+ 的資料目錄寫了一個 .session-node-version 檔,內容是 24.21.0(設了 VP_HOME 就在 VP_HOME 底下;沒設的話,在我們的機器上是 ~/.local/state/vite-plus/)。檔案是全域的,所以:
- 開另一個終端機、另一個 shell,一樣被覆寫。
- 切到完全不同的專案(那裡 pin 的是
20.19.0),也一樣被覆寫。 - 優先序高過專案的
.node-version。
vp env doctor 會把它標成警告(⚠ Session override (file)),但整體仍然顯示通過,不會讓指令失敗。
以上是 vp 以一般執行檔呼叫時的行為。如果 shell 載入了 Vite+ 的整合(type vp 顯示 vp is a function),vp env use 改設當前 shell 的環境變數 VP_NODE_VERSION,不寫檔,影響範圍只到這個 shell 和它開出的子行程;vp env doctor 顯示的是 Session override VP_NODE_VERSION=24.21.0。兩種情況都會蓋過切過去的每一個專案的 pin,差別只在新開的終端機會不會一起被蓋。
最小重現
在隔離的 VP_HOME 裡做,不碰既有環境:
# vp 若是 shell 函式(type vp 顯示 function),以下的 vp 改成 command vp
export VP_HOME=$(mktemp -d)
mkdir demo other
(cd other && vp env pin 20.19.0)
cd demo
vp env pin 22.19.0
vp node -v # v22.19.0
vp env use 24.21.0
vp node -v # v24.21.0,.node-version 還是 22.19.0
cd ../other && vp node -v # v24.21.0,這個目錄 pin 的是 20.19.0
# 另開終端機,export 同一個 VP_HOME 後到 other 跑 vp node -v,仍是 v24.21.0
vp env use --unset
vp node -v # v20.19.0,回到這個目錄自己 pin 的版本
判準
- pin 好了卻沒生效,先看覆寫,不要先改 pin。
vp env doctor裡有Session override就是它,vp env use --unset拿掉。 - 不要把
vp env use當成「只試一下」的指令。 它改的是全域狀態。要在單一專案固定版本,用vp env pin。 vp env current與vp node -v兩個指令的答案不一定一致。 在「優先用系統」模式下,我們測到前者回報系統的 24.21.0,後者卻用了 pin 的 22.19.0。判斷實際跑的版本,以vp node -v為準。
四、管理模式是分開開關的
vp env 不是全收或全不收。vp env doctor 會分開列出 Node.js、npm、pnpm、Yarn、Bun 各自是「託管」還是「優先用系統」。這個站的開發機就是混合的:Node.js 仍由系統端的版本管理工具提供,pnpm 則交給 Vite+ 託管,版本取自 package.json 的 packageManager 欄位。
所以「Vite+ 能不能取代 nvm」要拆開看:版本管理它做得到,但你得決定哪些元件交給它。把 Node.js 留給原本的工具、只讓它管 pnpm,是可行的做法。
五、升級紀錄與版本綁定
這個站的 vite-plus 在一個多月內升了兩次:
| 日期 | 變化 |
|---|---|
| 2026-09-10 | 0.1.22 → 0.3.1 |
| 2026-10-03 | 0.3.1 → 1.0.0-rc.0,vitest 同時從 4.1.11 升到 5.0.1 |
兩點值得記:
vitest版本跟著vite-plus走。 1.0.0-rc.0 搭vitest5.0.1,兩者是同一個 commit 一起換的,沒有單獨升其中一個。vite是被別名取代的。pnpm-workspace.yaml用 catalog 把vite指向@voidzero-dev/vite-plus-core,再用overrides讓所有依賴它的套件拿到同一份,並且在peerDependencyRules放行vite與vitest的任意版本。
另一個會踩到的是 pnpm 的 release-age 冷卻:剛發布的版本在冷卻期內裝不起來。升 1.0.0-rc.0 時,pnpm 把例外寫進 minimumReleaseAgeExclude,而且是一個一個列:vite-plus 本身、@voidzero-dev/vite-plus-core,加上 8 個平台的二進位套件(macOS 與 Windows 各 arm64、x64 兩個,Linux 的 arm64、x64 再各分 glibc 與 musl 兩種)。例外名單要跟著版本號寫,下一個版本就得重來一次。
升完之後的驗證,這個站是跑完整的 generate、build、型別檢查、vp test、lint 與 format 檢查,全部退出碼為 0 才算數。
這篇沒有驗證的
- 只在 Linux 上測過
vp env,Windows 與 macOS 沒測。 vp staged與vp hooks沒有拿來取代過別的東西,所以沒有評價。- 1.0.0-rc.0 仍是候選版,指令與行為在正式版前可能再變。這篇的重現步驟以此版本為準。