我把 nvm、pyenv、jenv 全换成了 mise

我平时工作接触的东西比较杂。虽然是游戏开发,但用到的工具其实不少:Android 打包要用 Java,平时写脚本会用 Python 或者 JavaScript,还要维护项目的 GM 后台前端。

关键是不同项目依赖的版本还可能不一样。从 GitHub 上 clone 下来的项目,因为环境版本不对出现 bug 的情况,我也遇到过不少次。

以前我分别用 nvmpyenvjenv 管理这些版本,单独使用也没觉得有什么大问题。

直到最近整理了一下系统环境,这个问题就暴露出来了:.zshrc 里 Node 一套初始化,Python 一套初始化,Java 又是一套初始化。三个工具各管各的,命令不一样,配置位置不一样,出了问题还得分别排查。

说实话,不是 nvmpyenvjenv 不好用。单独拿出来,它们都能把自己的事情做好。真正让我难受的是:为了管理三个运行时,我也得同时管理三套版本管理工具。

所以我就搜了一下有没有开源工具能把这些事情统一起来,结果找到了 mise 官网GitHub 仓库。截至 2026 年 8 月,GitHub 上已经有 33.1K Star。

这次我干脆全部换成了 mise。用了几天之后,我的感觉是:这种工具就应该统一啊,GitHub 真是一个宝库。

最烦的不是装版本,而是版本不一致

自己一个人开发时,版本不一致还不算特别明显。机器上默认是什么版本,项目通常就先用什么版本,遇到问题再切一下。

团队开发就不一样了。

同一份代码,在我电脑上能跑,到同事电脑上突然报一个莫名其妙的错。查半天业务逻辑,最后发现一个人用 Node 20,另一个人用 Node 22;或者 Python 的小版本不同,某个依赖装出来的结果不一样;再或者 Android 项目要求 JDK 17,某台机器的 JAVA_HOME 还指向 JDK 11。

这种 bug 最恶心的地方,是它看起来像代码问题,实际上是环境问题。

大家在群里说一句“这个项目要用 Node 20”没什么用,README 里写一遍也不保险。真正可靠的方式,是把工具版本和代码放在一起,让项目自己声明:我到底需要什么环境。

这正是 mise 最打动我的地方。

mise 是什么

可以先把 mise 理解成一个开发工具版本管理器。

以前是:

  • Node.js 交给 nvm
  • Python 交给 pyenv
  • Java 交给 jenv

现在是:

Node.js ─┐
Python  ─┼─> mise
Java    ─┘

而且它不只管这三种。Go、Ruby、Rust、Terraform、各种 npm、pipx、GitHub Release 里的 CLI 工具,也都可以放进同一套配置里管理。

当然,我目前真正高频使用的还是 Node.js、Python,所以这篇只聊我实际用到的部分,不拿“支持上千种工具”这种数字凑功能表。

安装之后,.zshrc 终于清净了

我在 Mac 上通过 Homebrew 安装:

brew install mise

然后在 .zshrc 里激活:

eval "$(mise activate zsh)"

重新打开终端后,可以跑一下:

mise doctor

以前为了三个版本管理工具,要分别初始化、分别处理 PATH 和环境变量。现在核心配置只剩这一份。

这一点听起来不算什么大功能,但迁移过一次电脑就知道有多舒服。以后换机器,我不用再回忆 nvm 怎么装、pyenv 还缺哪些编译依赖、jenv 的初始化顺序应该放在哪。先装 mise,再交给它统一处理就行。

全局默认版本,一条命令就够了

平时总得有一套默认环境,可以直接这样配置:

mise use --global node@x.x.x python@x.x.x java@x.x.x

这条命令会安装对应工具 (版本号换成自己用的),并把它们写进 mise 的全局配置。之后在没有项目级配置的目录里,就使用这套默认版本。

检查当前环境也很直接:

node --version
python --version
java --version
echo $JAVA_HOME

最舒服的是项目级版本

全局版本只是基础,mise 真正解决团队问题的,是项目里的 mise.toml

比如进入一个项目后执行:

mise use node@22.14.0 python@3.12.9 java@22.0.0

mise 会在当前目录创建或更新 mise.toml

[tools]
node = "22.14.0"
python = "3.12.9"
java = "22.0.0"

把这个文件提交到 Git,工具版本就不再是某个人电脑里的隐形配置,而是项目的一部分。

团队成员拉下代码后,执行:

mise install

需要的版本就会按项目配置安装好。进入这个目录,终端自动使用项目指定的 Node.js、Python 和 Java;离开目录,又恢复到全局默认版本或者另一个项目的版本。

