NVM 使用指南:多版本 Node 安装、切换与排障
一台机器上装一个 Node,写一个项目够用;同时维护两三个项目,版本很快就会打架。老项目要求旧版 Node 才能跑,新项目又要用新版的特性;系统里全局装的 Node 只有一个,升级就意味着所有项目一起跟着动。nvm(Node Version Manager)就是为了解决这个局面:它把 Node 装在用户自己的目录里,一个版本一套独立的目录,切换版本只是换一条路径,不动系统,不用管理员权限。
nvm 到底解决什么问题
先把问题说具体。没有版本管理工具时,常见的困境有四种。
第一是项目之间的版本冲突。老项目依赖的构建工具只在旧版 Node 上能跑,新项目又要用新版才有的功能,来回卸载重装既费时间又容易留残留。
第二是全局包互相干扰。系统级安装的 Node 把全局包都堆在同一个目录里,两个项目需要同一个工具的不同版本时只能二选一,删错一个,另一个跟着出问题。
第三是权限问题。系统目录的写入需要管理员权限,装全局包时动不动就要加 sudo,时间一长,各处混着不同属主的文件,清理起来很麻烦。
第四是回退困难。升级之后发现新版有问题,想回到旧版,往往要重新下载安装,还要凭记忆恢复之前装过的全局包。
nvm 的做法是把这些问题一次性绕开:每个 Node 版本连同它自带的 npm 一起装在用户主目录下的独立目录里,版本之间互不干扰;当前 shell 用哪个版本,由 nvm 动态调整 PATH 决定;换版本不需要卸载重装,装过的版本一直都在,随时可以切回去。理解这一点,后面所有命令的逻辑都顺了:nvm 不是在“升级 Node”,而是在管理“好几套 Node,哪套生效由你说了算”。
有两点先讲清。第一,nvm 管理的是 Node 本身,不是 npm 包;npm 跟着每个 Node 版本走,切换版本,全局包也会跟着换一套。第二,Windows 上用的 nvm-windows 是另一个独立实现,命令和行为有差异,后面单独讲。
安装:先分清你的系统
装 nvm 之前,先确认两件事:你的 shell 是哪一种,以及机器上是否已经装了系统级的 Node。shell 的类型决定了配置写进哪个文件:macOS 默认是 zsh,Linux 上常见 bash,也有用 fish 的;把配置写错文件,nvm 装好了也找不到命令。已经装了系统级 Node 也不用慌,nvm 装好之后可以用 nvm use system 临时切回系统那个版本,两者可以共存,不必先把旧的删干净。
macOS:Homebrew 安装
用 Homebrew 是 macOS 上最省事的路。装完之后要做两件事:建好 nvm 的工作目录,并把加载 nvm 的命令写进 shell 配置文件。
brew install nvm
mkdir ~/.nvm然后把下面几行追加到 shell 配置文件里。macOS 默认 shell 是 zsh,文件就是 ~/.zshrc;如果你用的是 bash,对应文件是 ~/.bash_profile 或 ~/.bashrc,按自己的 shell 来,不要照抄。
export NVM_DIR="$HOME/.nvm"
[ -s "/opt/homebrew/opt/nvm/nvm.sh" ] && \. "/opt/homebrew/opt/nvm/nvm.sh"
[ -s "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" ] && \. "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm"这里有个容易踩的坑:Apple 芯片的 Mac 上 Homebrew 装在 /opt/homebrew,而 Intel 芯片的 Mac 上在 /usr/local。上面写的是 Apple 芯片的路径;Intel 的机器要把路径里的 /opt/homebrew 换成 /usr/local。写完之后重新加载配置文件,或者干脆新开一个终端窗口,再运行 nvm --version,能打出版本号就是装好了。
Homebrew 装的 nvm,升级跟着 Homebrew 走(brew upgrade nvm),已装好的 Node 版本不受影响。
macOS 与 Linux:官方脚本安装
不走 Homebrew 的话,可以用官方仓库提供的安装脚本。这条路在 macOS 和 Linux 上都适用,适合没有包管理器或者想跟官方走得更近的人。
做法是到 nvm 的官方仓库(nvm-sh/nvm)README 里复制当时的安装命令执行。之所以不建议把某一条具体的安装命令背下来,是因为脚本地址里带的版本号会变,照抄旧文章里的命令可能拉到一个过时的版本;以仓库 README 为准,永远是最新的。脚本做的事情和手动安装一样:把 nvm 下载到 ~/.nvm,并往 shell 配置文件里追加加载命令。执行完之后同样要重新加载配置文件,或者新开终端,再用 nvm --version 验证。
官方脚本安装和 Homebrew 安装在日常使用上没有区别,区别只在升级方式:脚本装的 nvm,升级时重新跑一次安装脚本即可;它不会动你已经装好的 Node 版本。
Windows:nvm-windows 是另一个工具
Windows 上没有原生的 nvm,要用 nvm-windows。它由另外的作者维护,名字相近但实现不同,安装方式是下载安装包或者用 Scoop、Chocolatey 这类包管理器安装,装完之后在命令行里用 nvm version 验证。
命令上大部分是相通的,但有两处差异值得注意。第一,查看已安装版本的命令是 nvm list 而不是 nvm ls,虽然它也接受 ls 作为别名,但文档里写的是 list。第二,nvm-windows 对 .nvmrc 的支持和原版 nvm 不一样,项目级自动切换那一套在 Windows 上不能直接照搬,后面讲 .nvmrc 时会再提一句。Windows 用户如果发现某条命令在 nvm-windows 里不存在,先查 nvm-windows 自己的文档,不要默认原版 nvm 的命令全都通用。
装完先验证,再装 Node
不管走哪条路,装完先验证两件事:nvm --version 能输出版本号,nvm ls 能正常列出内容(刚装好时可能是空列表)。这两步过了再装 Node;很多人“装不上 Node”的问题,根子上其实是 nvm 没加载成功。
日常使用:安装、切换、默认版本
nvm 的日常命令不多,核心就四个:装、切、看、设默认。把这四个摸熟,日常开发基本够用。
安装 Node 版本
装 Node 有三种常见写法,分别对应三种需求。
nvm install --lts
nvm install node
nvm install <版本号>--lts 装的是长期支持版,适合大多数项目和生产环境,更新节奏稳,支持周期长。node 装的是当前最新版,适合想尝鲜或者做兼容性测试的人。指定版本号则装某一个确切的版本,适合老项目有明确要求的时候。版本号可以只写主版本号,比如只写大版本数字,nvm 会装该大版本线上的最新版;也可以写完整版本号,精确到补丁号。项目文档里如果写了“需要 Node 某大版本”,写大版本数字就够了,不必精确到补丁号。
装的时候还有两个常用选项。第一个是装新版本的同时把旧版本的全局包带过来:
nvm install <新版本号> --reinstall-packages-from=<旧版本号>这条命令先装新版 Node,再把旧版全局包目录里的包重新装到新版下面。迁移不是复制文件,而是重新安装,所以如果某个包的新版和旧版 Node 不兼容,迁移过程会报错,这反而是好事,说明这个包需要你手动处理,而不是悄悄带过来一个坏掉的包。
第二个是查看远端都有哪些版本可以装:
nvm ls-remote --lts不加 --lts 会列出全部历史版本,列表很长;日常记住三句就够了:生产用长期支持版,尝鲜用最新版,老项目按要求装指定版本。
切换版本
切换是 nvm 最常用的操作,命令和安装对应:
nvm use --lts
nvm use node
nvm use <版本号>
nvm use systemnvm use system 是切回系统里原来装的那个 Node,适合临时要用系统 Node 的场景。切换只对当前终端窗口生效,新开一个终端窗口还是默认版本。这个特性一开始会让人困惑:明明刚才切过版本,新开窗口又回去了。这不是 bug,而是 nvm 的设计:切换是 shell 会话级别的,想要每次打开终端都是某个版本,要用下面说的默认版本。
切换之后顺手验证一下,养成习惯能省很多排查时间:
node --version
npm --version两个命令的输出应该和你刚切的版本对应得上。如果 node 的版本对了,npm 的版本却不对,或者 node 命令根本找不到,说明切换没有真正生效,问题一般出在 shell 配置或者 PATH 上,后面排障一节会讲。
设置默认版本
默认版本就是每次新开终端时自动生效的版本,设置一次长期有效:
nvm alias default <版本号>
nvm alias default lts/*lts/* 的意思是“长期支持版里最新的那一个”,用它做默认版本的好处是:以后装了新的长期支持版,默认版本会自动跟进,不用每次手动改。查看当前所有别名用 nvm alias,不想要了用 nvm unalias <别名> 删掉。
默认版本和当前版本是两回事,刚开始容易混淆。nvm use 改的是当前窗口,关掉就没了;nvm alias default 改的是新窗口的起点。两者配合才是完整工作流:默认版本定一个常用的,临时需要别的版本时在当前窗口里切,用完关掉窗口,下次打开还是默认版本。
项目级锁定:.nvmrc
团队协作时,口头约定“这个项目用某版 Node”是靠不住的,总有人装错。.nvmrc 就是把约定写进文件:项目根目录放一个 .nvmrc 文件,里面只写一行,内容就是这个项目要用的版本。可以写完整版本号,也可以写大版本数字,或者写 lts/* 表示长期支持版里最新的。
echo "lts/*" > .nvmrc进了项目目录之后,运行一次不带参数的 nvm use,nvm 会自动读取 .nvmrc 里的版本并切换过去;如果那个版本还没装,nvm 会提示你先装。把 .nvmrc 提交进版本控制,团队成员和持续集成环境都能用同一个版本,版本不一致导致的“在我机器上能跑”会少很多。
更进一步,可以让 shell 在切换目录时自动执行 nvm use。zsh 的做法是在配置文件里加一个钩子函数:每次切换目录时检查往上有没有 .nvmrc,有就按文件内容切换,没有就不动。这个钩子在 nvm 官方文档里有现成的写法,bash 也有对应的实现,原理一样。自动切换虽然方便,但每次进目录都会多一点延迟,而且 .nvmrc 里的版本如果还没装,钩子会直接帮你装,第一次进目录时可能要等上一会儿。是否启用看个人偏好,手动 nvm use 也完全够用。
这里要提醒 Windows 用户一句:nvm-windows 对 .nvmrc 的支持有限,自动切换那套在 Windows 上不要直接照搬,先确认你用的 nvm-windows 版本支不支持,再决定工作流。
全局包跟着版本走
每个 Node 版本都有自己独立的全局包目录,切换版本,全局包也跟着换:在 A 版本下全局装的工具,切到 B 版本就找不到了,这不是丢了,只是当前版本下没有,需要再装一次;反过来,不同项目用不同版本时,全局包天然隔离,不会互相污染。
查看当前版本的全局包用 npm list -g --depth=0,只列顶层包,不展开依赖。迁移除了安装时带 --reinstall-packages-from,也可以事后补一条 nvm reinstall-packages <旧版本号>。迁移前先看一眼旧列表,把不再需要的剔掉。另外一个习惯:能局部装的包尽量局部装,写进 package.json,用 npx 运行;全局包只留确实跨项目通用的工具,这样换版本时要迁移的包就很少。
排障:按现象定位
nvm 出问题时,报错信息往往不太直观。按现象定位比逐条翻文档快。下面四种是最常见的现象,每种先讲最可能的原因,再讲查法。
现象一:nvm 命令找不到
终端里输入 nvm 回车,得到 command not found,这是最常见的新手问题,根子几乎都在 shell 配置上。
先查 nvm 的目录在不在:
ls ~/.nvm/nvm.sh文件存在,说明 nvm 装好了,只是当前 shell 没加载它。接着查配置文件里有没有加载命令:打开你的 shell 配置文件(zsh 是 ~/.zshrc,bash 是 ~/.bashrc 或 ~/.bash_profile),看里面有没有 NVM_DIR 和加载 nvm.sh 的那两行。没有就补上,有就检查路径对不对:Apple 芯片和 Intel 芯片的 Homebrew 路径不一样,路径写错是最常见的栽跟头。
文件不在,说明安装本身没完成,重新跑一次安装流程:Homebrew 装的,先确认 brew list 里有没有 nvm;脚本装的,确认 ~/.nvm 目录存在。还有一种情况:配置文件改对了,但当前终端窗口是改之前打开的,配置还没生效,新开一个窗口再试。
fish 用户要特别注意:nvm 是为 bash 和 zsh 写的,fish 语法不一样,直接照搬配置行不通,要用社区提供的封装,或者改用 bash/zsh 做开发 shell。
现象二:安装 Node 失败
nvm install 跑到一半报错,先看报错出现在哪个阶段,阶段不同,原因完全不同。
如果报错和网络有关,比如下载超时、校验失败,先查网络:能不能正常访问 Node 的官方下载站;如果在国内网络环境下直连很慢,可以设置镜像站。nvm 支持用环境变量指定镜像地址,把它写进 shell 配置文件,之后所有下载都走镜像:
export NVM_NODEJS_ORG_MIRROR=https://npmmirror.com/mirrors/node写完之后重新加载配置文件再试。镜像地址本身以镜像站的官方说明为准,上面这个是国内常用的一个,换别的镜像站也是同样的写法。
如果报错和架构有关,比如在 Apple 芯片的 Mac 上装旧版 Node 失败,原因往往是旧版没有提供对应芯片的原生二进制包,nvm 会转而尝试从源码编译,而编译需要一整套工具链,缺了就会失败。处理办法是优先选有原生包的版本;项目确实要求旧版时,先把编译工具装齐再重试,并且做好花不少时间的准备。报错里提到架构或者编译字眼的,基本就是这一类。
如果报错说某个版本不存在,先确认版本号写得对不对。nvm ls-remote 查一下远端到底有没有这个版本,版本号里的字母和点写错一位,nvm 是不会智能纠错的,只会告诉你找不到。
现象三:切换不生效
nvm use 执行完没有报错,但 node --version 还是旧的,或者干脆提示找不到 node。这种情况一般是 PATH 被别的配置覆盖了。
查法是从外到内。先看当前 shell 里 nvm 管理的 Node 目录在不在 PATH 里:
echo $PATH输出里应该有一段指向 nvm 目录下某个版本的 bin 目录。如果没有,说明 nvm 的加载顺序有问题:shell 配置文件里可能有别的工具在 nvm 之后又改写了 PATH,把 nvm 的路径挤掉了。把 nvm 的加载命令移到配置文件的靠后位置,或者检查是不是装了别的 Node 版本管理工具,两者同时生效时 PATH 很容易打架。
如果 PATH 里有 nvm 的路径,但 node 还是旧的,看是不是系统级 Node 的路径排在 nvm 前面。PATH 按顺序查找,排在前面的先生效;系统 Node 的路径通常在 /usr/local/bin,排在前面时输入 node 就会先命中它。解决办法是调整顺序,或者用 nvm use system 明确切到系统版本,再用 nvm use <版本号> 切回来,看切换本身是否正常。
还有一种容易被忽略的情况:当前目录下有 .nvmrc,而 shell 里配了自动切换钩子,进目录时钩子自动切到了文件里写的版本,覆盖了你手动切的版本。表现就是“刚切完,进个目录又回去了”。这不是故障,是钩子在工作;要确认是不是这个原因,进目录前后各跑一次 node --version 对比一下就清楚了。
现象四:终端重启后版本丢了
新开终端,Node 版本回到默认版本,昨天切的版本没了。这是 nvm 的设计:nvm use 只对当前窗口生效。想要的效果分两种:每次打开终端都是某个固定版本,用 nvm alias default 设默认版本;进项目目录自动切到项目版本,用 .nvmrc 加自动切换钩子。两种可以并存:默认版本管新开窗口的起点,.nvmrc 管进项目目录之后的切换。
还有一种“丢版本”其实是别的问题:终端重启后连 nvm 命令都找不到了。那不是版本丢了,是 nvm 本身没加载,回到现象一的查法。
权限问题:不要动不动就 sudo
nvm 的设计目标之一就是让 Node 的安装和使用都不需要管理员权限。如果在用 nvm 的过程中遇到权限报错,先停下来想一想:是不是在对系统目录操作。
最常见的权限报错来自两种误操作。一种是之前用系统方式装过 Node 和全局包,那些文件属主是管理员,nvm 想读写就会报权限不足。解决办法不是加 sudo 硬来,而是把两套分开:nvm 的版本和包都在用户主目录下,互不干扰;系统级残留如果不再需要,按系统包管理器的正常流程清掉,不要手动乱删系统目录。
另一种是 ~/.nvm 目录本身的属主被改坏了,比如曾经用 sudo 跑过 nvm 命令,导致目录里混进了属主为管理员的文件。查法是看目录属主:
ls -ld ~/.nvm
ls -l ~/.nvm/versions/node/ 2>/dev/null | head如果属主不是你自己的账号,把属主改回来,之后不要再对 nvm 的目录用 sudo。nvm 的一切操作都应该以普通用户身份跑,需要 sudo 才能跑通的操作,往往说明路径或者属主已经错了,sudo 只是把问题盖住,不是修好。
排障小清单
出问题时按这个顺序过一遍,大部分情况在前三步就能定位:
- 先确认 nvm 本身加载成功:
nvm --version有输出,ls ~/.nvm/nvm.sh文件存在 - 再确认目标版本真的装好了:
nvm ls里能看到它,node --version和切换目标一致 - 然后看 PATH:
echo $PATH里有没有 nvm 的版本目录,系统 Node 的路径是不是排在前面 - 再看目录因素:当前目录有没有
.nvmrc,有没有自动切换钩子在干预 - 最后看权限:报错是不是因为动了系统目录或者
~/.nvm属主不对,不要用 sudo 硬闯
收尾:留一套干净的版本管理习惯
nvm 的命令不多,难的是养成配套的习惯,归拢成三条:每个项目放一个 .nvmrc 并提交进版本控制,团队成员和持续集成都不用再猜这个项目要哪个 Node;默认版本设一个长期支持版,临时需要别的版本时在当前窗口切,用完关掉窗口,日常起点永远是稳定的;定期清理不再用的版本,留着两三个常用的就够了:
nvm uninstall <版本号>删之前确认一下:当前窗口是不是正在用它,默认版本是不是指向它,别的项目有没有 .nvmrc 写了它。删错了也不用慌,重新装一次就行。升级节奏上,长期支持版出新的大版本时,先在非关键项目上试,再切主力项目;老项目的 .nvmrc 不要跟着追新,除非项目本身要升级依赖。版本管理工具的意义不是让你永远用最新的,而是让你在需要的时候,能准确回到任何一个曾经用过的版本。