起因
每个项目用不同版本的 runtime / SDK:
- 项目 A:Node 18 + Python 3.11
- 项目 B:Node 20 + Python 3.12
- 项目 C:Go 1.22 + Bun 1.1
老办法各种 version manager:
- nvm(Node)
- pyenv(Python)
- rbenv(Ruby)
- gvm / goenv(Go)
每个工具一套 shell 集成 / shim 机制 → 启动慢 + 配置碎。
mise(Rust 写的,前身 rtx,前前身 asdf 的现代 fork)是一个工具
管理所有版本。.mise.toml 文件描述项目所需版本,进目录自动切。
装
# macOS
brew install mise
# Linux
curl https://mise.run | sh
# 集成 shell
echo 'eval "$(mise activate zsh)"' >> ~/.zshrc
项目用
cd ~/projects/myapp
# 给项目指定 Node 20 + Python 3.12
mise use node@20 [email protected]
# 自动写到 .mise.toml
cat .mise.toml
# [tools]
# node = "20"
# python = "3.12"
# 装这俩
mise install
进入这目录 → mise 自动激活对应版本:
$ cd ~/projects/myapp
$ node --version
v20.18.0
$ python --version
Python 3.12.4
$ cd ~/projects/other
$ node --version
v18.20.0 # 自动切换
.mise.toml 完整例
# .mise.toml(项目根,commit 进 git)
[tools]
node = "20.18.0" # 精确版本
python = "3.12" # 主版本
go = "1.22"
bun = "latest" # 最新
"npm:pnpm" = "9" # 通过 npm 装 pnpm
# 环境变量
[env]
NODE_ENV = "development"
PYTHONUNBUFFERED = "1"
_.file = ".env" # 加载 .env 文件
# 项目 task(取代 justfile / Makefile,简单场景)
[tasks.dev]
run = "node server.js"
[tasks.test]
run = "pnpm test"
mise install 装全部 → 进目录自动用。
比 asdf 快多少
mise 是 asdf 的 Rust rewrite + 改进:
| asdf | mise | |
|---|---|---|
| 语言 | Bash | Rust |
| shell hook 延迟 | 50-200ms | < 5ms |
| 工具机制 | shim | env-based + shim |
| 配置 | .tool-versions | .mise.toml |
| 任务系统 | 无 | 有(取代 just) |
| 装速度 | 慢 | 快 |
cd 到项目目录,asdf 的 shell hook 拖 100ms+。mise 几乎无感。
多 runtime + 同时
[tools]
node = ["20", "22"] # 两个版本都装
# 默认用第一个 → node 是 20
# 调 22:mise exec node@22 -- node --version
共享 mise.toml + 个人 override
# .mise.toml(commit)
[tools]
node = "20"
python = "3.12"
# .mise.local.toml(gitignore,个人覆盖)
[tools]
python = "3.13" # 我本地试 3.13
.mise.local.toml 覆盖 .mise.toml,团队不受影响。
全局默认
mise use -g node@22
mise use -g [email protected]
写到 ~/.config/mise/config.toml。没有 .mise.toml 的目录用全局
版本。
工具源
mise 用 asdf 插件 + 内置后端:
# 列出可装
mise ls-remote node
mise ls-remote python
# 通过 npm / pip / cargo 后端装
mise use "npm:typescript@5"
mise use "pipx:black@24"
mise use "cargo:ripgrep@13"
也支持 GitHub release / ubi / pre-compiled binary 等。
task runner
[tasks.lint]
description = "Run linters"
run = """
ruff check .
pnpm tsc --noEmit
"""
[tasks.test]
depends = ["lint"]
run = "pytest"
$ mise tasks # 列出
$ mise run test # 跑(先跑 lint)
替代 justfile 的简单场景。复杂的还是 just 强。
环境变量 + secrets
[env]
DATABASE_URL = "postgres://localhost/myapp"
_.file = ".env.local" # 也加载这文件
_.path = ["./bin", "./node_modules/.bin"] # 加 PATH
进目录自动 export → 写脚本不用 source。
.env.local 在 .gitignore,本地 secrets。
CI 用 mise
# GitHub Actions
- uses: jdx/mise-action@v2
with:
install: true
- run: mise run test
CI 装的版本 = .mise.toml 里的版本 = 本地版本。一致性保证。
与 Docker dev container 对比
| mise | Docker dev container | |
|---|---|---|
| 隔离 | 进程级 | 完全隔离 |
| 启动 | 即时 | 启动 container 几秒 |
| IDE | 原生(无远程) | VS Code Remote |
| 文件 IO | 原生 | bind mount 可能慢(mac) |
| 生产一致性 | 中(runtime 一致 OS 可能不同) | 高 |
小项目 / 单人 → mise 够用 + 更快。
大项目 + 多 OS 团队 → devcontainer 更靠谱。
我自己 95% 项目 mise,少数客户项目 devcontainer。
从 nvm 迁移
# 卸 nvm
rm -rf ~/.nvm
# 从 .zshrc 删 nvm source
# 装 mise + 集成
# 老的 .nvmrc 自动识别(兼容)
echo "v20" > .nvmrc
mise install
.nvmrc / .python-version / .tool-versions mise 全识别。
不强制迁到 .mise.toml,但建议(功能更强)。
踩过的坑
-
shell 没集成:
node --version返回老版本。mise 必须 shell
hook 才能切。mise activate加 rc 文件 + 重开终端。 -
CI 装很慢:mise 第一次装 Python 3.12 编译几分钟。
MISE_PYTHON_COMPILE=0用预编译 binary(uv 同样做法)。或者
cache~/.local/share/mise/installs。 -
跟 asdf 冲突:装了 asdf 又装 mise → PATH 混。卸一个。
-
某些工具没插件:罕见工具 mise 没现成插件。可以
mise plugin install <git-url>加 asdf 插件用。 -
mise use不写版本会写 latest 到 .mise.toml:commit 进 git
后别人装的可能是更新版本。建议明确版本号。
登录后参与评论。