整个切换过程不需要我手动执行 nvm usepyenv local 或者再改一次 JAVA_HOME。这几天在 Mac 上用下来,目录切换基本没什么存在感,很丝滑。

我觉得这才是版本管理工具该有的状态:平时感觉不到它,只有环境不对时才意识到它替你挡掉了一个坑。

以前 Python 需要一个 .python-version,Node.js 需要一个 .nvmrc,现在一份 mise.toml 就能搞定。

做个临时小东西,也不用污染全局环境

我平时经常会临时写个脚本、验证一个想法。这种小东西可能今天用 Node,明天用 Python,过几天自己都忘了当时是什么版本。

以前通常懒得专门配置,直接拿全局版本跑。几个月之后再打开,突然跑不起来了,还得猜当时到底用了什么环境。

现在即使只是个小目录,也可以顺手执行:

mise use node@22

多一个很小的 mise.toml,换来的是这个目录以后随时打开都知道该用什么版本。这个习惯对“大项目”当然有用,但我觉得它对那些生命周期不确定的小工具更有价值。

如果只是临时跑一次,连配置文件都不想创建,也可以这样:

mise exec python@3.12 -- python script.py

它会在指定的 Python 环境里执行脚本,不需要先把全局版本切来切去。

它不只是版本管理器

我一开始只是想用 mise 替代 nvmpyenvjenv,但它实际上还可以管理项目环境变量和任务。

比如:

[tools]
node = "22.14.0"

[env]
NODE_ENV = "development"

[tasks]
dev = "npm run dev"
build = "npm run build"

之后可以统一执行:

mise run dev
mise run build

这样工具版本、环境变量和常用命令都放在一个文件里。新同事接手项目时,不需要先看半天文档,再手动拼出一套本地环境。

不过我现在没有急着把所有脚本都迁进去。版本管理已经解决了我最主要的问题,任务和环境变量准备后面按项目需要慢慢用。工具功能多不代表必须一次全上,先解决真实痛点更重要。

我最喜欢的三个地方

第一,一个入口

脑子里不用再记三套命令。想装版本、切版本、看当前版本,全部先找 mise。

工具一多,统一入口带来的不是少打几条命令,而是少了一整套上下文切换。

第二,项目环境可以提交到 Git

口头约定和 README 都会过期,配置文件才是能执行的约定。

只要团队统一安装 mise,拉代码后就能得到相同的运行时版本。它不可能消灭所有“我这里能跑”的问题,但至少能先把 Node、Python、Java 版本不同这一大类问题排除掉。

第三,迁移机器轻松很多

以前重装系统,需要分别安装 nvmpyenvjenv 和 JDK,再用不同的方式恢复各个项目需要的版本。现在只需要先装 mise,剩下的工具配置都能从全局配置和各项目的 mise.toml 恢复。

对于我这种今天写 TypeScript、明天跑 Python、后天又要打 Android 包的人,这种统一感比单项功能多强大更重要。

翻了翻其他人的评测,优缺点还挺一致

为了确认是不是刚换工具带来的新鲜感,我又看了几篇其他开发者的长期使用评测。

不过评价也不全是夸。在一篇 [Reddit 讨论]: https://www.reddit.com/r/webdev/comments/1hiripn/anybody_have_experience_with_mise_considering_it/ 里,比较集中的问题有两个:一是 VS Code 等从桌面启动的 IDE 有时拿不到终端里的 PATH,需要单独处理;二是冷门工具依赖不同 backend,质量和稳定性不一定像 Node.js、Python、Java 这些内置核心工具一样稳定。也有人对部分版本升级时的行为变化有意见。

所以我的判断没变:如果主要管理 Node.js、Python、Java 这类常用工具,mise 已经很好用了;如果项目依赖比较冷门的 SDK 或插件,先在自己的环境里完整跑一遍安装、切换和 CI,再决定要不要全团队迁移。

目前遇到和需要注意的地方

mise 也不是装完就能让所有软件自动理解你的环境,有几个边界最好提前知道。

第一,终端切换成功,不代表已经打开的 IDE 也会立刻切换。

Java 版本变化后,依赖 JAVA_HOME 的 IDE 可能需要重启。Android Studio 自己也有 Gradle JDK 配置,Gradle Daemon 还可能继续使用之前启动时的 Java。终端里 java --version 正确,只能证明当前终端正确,打包前最好再确认一下 Gradle 实际用了哪个 JDK。

第二,只使用 shims 时,部分环境变量能力不完整。

例如 Java 的 JAVA_HOME 需要 mise activatemise execmise run 提供完整环境,不能只看到 java 命令能运行就以为全部配置好了。交互式终端我更推荐按官方方式完整激活;CI 和脚本里则可以显式使用 mise execmise run

