每個有點規模的後端團隊,桌面上的專案多半都是混雜的。一個 Node 18 的 API、一個 Python 3.12 的資料 ETL、一個 Go 1.22 寫的 CLI、再加上某個老舊 Rails 5 的內部報表系統。每次切到不同 repo,shell 裡的 node --version 跟 python --version 都得跟著換,這就是版本管理工具存在的理由。
過去十年這塊地盤幾乎是 asdf 在守。它用 bash 寫的 plugin 架構把 nvm、pyenv、rbenv、gvm 等各語言專屬工具收成一個介面。問題是 bash 跟 shim 這兩個基底實作起來,現代開發者越來越受不了——每執行一條指令多 120ms 的延遲、每次 cd 進 monorepo 卡個幾百毫秒,累積一整天就是真實的工時損失。asdf 2025 年底發布 0.16 版本,整個核心從 bash 改寫成 Go,效能終於追了上來。但這個時間點,Mise 已經把版本管理、環境變數、task runner 三件事都收進同一支 Rust binary,而且累積到接近三萬顆 GitHub 星。
shim 這個架構從根上就慢
asdf 之所以能支援所有語言,靠的是 shim 機制:當執行 node 時,實際呼叫到的是 ~/.asdf/shims/node 這個 bash 腳本,腳本去讀當前目錄的 .tool-versions、解析該用哪個版本、再轉發到真正的執行檔。這個設計很優雅,但每次都要付出代價——bash 解釋器啟動、檔案 I/O、字串解析,全部加起來大約 120 毫秒。
對人工敲指令的場景,120ms 還在容忍範圍內。但編輯器、Language Server、Makefile、CI script 都會大量呼叫 node、npm、python,這個延遲會被乘上幾十倍。LSP 啟動一次跑 30 個 sub-process,光是 shim 開銷就占掉 3.6 秒。寫 React 專案的人應該都有過「按下儲存後游標卡住一拍」的經驗,那個拍子的相當部分就是 shim 解析吃掉的。
Mise 的解法是徹底捨棄 shim。它直接修改 shell 的 PATH 環境變數,把當前專案該用的版本路徑塞進去,呼叫 node 就是直接打到對應版本的二進位檔,中間沒有任何代理層。cd 進新目錄時觸發一個用 Rust 寫的偵測函式,沒變動就 4ms 結束、需要重載也只要 14ms。對比之下 asdf 0.16 改成 Go 之後,directory hook 也壓到了個位數毫秒,這部分差距確實縮小了。
但效能不是 Mise 真正的賣點
asdf 在 0.16 用 Go 重寫之後,純版本管理場景的效能差距已經沒那麼明顯。Mise 仍然快,但快得不夠多到值得整組團隊換工具。真正讓 Mise 成為 2026 年新專案預設選擇的,是它把另外兩件事一起做了。
第一件是環境變數管理。一個典型的 Node 專案會有 .env 載入 DATABASE_URL、Python 專案會有 .venv/bin/activate 設定路徑、Go 專案會有 GOFLAGS。傳統做法是搭配 direnv 之類的工具,但 direnv 又是另一個要裝、要設定、要除錯的元件。Mise 直接把這層收進 mise.toml:
1 | [tools] |
_.file 自動載入 .env 檔,_.source 執行 shell 腳本並擷取其匯出的變數,PATH 操作則用結構化語法處理,不必再寫一堆 bash 拼接。對於把密鑰存在 1Password CLI 或 AWS Secrets Manager 的團隊,Mise 也有原生整合可以動態抓進來,不會落地到磁碟。
第二件是 task runner。寫 Makefile、寫 npm script、寫 justfile,每個專案的慣例都不同。Mise 提供統一的 task 語法,定義在同一個 mise.toml:
1 | [tasks.build] |
mise run test 就能跑,mise tasks 列出所有可用任務。對於剛接手陌生 repo 的新成員,這個 self-documenting 的特性比翻 README 找指令快得多。task 之間還能用 depends 串依賴關係,等於把 Makefile 的功能用更現代的語法重做了一次。
跟既有工具的相容性處理得相當細
工具切換最怕的是搬家成本。Mise 在這方面做得意外乾淨——直接讀 .tool-versions、直接吃 asdf plugin、.nvmrc、.python-version、.ruby-version 全部支援。從 asdf 切過去通常只需要兩步:裝 Mise、註冊 shell hook,原有的 .tool-versions 不用改。
對需要保留多種工具同時運作的環境,Mise 也不會搶 PATH。它只在偵測到當前目錄屬於某個 mise 管理的專案時才注入路徑,離開該目錄就還原。這代表 nvm 或 pyenv 沒有完全移除也不會打架,可以漸進式遷移。
Plugin 安全性是另一個值得提的差異。asdf plugin 本質上是 bash 腳本,裝一個 plugin 等於授權對方在你 shell 裡執行任意指令,過去也確實出過 plugin repo 被棄置後遭收購、塞惡意程式碼的事件。Mise 預設使用內建的 backend,950 個常用工具直接從官方來源驗證 GPG、Cosign、SLSA 簽章下載,不必經過第三方腳本。需要冷門工具時還是可以裝 asdf plugin,但這個選擇是顯式的,不是預設值。
什麼時候該繼續用 nvm 或 pyenv
不是所有人都該換 Mise。三種場景反而適合留在單一語言的版本管理工具:純前端團隊只開 Node 專案、原本就習慣 nvm 的 shell 整合,沒理由把學習成本花在 Mise;資料科學環境深度綁定 conda 或 uv,這兩個的虛擬環境機制比 Mise 的 venv 管理更完整;CI runner 容器化以後本來就只裝一個語言版本,多包一層 Mise 反而增加 image 大小。
但只要專案開始混雜兩種以上語言、或團隊有多個專案需要不同版本,Mise 的整合價值就會浮出來。mise.toml 進 git 之後,新人 clone 完 repo 跑 mise install 就拿到一致的工具版本、環境變數、task 列表,不必再寫一份「開發環境設定」的 README 還跟維護現實脫節。
在伺服器上怎麼用
Mise 不只是本機開發工具,伺服器端的部署流程也用得到。常見的兩種場景:CI runner 跟自架 build server。
CI 場景下,把 mise.toml 進 repo 後,CI 腳本第一行 mise install 就能裝齊本次 build 需要的所有工具版本。比 GitHub Actions 那種 actions/setup-node 系列要快——後者每個語言要一個 action、每個都跑一次網路請求;Mise 一次解析完整個專案的需求一起裝,並把結果快取在 ~/.local/share/mise/,下次 build 直接用。
自架 build server 上更明顯。一臺機器上同時跑多個 repo 的 CI job,每個 repo 用不同語言版本,Mise 用 PATH 切換就能做到隔離,不必為每個版本組合開不同的 container。對自己維運 CI 機房的團隊,這是省下不少 image 維護成本的設計。
結論
asdf 用 0.16 補上了 bash 改 Go 這個遲到的修補,但 Mise 已經把問題的定義拉大——版本管理只是入口,環境變數跟 task 都是配套的同一件事。對新專案來說,預設選 Mise 已經沒有什麼好猶豫的;既有 asdf 環境的團隊則可以利用兩者的相容性,挑一個低風險時機切過去。
執行 CI runner、build server、跑開發環境,這些都是吃 CPU 跟 I/O 的工作負載。NCSE Network 在臺灣是方電訊機房提供 Intel Gold CPU 加 NVMe SSD 的 VPS 主機,本地網路延遲低,跑自架 CI、共享 build server 或多開發者協作環境的反應速度都相當穩定,是把 Mise 工作流搬上雲端的合理基礎。