从 nvm 到 fnm:更快、更省心的 Node.js 版本管理器迁移指南
如果你受够了每次开终端都要等上好几秒、
nvm use在新 shell 里"失忆"、Windows 上还得装nvm-windows这个另起炉灶的分支------那么 fnm(Fast Node Manager)值得你花十分钟迁移过来。
一、为什么需要 Node.js 版本管理器
前端生态迭代极快:一个老项目可能锁死在 Node 16,新项目却要用 Node 22 的原生 ESM 与测试运行器。手动装/卸 Node 版本既痛苦又容易污染全局。版本管理器的价值在于:
- 一键切换 :
nvm use 18/fnm use 18秒切版本 - 目录自动切换 :进入项目目录自动读取
.nvmrc/.node-version,无需手动 - 隔离全局依赖 :每个 Node 版本有独立的全局
node_modules,互不干扰
二、nvm:经典但显老
nvm(Node Version Manager) 是社区最老牌的方案,GitHub 仓库 nvm-sh/nvm 累计 ~81k stars。
优点:
- 生态最成熟,几乎所有教程都默认你用 nvm
- 纯 Bash/Zsh 脚本实现,零额外依赖
痛点:
- 慢 :每次启动 shell 都要
source nvm.sh,这段脚本会同步加载所有版本元数据,冷启动开销大(普遍几百毫秒到数秒) - Windows 不官方支持 :必须改用第三方分支
coreybutler/nvm-windows,命令和实现都和 macOS/Linux 版不一样 - 自动切换靠手搓 hook :需要自己在
.bashrc里写cd钩子才能根据.nvmrc自动切版本 - 单线程 shell 脚本:下载、解压、读元数据都没有并行优化
三、fnm:用 Rust 重写的现代替代品
fnm(Fast Node Manager) 由 Gal Schlezinger 开源,仓库地址 Schniz/fnm,目前 26.4k stars ,用 Rust 编写,单一二进制文件。
核心优势:
| 特性 | nvm | fnm |
|---|---|---|
| 实现语言 | Bash/Zsh 脚本 | Rust 编译二进制 |
| 安装包体积 | 脚本(KB 级) | 单二进制(~5MB) |
| Shell 启动开销 | 较大(source 脚本) | 极小(仅注入一个 shell 函数) |
| 版本切换速度 | 慢(同步解析) | 快(并行下载 + 缓存) |
| Windows 原生支持 | ❌ 需 nvm-windows 分支 | ✅ 原生支持 |
.nvmrc 自动切换 |
需手写 cd 钩子 | ✅ 内置 --use-on-cd |
.node-version 支持 |
❌ | ✅ |
| 多架构(arm64 等) | 支持 | 支持 |
简单说:fnm 把 nvm 做的事,用 Rust 重新做了一遍,而且做得更快、更跨平台、开箱即用。
四、安装 fnm
macOS / Linux(推荐 Homebrew 或脚本)
bash
# 方式一:Homebrew(macOS / Linuxbrew)
brew install fnm
# 方式二:官方安装脚本(curl)
curl -fsSL https://fnm.vercel.app/install | bash
Windows
powershell
# 方式一:Scoop
scoop install fnm
# 方式二:Winget
winget install Schniz.fnm
# 方式三:Chocolatey
choco install fnm
配置 Shell 集成(关键一步)
安装后必须把 fnm 的 shell 钩子加到你的启动脚本里,并开启 自动切换:
Bash (~/.bashrc):
bash
eval "$(fnm env --use-on-cd --shell bash)"
Zsh (~/.zshrc):
zsh
eval "$(fnm env --use-on-cd --shell zsh)"
PowerShell ($PROFILE):
powershell
fnm env --use-on-cd --shell powershell | Out-String | Invoke-Expression
Fish (~/.config/fish/config.fish):
fish
fnm env --use-on-cd --shell fish | source
💡
--use-on-cd是 fnm 的杀手锏:进入任何含.nvmrc或.node-version的目录时,自动切换到对应 Node 版本;离开时自动切回默认版本。nvm 需要你手写钩子才能实现。
配置完成后重启终端,验证安装:
bash
fnm --version
五、命令对照表(nvm → fnm 迁移速查)
这是迁移过程中最常查的部分,建议收藏:
| 操作 | nvm 命令 | fnm 命令 |
|---|---|---|
| 安装指定版本 | nvm install 18 |
fnm install 18 |
| 安装最新 LTS | nvm install --lts |
fnm install --lts |
| 使用某版本(当前 shell) | nvm use 18 |
fnm use 18 |
| 设默认版本 | nvm alias default 18 |
fnm default 18 |
| 列出已装版本 | nvm ls |
fnm list |
| 列出远程可装版本 | nvm ls-remote |
fnm list-remote |
| 卸载某版本 | nvm uninstall 18 |
fnm uninstall 18 |
| 查看当前版本 | nvm current |
fnm current |
| 执行指定版本的命令 | nvm exec 18 node app.js |
fnm exec --using=18 node app.js |
| 跑一次性命令 | nvm run 18 --version |
fnm run --using=18 --version |
可以看到,命令几乎是一一对应的,记忆成本极低。
六、完整迁移流程(nvm → fnm)
Step 1:记录当前 nvm 管理的版本
迁移前先摸清家底,避免遗漏:
bash
nvm ls
记下你实际在用的版本(比如 16.20.2、18.20.4、20.18.0),以及默认版本。
Step 2:安装 fnm
按上一节的步骤安装并配置好 shell 集成,确认 fnm --version 正常输出。
Step 3:用 fnm 重新安装所需版本
bash
# 安装你记录下来的版本(支持模糊匹配,自动装该大版本最新)
fnm install 16
fnm install 18
fnm install 20
# 安装最新 LTS 版本
fnm install --lts
# 设置默认版本(相当于 nvm alias default)
fnm default 20
fnm 的版本号支持模糊匹配:
fnm install 18会自动装 18.x 的最新版;--lts则安装当前最新的 LTS 版本。
Step 4:迁移 .nvmrc 习惯
好消息:fnm 完全兼容 .nvmrc ,你项目里现有的 .nvmrc 文件无需任何修改。
只要开启了 --use-on-cd,cd 进项目目录就会自动 fnm use 对应版本。你也可以手动触发:
bash
fnm use # 读取当前目录的 .nvmrc / .node-version
fnm install # 读取 .nvmrc 并安装对应版本(首次进入新项目时很方便)
Step 5:确认全局 npm 包
注意:fnm 和 nvm 的版本目录结构不同,全局包不会自动继承。 迁移后需要在每个版本下重装你依赖的全局工具(如 pnpm、yarn、tsx、nodemon 等):
bash
fnm use 20
npm install -g pnpm yarn tsx
如果你全局包很多,建议先在 nvm 下导出清单:
bash
nvm use 20
npm ls -g --depth=0 > global-packages.txt
再用脚本批量重装。
Step 6:卸载 nvm(可选)
确认 fnm 一切正常后,可以清理 nvm:
1. 从 shell 启动脚本中移除 nvm 配置
编辑 ~/.bashrc / ~/.zshrc,删除或注释掉以下类似行:
bash
# export NVM_DIR="$HOME/.nvm"
# [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
# [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
2. 删除 nvm 目录
bash
rm -rf "$NVM_DIR"
# 通常是
rm -rf ~/.nvm
Windows(nvm-windows) :通过"添加或删除程序"卸载,再删除 %NVM_HOME% 与 %NVM_SYMLINK% 指向的目录,并清理环境变量 NVM_HOME、NVM_SYMLINK。
Step 7:验证迁移成功
bash
# 确认 fnm 生效、node 来源正确
which node # 应指向 fnm 管理的路径(如 ~/.fnm/...)
node -v
fnm current
fnm list
# 测试自动切换:进入一个有 .nvmrc 的项目
cd your-project/
fnm current # 应自动切到 .nvmrc 指定的版本
七、迁移决策建议
#mermaid-svg-iF8NY1XpNuRs1HIP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-iF8NY1XpNuRs1HIP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-iF8NY1XpNuRs1HIP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-iF8NY1XpNuRs1HIP .error-icon{fill:#552222;}#mermaid-svg-iF8NY1XpNuRs1HIP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-iF8NY1XpNuRs1HIP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-iF8NY1XpNuRs1HIP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-iF8NY1XpNuRs1HIP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-iF8NY1XpNuRs1HIP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-iF8NY1XpNuRs1HIP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-iF8NY1XpNuRs1HIP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-iF8NY1XpNuRs1HIP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-iF8NY1XpNuRs1HIP .marker.cross{stroke:#333333;}#mermaid-svg-iF8NY1XpNuRs1HIP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-iF8NY1XpNuRs1HIP p{margin:0;}#mermaid-svg-iF8NY1XpNuRs1HIP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-iF8NY1XpNuRs1HIP .cluster-label text{fill:#333;}#mermaid-svg-iF8NY1XpNuRs1HIP .cluster-label span{color:#333;}#mermaid-svg-iF8NY1XpNuRs1HIP .cluster-label span p{background-color:transparent;}#mermaid-svg-iF8NY1XpNuRs1HIP .label text,#mermaid-svg-iF8NY1XpNuRs1HIP span{fill:#333;color:#333;}#mermaid-svg-iF8NY1XpNuRs1HIP .node rect,#mermaid-svg-iF8NY1XpNuRs1HIP .node circle,#mermaid-svg-iF8NY1XpNuRs1HIP .node ellipse,#mermaid-svg-iF8NY1XpNuRs1HIP .node polygon,#mermaid-svg-iF8NY1XpNuRs1HIP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-iF8NY1XpNuRs1HIP .rough-node .label text,#mermaid-svg-iF8NY1XpNuRs1HIP .node .label text,#mermaid-svg-iF8NY1XpNuRs1HIP .image-shape .label,#mermaid-svg-iF8NY1XpNuRs1HIP .icon-shape .label{text-anchor:middle;}#mermaid-svg-iF8NY1XpNuRs1HIP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-iF8NY1XpNuRs1HIP .rough-node .label,#mermaid-svg-iF8NY1XpNuRs1HIP .node .label,#mermaid-svg-iF8NY1XpNuRs1HIP .image-shape .label,#mermaid-svg-iF8NY1XpNuRs1HIP .icon-shape .label{text-align:center;}#mermaid-svg-iF8NY1XpNuRs1HIP .node.clickable{cursor:pointer;}#mermaid-svg-iF8NY1XpNuRs1HIP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-iF8NY1XpNuRs1HIP .arrowheadPath{fill:#333333;}#mermaid-svg-iF8NY1XpNuRs1HIP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-iF8NY1XpNuRs1HIP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-iF8NY1XpNuRs1HIP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iF8NY1XpNuRs1HIP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-iF8NY1XpNuRs1HIP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iF8NY1XpNuRs1HIP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-iF8NY1XpNuRs1HIP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-iF8NY1XpNuRs1HIP .cluster text{fill:#333;}#mermaid-svg-iF8NY1XpNuRs1HIP .cluster span{color:#333;}#mermaid-svg-iF8NY1XpNuRs1HIP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-iF8NY1XpNuRs1HIP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-iF8NY1XpNuRs1HIP rect.text{fill:none;stroke-width:0;}#mermaid-svg-iF8NY1XpNuRs1HIP .icon-shape,#mermaid-svg-iF8NY1XpNuRs1HIP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iF8NY1XpNuRs1HIP .icon-shape p,#mermaid-svg-iF8NY1XpNuRs1HIP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-iF8NY1XpNuRs1HIP .icon-shape .label rect,#mermaid-svg-iF8NY1XpNuRs1HIP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iF8NY1XpNuRs1HIP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-iF8NY1XpNuRs1HIP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-iF8NY1XpNuRs1HIP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
Windows
macOS / Linux
是
否
是
否
你需要 Node 版本管理器吗?
主要平台?
✅ 强烈推荐 fnm
原生支持,告别 nvm-windows
痛点是 shell 启动慢
或自动切换失灵?
✅ 推荐迁移 fnm
团队/教程都强绑 nvm?
⚠ 可暂留 nvm
但 fnm 完全兼容 .nvmrc
📦 开始迁移
一句话总结:
- 如果你在 Windows 上 ,或受够了 shell 启动慢 / 自动切换难用 ,迁移到 fnm 收益巨大,且
.nvmrc零成本兼容。 - 如果你深度依赖 nvm 的某些边缘特性 、或团队强制统一,fnm 也能和平共处(两者可并存,只要 shell 启动脚本里只
eval一个)。
八、常见坑与 FAQ
Q1:迁移后 node 命令找不到了?
A:99% 是 shell 集成没配好。确认启动脚本里有 eval "$(fnm env --use-on-cd)",并重启终端。用 which node 检查路径是否在 ~/.fnm 下。
Q2:fnm 和 nvm 能同时装吗?
A:能装,但同一个 shell 会话里只应激活一个 。如果 node 路径混乱,检查启动脚本里是否同时 source 了 nvm 和 eval 了 fnm,注释掉其一即可。
Q3:--use-on-cd 没生效?
A:确认配置时带了 --use-on-cd 参数(不是只写 fnm env)。另外,离开目录时 fnm 默认切回 fnm default 设的版本,记得先 fnm default 20 设一个默认值。
Q4:CI 环境怎么用 fnm?
A:GitHub Actions 官方有 Schniz/fnm action,或用 fnm install && fnm use 两行搞定,比 nvm 快很多。
Q5:fnm 安装包从哪下载?会不会很慢?
A:fnm 从 Node 官方源下载 Node 二进制。国内可通过设置镜像加速:
bash
export NODE_DIST_URL=https://npmmirror.com/mirrors/node
fnm install 20
九、参考链接
- fnm 官方仓库:https://github.com/Schniz/fnm
- fnm 官方文档:https://fnm.vercel.app
- nvm 官方仓库:https://github.com/nvm-sh/nvm
- nvm-windows:https://github.com/coreybutler/nvm-windows
写在最后 :版本管理器是开发体验里"基础设施级"的工具,换一个更快的,每天能省下的等待时间是复利。fnm 用 Rust 把这件事做到了近乎完美------跨平台、快、兼容 .nvmrc,迁移成本远小于你想象。如果你还在用 nvm,不妨今晚就试试。