第三,陌生项目的配置别无脑信任。(项目配置是可以执行行为的, 这里可能会有恶意代码注入 或者 信息泄露风险,需谨慎)

别人提交的 mise.toml 如果包含环境指令、Hook 或任务,可能执行项目定义的命令,存在恶意代码执行或信息泄露风险。mise 遇到未信任的配置时可能会提示 mise trust,但也不要把这行提示当成唯一的安全检查:当前普通模式下,mise installmise runmise exec 等显式执行项目行为的命令会自动信任当前配置。网上随便拉下来的仓库,先看看配置写了什么,再执行这些命令。

第四,旧版本文件的兼容要自己确认。

mise 可以识别 .nvmrc.python-version.java-version 这类已有文件,但目前这些“惯用版本文件”默认并不是全部自动启用。如果正在迁移老项目,不要看到文件还在就默认 mise 一定读取了,先用 mise config ls 和实际版本确认一下。我的选择是新项目统一使用 mise.toml,规则更直观。

Windows 支持,但我没测过

mise 官方支持 Windows 和 PowerShell,Node.js、Python 等核心后端也提供 Windows 支持。官方文档还专门处理了 Windows 下 Python 可执行文件和 pip 包装脚本的问题。

因为我手上没有 Windows 设备,没有真实跑过安装、自动切换、PowerShell 激活和 IDE 集成,不清楚是否和 Mac 上体验一样丝滑。

Mac 上这几天的体验我可以负责:安装简单,切目录很顺,Node.js、Python、Java 放在一起管理之后,整个开发环境清爽了很多。Windows 到底有没有路径、权限或者终端兼容方面的小坑,还是得实际用过才知道。

最后附一份常用命令

下面这些基本覆盖了我日常管理 Node.js、Python 和 Java 的场景。命令里的 x.x.x 换成实际版本号。

# 安装 mise
brew install mise

# 安装后把这一行加入 .zshrc,重新打开终端生效
eval "$(mise activate zsh)"

# 查看当前生效版本
mise current
mise current node
mise ls

# 查看远端可安装版本
mise ls-remote java
mise ls-remote node
mise ls-remote python

# 设置全局默认版本
mise use --global python@x.x.x
mise use --global node@x.x.x
mise use --global java@x.x.x

# 为当前项目设置版本,同时写入当前目录的 mise.toml
mise use python@x.x.x
mise use node@x.x.x
mise use java@x.x.x

# 安装当前项目 mise.toml 中声明的全部工具
mise install

# 临时切换当前终端的版本
mise shell java@x.x.x

# 查看当前实际执行的命令,以及对应工具的安装目录
mise which node
mise where node

# 检查可更新项并升级
mise outdated
mise upgrade

# 仅安装指定版本,不写入 mise.toml,也不切换当前版本
mise install python@x.x.x
mise install node@x.x.x

# 卸载指定的已安装版本
mise uninstall node@x.x.x

# 环境诊断
mise doctor

这里有几个容易搞混的地方:

  • mise use 是“安装并使用”,还会把版本写入全局配置或当前项目的 mise.toml
  • mise install 只是安装。后面带版本号时不会自动写配置,也不会让这个版本立即生效;不带参数时则安装当前配置声明的全部工具;
  • mise shell 只影响当前终端,而且要求当前 shell 已经执行过 mise activate;关闭终端后这次切换就没了;
  • mise upgrade 默认只在配置允许的版本范围内升级。如果 mise.toml 写死了完整版本号,想升级并改写配置,需要先看 mise outdated --bump,再执行 mise upgrade --bump
  • mise uninstall 只删除本机安装的工具版本,不会删除 mise.toml 里的配置。如果项目仍然引用这个版本,下次执行 mise install 还会重新装回来。

总结

如果你只写一种语言,而且现有的 nvmpyenv 已经用得很舒服,完全没必要为了追新工具专门折腾。mise 最大的价值,不是它把某一种语言管理得比所有专用工具都强,而是它能用同一种方式管理很多工具。

如果你和我一样:

  • 项目类型比较杂,Node.js、Python、Java 来回切;
  • 经常因为团队成员环境版本不同遇到奇怪问题;
  • .zshrc 里塞了好几套版本管理工具的初始化;
  • 希望项目自己声明工具版本,换电脑后也能快速恢复;

那 mise 很值得试一下。

我目前的评价很简单:它没有发明版本管理这件事,只是终于把散落在各个语言里的版本管理,收进了同一个工具。

用了几天之后,我已经不太想回到 nvmpyenvjenv 各管一摊的状态了。