说个很多同行都遇到过的场景:接手一个旧项目,npm install直接报一堆gyp编译错误,查半天发现项目锁在 Node 16,而你机器上装的是 Node 22;换个新项目又要求 Node 20 以上,于是你开始卸载重装、改 PATH、清理残留,一个下午就这么没了。nvm(Node Version Manager)就是专门解决这件事的工具——它让同一台机器上并存多个 Node 版本,用一条命令切换,互不干扰。这篇内容我把 Windows 和 macOS 两条路线完整走一遍,从下载、安装、shell 配置到日常使用和故障排查,全部摊开讲。适合三类人看:刚接触 Node 生态、机器上只装过一个版本的新手;需要同时维护多个老项目的中高级开发;以及负责给团队统一环境的角色。装过但总觉得"有点玄学"的同学,也建议往下看,很多坑其实都有明确原因。
1. 为什么值得花十分钟把 nvm 装明白
1.1 多版本共存这件事,绕不过去
Node 的版本迭代速度摆在那儿,每年两个大版本,LTS 长期维护版也有明确的起止时间。企业项目为了省心,通常锁在一个 LTS 上不动;个人折腾新特性又会装最新版。这两类需求在一台电脑上同时存在,靠手动卸载重装是撑不住的——不同版本的 npm 全局包互相覆盖、原生模块(比如一些需要编译的 C++ 扩展)缓存错乱、node_modules里锁死的二进制文件对不上 ABI,都是真实会炸的点。
nvm 的思路很朴素:每个 Node 版本单独一个目录,切换时只改当前 shell 的PATH,让系统优先找到目标版本的bin目录。所有版本的文件都在,只是"当前生效"的那个变了。理解这句话,后面很多现象你就能自己推出来——比如为什么切换是瞬间完成的、为什么全局包装在某个版本下换个版本就看不见了、为什么开新终端窗口版本又变了。
1.2 nvm 其实是两个不同的东西,别装错
这是最容易踩的第一个坑。你在 macOS 或 Linux 上搜到的 nvm,和 Windows 上用的 nvm,不是一个代码库。
| 对比项 | POSIX 版 nvm | Windows 版 nvm-windows |
|---|---|---|
| 实现语言 | Shell 脚本 | Go 编译的可执行文件 |
| 运行方式 | 必须被source加载进当前 shell | 独立 exe,直接命令行调用 |
| 版本切换机制 | 改环境变量PATH | 改一个软链接指向 |
| 常用命令 | nvm ls、nvm install、nvm use | nvm list、nvm install、nvm use |
.nvmrc支持 | 原生支持,可做自动切换 | 较新版本支持读取,但行为不完全一致 |
| 卸载方式 | 删目录 + 删配置行 | 走卸载程序,还得清理软链接和 PATH |
命令看着像,底层差得远。所以你在网上抄到一段curl一行安装的命令,拿到 Windows 的 PowerShell 里执行是没用的;反过来把nvm-setup.exe的教程套到 macOS 上也不行。先把这条分清,能省掉后面一半的困惑。
1.3 动手前先做三件小事
第一件,清点现有的 Node。macOS 上执行which -a node,Windows 上在 PowerShell 里执行Get-Command node -All。如果输出指向/usr/local/bin/node或者C:\Program Files\nodejs\node.exe,说明你之前用官方安装包装过,这个版本会和 nvm 抢 PATH。建议先卸干净:官方安装包装的版本有专门的卸载脚本或卸载入口,别直接删目录了事。
第二件,确认 shell 类型。macOS 从 Catalina 起默认是 zsh,配置文件是~/.zshrc;如果你手动改过用 bash,那就是~/.bash_profile或~/.bashrc。确认命令是echo $SHELL。写错文件是"装完 nvm 命令找不到"的头号原因。
第三件,给安装目录选个干净的路径。~/.nvm这个默认位置就很好。特别提醒一句:不要把 nvm 目录放在任何同步盘的同步范围内,那些工具会在后台不停扫描文件、加锁,node_modules动辄几万个文件,同步冲突和权限错误会没完没了。
2. macOS 上的安装:脚本、配置、验证一条龙
2.1 三种装法怎么选,我为什么推荐脚本
macOS 上主流有三条路:官方提供的一键安装脚本、包管理器安装、手动克隆仓库。三者都能用,但适用场景不一样。
官方脚本是作者维护的主要分发方式,它会自动把仓库克隆到~/.nvm,往你的 shell 配置文件里追加加载语句,顺手装好命令补全。优点是版本和目录结构都是"标准答案",后面查文档、看报错都对得上。包管理器装的版本,装完不会自动写配置,还得自己补;而且它管的 nvm 和它管的 node 之间偶尔会打架,node命令的优先级容易乱。手动克隆最灵活,适合你想锁死某个提交、或者机器上完全没法联网跑脚本的情况,代价是补全和配置都得手工来。
我一般推荐脚本安装,除非你在受限网络环境里连脚本都拉不下来。下面重点讲这条。
2.2 一键脚本安装的完整过程
先把脚本内容下载下来看一眼,再执行,这是好习惯——不是多疑,是这类curl | bash的东西你至少要知道它动了哪些文件。
# 第一步:只下载,不执行,先看内容 curl -o- <官方仓库中对应版本的 install.sh 地址> > /tmp/nvm-install.sh less /tmp/nvm-install.sh # 第二步:确认无误后执行本地这份 bash /tmp/nvm-install.sh地址从官方仓库的发布页面上找,选最新的版本标签,拼成install.sh的完整路径。脚本干的事大概分四步:git clone仓库到$NVM_DIR(默认~/.nvm)、检测当前 shell 类型、往对应的 profile 文件里追加加载块、尝试加载补全脚本。整个流程几十秒,取决于网络。
脚本支持几个有用的参数,值得记住:
# 装完不自动 use 任何版本(防止刚装完就有个默认版本顶在前面) bash install.sh --no-use # 指定安装目录 NVM_DIR="$HOME/tools/nvm" bash install.sh # 指定写入哪个配置文件 PROFILE="$HOME/.zshrc" bash install.sh # 完全不写配置文件,只克隆代码 PROFILE=/dev/null bash install.sh--no-use这个参数我强烈建议加上。不加的话,脚本在加载时会尝试用一个默认版本,有时候会把系统里原有的 node 顶掉,排查起来更乱。装的时候干净一点,后面主动权在你手上。
2.3 shell 配置文件里到底要写什么
脚本自动写好的是这么几行,你自己手动补也得是这几行:
export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"这三行的门道不少。-s判断的是"文件存在且大小不为零",比-f更严格一点,防止一次坏的下载留下空文件把整个 shell 搞挂。\.是source的 POSIX 写法,两者等价,\.兼容性更好。&&保证前一个条件不成立就跳过,不会报错刷屏。
最关键的一点:nvm.sh 必须被 source,不能当脚本执行。原因在于 nvm 的全部工作都是修改当前 shell 的环境变量——PATH、NVM_BIN、NVM_INC这些。执行一个脚本会开一个子进程,子进程里改再多变量,退出后对父 shell 没有任何影响,你会看到命令执行"成功"了但nvm就是不认识。这个原理想通了,以后遇到"我明明跑了但还是 command not found",第一反应就该是"我是不是用./nvm.sh跑的"。
另外注意,这段配置要放在 profile 文件的靠后位置。如果你文件里更早的地方有一行把PATH整个重写了(比如某些老教程里写的export PATH=/usr/local/bin:$PATH),nvm 改过的 PATH 会被冲掉。
2.4 验证与第一次版本切换
新开一个终端窗口(或者source ~/.zshrc),依次验证:
command -v nvm # 应该输出 nvm,注意这里必须用 command -v,不能用 which nvm --version # 输出版本号 nvm ls # 列出已安装的版本,刚装完是空的 nvm ls-remote --lts # 列出所有仍在维护的 LTS 版本这里有个新手常掉的坑:which nvm什么都不输出。原因是 nvm 是一个 shell 函数,不是磁盘上的可执行文件,which只找可执行文件。判断 nvm 有没有加载成功,用command -v nvm,或者直接type nvm。
装第一个版本:
nvm install 20 # 装 20 系列的最新版 nvm install --lts # 装最新的 LTS nvm alias default 20 # 把 20 设为新终端默认版本 nvm use 20 node -v # 确认生效 npm -vnvm install 20里的20是个模糊匹配,nvm 会解析成该大版本下最新的小版本。装的时候你能看到它在下载一个.tar.xz包然后解压——这说明 nvm 用的是官方预编译好的二进制,不是本地编译,所以速度取决于网络,整个过程也就十几秒。
有个细节值得注意:nvm 给每个版本配的 npm 是那个 Node 版本发布时自带的版本。Node 20 刚发布时的 npm 可能是 10.0,现在 Node 20 已经更新到 20.1x 了,但如果你装的是早期的具体小版本,npm 也会是老的。想用最新 npm:
nvm install 20 --latest-npm这条会把该版本下的 npm 升到当前最新。但我要提醒一句,npm 大版本升级偶尔会有行为变化,团队项目里别乱升,跟着 lockfile 走更稳。
3. Windows 上的安装:nvm-windows 的取舍与细节
3.1 为什么 Windows 用的是另一个项目
前面说过,POSIX 版 nvm 是一堆 shell 脚本,靠source修改当前进程环境变量,这套机制在 Windows 的 cmd 和 PowerShell 里根本没有对应物。所以 Windows 上需要一个原生程序来做同样的事,nvm-windows 就是这么来的。
它换了个实现思路:不折腾 PATH 的顺序,而是在 nvm 的安装目录里放各个 Node 版本,然后维护一个软链接(symlink),让C:\Program Files\nodejs这个固定路径始终指向当前选中的版本。系统 PATH 里永远只有这一个路径,切换版本就是改软链接的指向。这个设计的好处是 PATH 永远干净,坏处是所有操作都跟文件系统权限挂钩——这也是 Windows 上大部分报错的根源。
3.2 安装向导里的两个路径选项
下载nvm-setup.exe后一路下一步,但中间有两个路径选择必须停下来看一眼。
第一个是nvm 的安装根目录(对应NVM_HOME),默认在用户目录下的AppData\Roaming\nvm。这里会存 nvm 本体和所有下载的 Node 版本,占空间可能好几个 G,如果你的系统盘吃紧,可以换到别的盘。
第二个是Node 软链接路径(对应NVM_SYMLINK),默认C:\Program Files\nodejs。这个路径就是系统 PATH 里出现的那一个。
这两处有个共同禁忌:不要带空格,不要带中文。C:\Program Files\nodejs里的空格是官方默认值,能跑,但你在命令行里手敲路径、写脚本、配 CI 的时候就得处处加引号。我一般建议把软链接改到C:\nodejs这种干净路径,一劳永逸。中文路径的问题更直接,某些老工具链读路径会乱码。
权限这块要提前想清楚。C:\Program Files是受保护目录,创建和修改软链接需要管理员权限。也就是说在默认配置下,你每次nvm use都得以管理员身份开终端。要么改路径避开受保护目录,要么接受这个约束。
还有一个高频翻车点:装之前先把原有的 Node 卸干净。官方安装包装的 Node 会占着C:\Program Files\nodejs这个位置,nvm 安装时要么报目录已存在,要么装完了两个东西互相覆盖。正确顺序是:控制面板卸载 Node → 手动确认C:\Program Files\nodejs目录已经删掉 → 检查系统环境变量里有没有残留的 Node 路径 → 再装 nvm。
3.3 装完先别急着用:settings 与镜像配置
nvm-windows 的行为由安装目录下的settings.txt控制,装完第一件事就是打开它看看。里面通常长这样:
root: C:\Users\<你的用户名>\AppData\Roaming\nvm path: C:\nodejs node_mirror: <Node 二进制包的分发地址> npm_mirror: <npm 包的分发地址>root和path就是前面那两个路径。下面两行是下载源,如果你所在网络拉官方源速度不理想,可以换成任意一个你能正常访问的公共镜像地址。较新版本也支持用命令改,不用手编辑文件:
nvm node_mirror <镜像地址> nvm npm_mirror <镜像地址>改完记得nvm root确认一下当前生效的目录,别改错文件了。如果你机器上装了多个 nvm 副本(比如以前装过没卸干净),这种错位会非常迷惑。
配置完,装第一个版本:
nvm install lts nvm use 20.11.1 node -v npm -v注意 Windows 版的nvm install lts装的是"当前最新 LTS",但nvm use的时候必须给完整版本号,nvm use 20这种模糊写法在部分版本上不被接受。装完先nvm list看一眼完整版本号,再 use,能少一次报错。
3.4 验证清单与首个 Node 版本落地
装完跑一遍这几条,全部通过再开始干活:
| 检查项 | 命令 | 预期结果 |
|---|---|---|
| nvm 是否可用 | nvm version | 输出类似 1.1.x |
| 版本列表是否正常 | nvm list | 列出已装版本,当前版本带* |
| 远程版本能否拉到 | nvm list available | 输出一张版本表 |
| 软链接是否建成 | nvm use <完整版本号> | 输出Now using node vX.Y.Z |
| Node 是否生效 | node -v | 输出对应版本 |
| npm 是否可用 | npm -v | 输出 npm 版本 |
| 全局包路径是否正确 | npm config get prefix | 应指向 nvm 下的对应版本目录 |
最后一条特别重要。如果npm config get prefix输出的不是 nvm 版本目录,而是类似C:\Users\xxx\AppData\Roaming\npm这种东西,说明你的.npmrc里有一行prefix=配置在捣鬼。这个配置会让所有全局包装到同一个公共位置,nvm 的版本隔离就形同虚设了——你在 Node 18 下装的全局 CLI 工具,切到 Node 22 还在,但它可能根本跑不起来。把这行删掉。
4. 日常操作:把版本切换变成肌肉记忆
4.1 高频命令速查
真正每天在用的就那么几条,我把两个平台的差异标出来:
| 用途 | macOS / Linux | Windows |
|---|---|---|
| 装指定版本 | nvm install 18.20.4 | nvm install 18.20.4 |
| 装最新 LTS | nvm install --lts | nvm install lts |
| 切到某版本 | nvm use 18.20.4 | nvm use 18.20.4 |
| 列出已装 | nvm ls | nvm list |
| 列出可装 | nvm ls-remote | nvm list available |
| 看当前版本 | nvm current | nvm list(带*号) |
| 设默认版本 | nvm alias default 18 | nvm alias default 18 |
| 看版本真实路径 | nvm which 18 | nvm root |
| 卸载某版本 | nvm uninstall 18.20.4 | nvm uninstall 18.20.4 |
| 临时关掉 nvm | nvm deactivate | nvm off |
有几个命令值得单独说一下用途。
nvm exec 18 node -v:用指定版本跑一条命令,不切换当前 shell。写脚本的时候很有用,比如你要在 Node 18 下跑一次构建,跑完后面还是原来的版本。
nvm alias除了default,还能自定义名字:nvm alias work 20.11.1,之后nvm use work就行。团队里版本命名统一的话,这套别名挺省事。
nvm unload只影响当前 shell,把 nvm 的函数和变量全清掉,nvm命令立刻消失。做实验前用它比开新窗口快。
4.2 用 .nvmrc 把项目版本钉死
光靠个人记忆切换版本,团队里迟早出事:A 用 18 提交的代码,B 用 22 拉下来跑,某天某个依赖的行为变了,排查两天。解决办法是在项目根目录放一个.nvmrc文件,内容就一行版本号:
18.20.4写大版本号也行(18),nvm 会自动匹配该大版本下最新已装的版本。然后:
nvm use # 不带参数,自动读 .nvmrc nvm install # 不带参数,装 .nvmrc 指定的版本再进一步,让它进目录就自动切换。在~/.zshrc里加一段chpwd钩子:
autoload -U add-zsh-hook load-nvmrc() { local node_version="$(nvm version)" local nvmrc_path="$(nvm_find_nvmrc)" if [ -n "$nvmrc_path" ]; then local nvmrc_node_version=$(nvm version "$(cat "${nvmrc_path}")") if [ "$nvmrc_node_version" = "N/A" ]; then nvm install elif [ "$nvmrc_node_version" != "$node_version" ]; then nvm use fi elif [ "$node_version" != "$(nvm version default)" ]; then nvm use default fi } add-zsh-hook chpwd load-nvmrc load-nvmrc这段逻辑是:进目录时找.nvmrc,找到了但版本没装就自动装,装了但没在用就切过去;没找到.nvmrc就退回默认版本。用 bash 的话把chpwd那套换成PROMPT_COMMAND里调用load-nvmrc。
有个副作用得说清楚:每次cd都可能触发一次版本切换,如果切到的版本缺全局包,你的命令行工具会短暂"消失"。习惯就好,或者只在确实需要的项目里开这个功能。
Windows 上的.nvmrc支持要看版本,较新的 nvm-windows 能在目录里执行nvm use时读取它,但兼容性和 POSIX 版不完全一样,遇到不生效的情况就直接敲完整版本号,别在工具上耗时间。
4.3 全局包的迁移与重装
这是 nvm 用户最常问的问题之一:"我换了版本,pnpm怎么没了?"
因为全局包是装在每个 Node 版本自己的目录里的,路径大概是~/.nvm/versions/node/v20.11.1/lib/node_modules。切到另一个版本,那里是空的,自然没有。这不是 bug,是设计——正是这种隔离保证了版本之间的干净。
但重复装也烦。nvm 提供了迁移命令:
nvm install 20 --reinstall-packages-from=18装 20 的同时,把 18 下所有全局包装过去。已经装好了也能补:
# 先看看 18 下有什么 nvm exec 18 npm ls -g --depth=0 # 再迁移 nvm reinstall-packages 18注意--depth=0,不加这个参数会把整棵依赖树都列出来,几百行刷屏。另外要提醒的是,迁移只搬包不搬配置。那些把状态存在全局包目录里的工具(某些 CLI 的缓存、登录凭据),迁过去可能还得重新登录一次。
还有一种选择是干脆不装全局包。现在主流做法是把 CLI 工具放进项目的devDependencies,用npx或npm exec调用。好处是版本跟项目走、CI 里天然一致、别人克隆下来不用额外配置。我自己现在全局只留两三个真的跨项目高频的东西,其余全下沉到项目里了。
5. 报错排查实录:那些年踩过的坑
5.1 macOS 侧高频故障
装完提示nvm: command not found。九成是配置写错了文件。zsh 环境里你把那三行写进了~/.bash_profile,那当然不生效。正确的位置是~/.zshrc。判断方法:echo $SHELL看当前 shell,ls -la ~ | grep -E "zshrc|bash_profile"看有哪些文件。还有一种可能是配置写对了但当前窗口没重载,source ~/.zshrc或者干脆开新窗口。
nvm ls-remote卡住不动或者超时。这个命令要去远程拉版本清单,网络不理想就会卡。临时方案是换个下载源,通过环境变量指定:
export NVM_NODEJS_ORG_MIRROR=<可用的公共分发地址>写进~/.zshrc里长期生效。注意这个变量只影响 Node 二进制的下载,npm 包的下载源是另一回事,要在npm config set registry里单独设。
node命令指向了系统里的老版本。用which -a node把所有匹配项都列出来,如果/usr/local/bin/node排在~/.nvm/...前面,说明系统那个版本还在抢位置。把/usr/local/bin/node和/usr/local/bin/npm删掉(它们通常是指向../lib/node_modules的软链接),或者用官方卸载脚本清干净。
nvm use报权限错误。千万别加sudo nvm use。nvm 是当前用户级工具,加 sudo 会以 root 身份改环境变量,之后~/.nvm下的文件所有者变成 root,后续所有操作都会因为没权限而失败。真的已经用 sudo 了,用sudo chown -R $(whoami) ~/.nvm修回来。
npm i -g报 EACCES。经典问题。正确解法是回退到用 nvm 管权限,不要去改~/.npmrc里的prefix。那行配置虽然当时能解决报错,但把版本隔离彻底破坏了,后患无穷。检查方法就是前面说的npm config get prefix。
5.2 Windows 侧高频故障
nvm use报exit status 1: Access is denied或者系统错误 5。权限问题,两种解法:以管理员身份运行终端;或者把软链接路径从C:\Program Files\nodejs换到用户目录下,一劳永逸。还有个容易被忽略的原因——软链接目标正被占用。开着编辑器、终端、Docker 里跑着 node 进程,都可能锁着那个目录。把相关进程全关掉再试。
node命令找不到,但 nvm 说 use 成功了。检查顺序:nvm root看 nvm 装在哪儿 → 看那个目录下的settings.txt里path是什么 → 检查系统环境变量PATH里有没有这一条 → 检查环境变量NVM_HOME和NVM_SYMLINK是否都在。改完环境变量必须重开终端,光refreshenv有时候不够。
中文输出乱码。控制台代码页的问题。临时解决chcp 65001切到 UTF-8,长期解决是在系统设置里把"使用 Unicode UTF-8 提供全球语言支持"打开(注意这个选项会影响一些老程序的编码行为,谨慎开)。另一个土办法是把终端换成 Windows Terminal,默认编码处理得好很多。
nvm install下载到一半失败。换镜像源,前面说过改settings.txt或nvm node_mirror。还有一种情况是杀毒软件把下载的压缩包拦了,临时关掉或者加白名单再试。
装完 nvm 发现之前全局装的工具全没了。第一次装 nvm 时把原版 Node 卸了,原版装的那些全局包当然跟着没了。回不去了,重新装吧。列一下清单:npm ls -g --depth=0这是在新环境里查当前有什么,老清单只能靠自己的记忆或者以前的文档。这也是个教训——全局工具清单最好记在一个 dotfiles 仓库里。
5.3 排查速查表
| 现象 | 最可能原因 | 处理动作 |
|---|---|---|
nvm命令不存在 | 配置没加载 / 写错文件 | 检查 profile 文件、重开终端、source一次 |
nvm命令存在但不管用 | 用./nvm.sh执行而不是source | 改成 source 加载 |
切换成功但node -v没变 | PATH 里有更靠前的 node | which -a node找出来删掉 |
| 新终端版本又变回去了 | 没设default别名 | nvm alias default <版本> |
| 全局包换版本就消失 | 版本隔离的正常行为 | 用reinstall-packages迁移 |
npm i -g权限报错 | 全局目录权限 / prefix 被改 | 查npm config get prefix,删掉 prefix 配置 |
| Windows 软链接失败 | 权限不足或目标被占用 | 管理员终端 / 关进程 / 换路径 |
| 下载卡住或超时 | 源不可达 | 换镜像地址 |
| 原生模块编译失败 | Node 版本与模块 ABI 不匹配 | 切到项目要求的版本,清 node_modules 重装 |
6. 一点工程化经验:团队与流水线里怎么用
6.1 团队里怎么把版本约定下来
.nvmrc只是个人便利,光有它不够。真正能约束住团队的是三层配合:.nvmrc给本地用、package.json的engines字段声明版本范围、lockfile 锁住依赖树。三层都对齐了,新人克隆下来才有可能"一次跑通"。
engines字段长这样:
{ "engines": { "node": ">=18.18.0 <21", "npm": ">=9" } }注意它的默认行为只是警告,不会阻止安装。想让它真正拦截,可以配engine-strict=true,但我不太建议一上来就开得这么硬——有些边缘依赖的 engines 声明写得很随意,一开就全卡住了。折中方案是在 preinstall 钩子里自己判断一下版本,给个明确的中文提示。
另外一个实操细节:.nvmrc里写的版本号最好和 CI 里用的完全一致,包括小版本号。写18让 nvm 自己解析,不同人机器上装的最新 18.x 可能不一样,某些依赖的行为在小版本之间确实会变。
6.2 流水线环境的处理
CI 环境里没必要装 nvm。流水线容器每次都是全新的,直接指定版本更直接。常见的两种做法:
一种是用环境自带的多版本管理能力,在流水线的 setup 步骤里声明要哪个版本,跑之前把版本装上并设为当前。
另一种是干脆让容器镜像自带固定版本。这种做法最省心——镜像构建时就决定了版本,运行时没有任何版本切换的变数。缺点是升级版本要重建镜像,周期长一点。
不管哪种,都建议在流水线的第一步加一句node -v打印出来,写进日志。我吃过这个亏:某次本地一切正常,CI 上挂了,查了一下午才发现流水线偷偷换了个默认 Node 版本,跟.nvmrc里写的隔了一个大版本。日志里有一行版本号,两秒就能定位。
还有一种情况要提防:缓存。很多流水线会缓存~/.npm或者node_modules来加速,缓存 key 应当包含 Node 版本号。否则换了版本但缓存没失效,装出来的原生模块跟当前 Node 的 ABI 对不上,报错信息会非常莫名其妙。
6.3 什么时候该考虑换掉 nvm
nvm 不是唯一选择,它胜在资历老、文档多、坑都被人踩过了。但它在两个场景下确实有短板。
一是启动速度。POSIX 版 nvm 是纯 shell 脚本,每次开终端都要执行一遍函数定义,机器慢的时候能感知到几十上百毫秒的延迟。有些新工具用编译语言实现,启动快一个数量级,切换版本也更快。
二是跨平台一致性。前面说了 macOS 和 Windows 用的是两套东西,命令、行为、.nvmrc支持度都有差异。如果团队里两种系统都有,想统一体验的话,找那些用同一份代码支持全平台的工具会省不少沟通成本。还有一类做法是把版本管理下沉到项目层面,结合engines字段自动帮你切,连手动nvm use都省了。
我的态度是:已经装好 nvm 并且用着没问题的,别折腾。工具换来换去的成本远大于那点启动延迟。只有当你在多系统协作、或者终端启动速度真的影响到日常体验时,才值得花半天迁移一次。
6.4 我个人的几条习惯
最后分享几个实操细节,都是吃过亏总结出来的。
~/.nvm目录我会定期看一眼大小,du -sh ~/.nvm。这地方很容易膨胀到十几 G,因为每个版本都带完整的一套 npm 和全局包。长期不用的版本及时nvm uninstall清掉,尤其是那些装过一次就没再用过的奇偶版本。
装新版本之前,先在项目里cat .nvmrc确认一下目标版本,别凭印象敲。我干过好几次"以为项目在 18 上,实际锁的是 16"的事,删了 node_modules 重装半天才发现压根不用切。
还有一条,任何时候都别把 nvm 和别的 Node 管理工具混用在同一台机器上。两个工具互相覆盖 PATH、抢同一个目录,报错信息还互相矛盾,排查成本极高。用哪个就一路用到底,要换就彻底卸干净再换。
真遇到实在搞不定的环境问题,最后一招是去看 nvm 自己的诊断输出。POSIX 版有nvm debug,会把 shell 类型、PATH、各关键环境变量、目录权限全打印出来,绝大多数配置类问题看一眼就清楚了。比在网上翻半天帖子快得多。