☰
Windows下nvm+nodejs+pnpm完整搭建与离线迁移实战
2026/9/29 8:44:32 网站建设 项目流程

最近要帮同事在一台内网机器上把前端开发环境从头搭一遍,正赶上我自己也在筹备换工作机,就顺手把 nvm、nodejs、pnpm 的完整搭建过程和离线迁移流程都验证了一遍。折腾下来最大的感触是:这三样工具单独拿出来都不难,难的是它们之间的配合——版本切换、全局路径、执行策略、符号链接、包存储目录,每一环都可能成为埋雷点。这篇文章就把我实际走的每一步、踩的每一个坑、以及最后的离线搬迁方案都记录下来,覆盖 Windows 下从零搭建到整机迁移的完整链路,也给那些总在搜索引擎里撞见同样报错的朋友一个可以直接照着做的答案。

1. 为什么是 nvm + nodejs + pnpm 这套组合

1.1 nvm 解决的版本管理痛点

如果你同时维护老项目和新项目,一定被 node 版本折腾过。老项目可能锁死在 node 12、14,新项目又要求 node 18、20 起步,直接安装官方 node 安装包只能存在一个版本,想升级就得卸载重装,而且卸载经常卸不干净,注册表、缓存、全局包残留一大堆。nvm(Node Version Manager)就是用来管理多版本 node 的工具,你可以在系统里同时安装多个 node 版本,随时切换,互不干扰。它是用户级的,不需要管理员权限也能完成大部分操作,对团队协作和统一环境非常有帮助。

Windows 上最常用的实现是 nvm-windows,它的底层原理和 Linux/macOS 上的 nvm 稍有不同:它更像是一个"版本切换器"——把当前选中的真实 node 目录通过符号链接映射到一个固定路径(默认是C:\Program Files\nodejs),你的 PATH 里只需要保留这一个入口,切换版本时只需要改符号链接的指向即可。理解这一点,后面离线迁移时就不会摸不着头脑。

1.2 pnpm 比 npm 强在哪

pnpm 的核心卖点有三个:磁盘空间、安装速度、依赖管理严谨性。它不像 npm 那样把每个依赖平铺复制到node_modules,而是在全局维护一个内容寻址的 store,所有项目通过硬链接复用同一个文件包。同一个依赖第一次安装时会写入 store,第二次装到其他项目时几乎零成本。实际体验就是:npm install 要二三十秒的项目,pnpm 可能几秒就完事。更重要的是,pnpm 的node_modules结构严格遵循依赖声明,不会出现"幽灵依赖"——就是那种你 package.json 里没写、却因为间接依赖才能正常 require 的包。这在大型项目里能避免很多玄学问题。

1.3 这套组合适合谁

我这次的搭建场景其实很典型:一台新到手的 Windows 工作机、一套要在内网离线复现的工程环境、多个前端项目对应不同 node 版本、统一使用 pnpm 管理依赖。这套方案适合三类人:一是手上项目多、需要频繁切换 node 版本的前端开发;二是经常写 Node 工具脚本、还要把这些工具分发出去使用的人;三是团队想统一依赖管理规范、减少"我本机跑得好好的"这类环境差异问题的人。如果你也属于其中任何一类,下面这套流程可以直接照着做。

2. Windows 下 nvm 安装与 node 版本管理实操

2.1 nvm-windows 安装与镜像配置

先去 nvm-windows 的 GitHub Releases 页下载最新的安装包。这里有一个很关键的点:安装路径一定不要有空格,也尽量不要放在 C 盘系统目录。我习惯放到D:\dev\nvm,这个路径后面就是NVM_HOME。如果你用可执行文件安装,全程下一步即可;如果下载的是 zip 压缩包,手动解压后需要自己配置环境变量。

安装完成后,第一件事就是设置镜像源。nvm 默认下载 node 是从官方服务器拉取,在国内网络环境下经常超时,建议提前配置:

nvm node_mirror https://npmmirror.com/mirrors/node/ nvm npm_mirror https://npmmirror.com/mirrors/npm/

node_mirror控制 node 发行版的下载地址,npm_mirror控制随 node 附带的 npm 包的下载地址。配好后用nvm version验证一下是否安装成功,然后继续。

2.2 用 nvm 安装和切换 node 版本

常用命令就那么几条:nvm list available查看可安装版本,nvm install 20.19.3安装指定版本,nvm use 20.19.3切换当前版本,nvm ls查看所有已安装版本,nvm alias default 20.19.3设置默认版本。我一般会安装两个版本,一个 LTS 一个较新版本,比如 18.20.8 和 22.x 各装一个,平时用 LTS,偶尔验证新特性时切换到新版本。

升级 node 版本的场景在 Windows 下也主要通过 nvm 完成:新版本nvm install后nvm use切换过去,老版本确认不再需要之后nvm uninstall 旧版本号清理掉,相当于一键升级加回滚保护。这里有个细节:nvm alias default设置的是每次新开终端的默认版本,但如果你在某一个终端里切了版本,这个终端会一直保持切换后的版本,直到重开。这个行为偶尔会让人困惑,实际不是 bug。

2.3 npm 全局目录和缓存目录的重新布局

node 装好之后,npm -v大概率有输出,但这只是开始。当你想npm install -g安装全局工具时,默认会装到当前 node 版本目录下的node_modules,比如D:\dev\nvm\v20.19.3\node_modules。这个位置有两个问题:一是 nvm 切换版本后,这个版本的全局包立刻失效,另一个版本里什么都没有;二是全局工具和 node 本体混在一起,升级时不够清爽。

我的做法是先重新指定 npm 前缀和缓存目录。新建D:\dev\npm-global和D:\dev\npm-cache两个目录,然后执行:

npm config set prefix "D:\dev\npm-global" npm config set cache "D:\dev\npm-cache" npm config set registry https://registry.npmmirror.com

配置会写入当前用户下的.npmrc。接着把D:\dev\npm-global追加到 PATH 环境变量。这样无论 nvm 切换哪个 node 版本,用 npm 安装的全局工具都在独立的目录里,不会因为切版本而丢。虽然 pnpm 我最终推荐独立安装而不依赖 npm 全局,但 npm 自身的全局目录规范化还是建议做掉,因为很多工具的安装脚本仍然会调用 npm。

3. pnpm 的安装、配置与项目接入

3.1 pnpm 三种安装方式,我为什么选独立安装

pnpm 常见的安装方式有三种。第一种是npm install -g pnpm,最简单,但致命缺点是:npm 全局装的包在 nvm 切换 node 版本后会失效。如果你固定用一个 node 版本,这种方式没毛病;但既然都引入 nvm 了,说明你有切换需求,那这就不合适。第二种是用 node 自带的 corepack:

corepack enable corepack prepare pnpm@latest --activate

corepack 的好处是随 node 附赠,而且支持按项目自动切换包管理器版本——只要 package.json 里声明了packageManager字段,corepack 会自动拉取对应版本。坏处是某些环境里 corepack 拉包偶尔会失败,而且在 nvm 切换 node 版本后,corepack 绑定的 pnpm 版本也可能跟着变化,排查起来更绕。

第三种是我在 Windows 下最推荐的:使用 pnpm 官方提供的独立安装脚本,把 pnpm 安装到独立目录PNPM_HOME。执行安装脚本后,它会创建%USERPROFILE%\AppData\Local\pnpm(或者自定义位置),在这个目录下生成 pnpm 的可执行文件和 shim,然后把PNPM_HOME添加到 PATH。由于它不依赖任何特定 node 版本,nvm 切来切去都不会影响 pnpm 本身,这是隔离性最强的方式。装完验证pnpm -v即可。

3.2 pnpm 的全局配置:store 路径、镜像源和全局工具

pnpm 有一个全局内容寻址存储目录,默认在系统盘用户目录下。时间久了这个目录体积非常可观,我见过不少人 C 盘空间告急,查来查去最后发现是 pnpm store 占了几十 GB。所以安装完第一件事就是把它移到非系统盘:

pnpm config set store-dir "D:\dev\pnpm-store" pnpm config set registry https://registry.npmmirror.com

store-dir就是 pnpm 的全局存储目录,所有依赖包解压后的内容都在这里被硬链接复用。还有两个路径建议顺手确认:global-dir和global-bin-dir,它们管的是pnpm add -g全局安装的包和可执行文件位置。默认情况下 global-bin-dir 会指向PNPM_HOME,这是正常的,只要你不把全局pnpm本体也装到同一个目录里反复 sets 就行了——否则会出现后面说到的 shim 循环指向问题。

3.3 项目接入 pnpm 与 workspace 配置

在项目目录里执行pnpm install,第一次运行时它会生成pnpm-lock.yaml。这个 lockfile 和 package-lock.json 一样锁定了所有依赖版本和安装来源,团队协作时一定要提交到仓库。对比 npm 的 lockfile,pnpm-lock.yaml 的结构更紧凑,可读性也更好。

如果是 monorepo 多包仓库,还需要在根目录创建pnpm-workspace.yaml:

packages: - "apps/*" - "packages/*" - "shared/*"

这个文件声明了哪些目录是独立的工作区包。有一个高频报错就出在这里:ERR_PNPM_INVALID_WORKSPACE_CONFIGURATION packages field missing or empty。我排查过一次,是有人不小心创建了一个空白的pnpm-workspace.yaml留在仓库里,pnpm 认为你在使用 workspace,但这个文件又没有合法的packages字段,直接报错。解决办法很简单:要么删掉这个多余的文件,要么补全packages配置。涉及私有库时,pnpm link也很有用:在本地开发 CLI 工具时,可以在工具目录执行pnpm link --global把它挂到全局进行调试,然后在另一个项目里pnpm link 工具名关联回来,比每次发布再安装要高效得多。

4. 高频报错逐个拆解与排查思路

4.1 PowerShell 执行策略导致 npm、pnpm 无法启动

npm : 无法加载文件 ... \npm.ps1,因为在此系统上禁止运行脚本——这句话是 Windows 相关搜索里出现频率最高的报错之一。原因很简单:PowerShell 默认的执行策略是Restricted,禁止运行 .ps1 脚本,而 npm、pnpm 在 Windows 下都通过 .ps1 包装脚本启动,于是直接被拦住了。

两种解决方案。一种是以管理员身份运行 PowerShell,然后调整当前用户的执行策略:

Set-ExecutionPolicy -Scope CurrentUser RemoteSigned

RemoteSigned意味着本地脚本可以运行,从远程下载的脚本需要合法签名。这样既解决了问题,又不会把安全策略放得太开。执行后可以运行Get-ExecutionPolicy -List确认各层策略。另一种方式是绕开 PowerShell:在 cmd 里运行 npm、pnpm 不存在这个问题,因为它们是 .cmd 包装。如果你不想改动执行策略,平时直接用 Windows Terminal 里的 cmd 配置也行。但我的建议还是把执行策略改掉,毕竟现在 VSCode 集成终端默认就是 PowerShell,一直切换很别扭。

4.2 PATH 顺序引起"命令找不到"

pnpm : 无法将“pnpm”项识别为 cmdlet、函数、脚本文件或可运行程序的名称,或者 cmd 下的'pnpm' 不是内部或外部命令,也不是可运行的程序,这类报错的排查思路基本都是 PATH。先运行where.exe pnpm看系统到底有没有找到这个命令,如果没有任何输出,说明PNPM_HOME(或D:\dev\npm-global)没有加入 PATH,或者加完之后没有重开终端。PATH 的修改在旧终端里不会自动生效,新开的终端才会加载。

还有一点容易忽略:PATH 的排列顺序决定了命令优先级。如果同一个命令在多个目录里存在,Windows 会按 PATH 里的顺序取第一个。我见过有人同时装了多个 node、多个包管理器,结果where.exe node指向的路径根本不是 nvm 的符号链接,而是一个不知何时装上的旧版 node 目录。遇到奇怪行为时,先检查node -v的实际路径是不是D:\dev\nvm底下的,如果指向别处,多半是旧安装残留。

4.3 nvm 切换版本后 VSCode 里 CLI 工具报 permission denied

有一个很典型的场景:nvm 装好了多个 node 版本,VSCode 终端里运行 Claude Code 这类 AI 编程命令行工具时报/claude: permission denied。这个报错看起来像 Unix 权限问题,但 Windows 下真正的原因往往是三类:第一,工具的全局脚本被装在了 nvm 当前版本的目录下,一旦切换 node 版本,这个可执行文件就消失了,VSCode 终端里自然执行失败;第二,PowerShell 执行策略没调过,报的是被禁止运行脚本,但表现上可能也是"没有权限";第三,PATH 中存在多个同名命令,VSCode 引用了某一个旧目录下的 shim 文件。

排查顺序建议是这样:先看Get-Command claude的输出,确认它解析到哪个路径;然后确认当前node -v对应的版本和安装工具时的版本是否一致;再检查执行策略Get-ExecutionPolicy -List。如果工具确实是通过 npm 全局装的,而且你又必须切换 node 版本,根治方法是把这类经常要用的 CLI 改成不依赖特定 node 版本的方式安装——比如 pnpm 的独立安装思路,或使用工具的官方独立安装脚本。这也是为什么我前面特意强调 pnpm 用独立安装而不是挂靠 npm:在 nvm + node 环境下,所有"全局命令"都应该尽量独立于 node 版本,否则每切一次版本就失灵一次,救都救不过来。

4.4 pnpm shim 指向自身的循环报错

pnpm: the global target of the pnpm shim points back at the shim这个报错稍微冷门一些,但一出现就让人摸不着头脑。它的本质是:PNPM_HOME下的 pnpm 启动脚本,通过global-bin-dir解析"真实的全局可执行程序",结果解析出来的路径还是 pnpm 自己的 shim 文件,形成循环指向。常见触发原因有两个:一是有人在PNPM_HOME目录里用 npm 又装了一份 pnpm,导致 shim 和真实程序混在一起;二是配置的global-bin-dir被手动改成了存放 pnpm 本体的目录,而没指向保存全局工具 shim 的位置。

遇到之后我建议干脆重装:先查清楚pnpm config get global-bin-dir和当前pnpm可执行文件的真实路径,然后卸载 pnpm 相关目录,用独立脚本重新安装,并且保持PNPM_HOME里只放 pnpm 自带的 shim,不再往里面额外npm i -g其他同名命令。环境变量的坑最忌讳修补,清理干净重来往往比定位配置更省时间。

5. 离线迁移:把整套环境搬到新机器

5.1 先理清需要迁移的清单

离线迁移听起来复杂,本质就是回答三个问题:环境由哪些独立部分组成、每个部分放在哪个目录、新机器上怎么还原。以我这套环境为例,清单如下:

  • nvm 主程序目录,即NVM_HOME,里面包含 nvm.exe 和所有已安装的 node 版本目录(如v20.19.3)
  • npm 的全局工具目录D:\dev\npm-global和缓存目录D:\dev\npm-cache
  • pnpm 本体,即PNPM_HOME目录
  • pnpm 的全局 store 目录D:\dev\pnpm-store
  • 项目自身的node_modules、pnpm-lock.yaml以及根目录的.npmrc、.pnpmfile.cjs等配置文件
  • VSCode 的 settings、终端 profile 等个性化配置,这个视个人需要而定

在旧机器上先执行一轮命令把路径都确认出来,避免凭记忆去找:

echo %NVM_HOME% npm config get prefix npm config get cache pnpm config get store-dir pnpm config get global-bin-dir pnpm store path

5.2 打包 nvm 与 node 版本目录

nvm-windows 的版本目录就在NVM_HOME下面,每个vX.Y.Z文件夹就是一个完整的 node 发行版。离线迁移最简单粗暴的做法是:把整个NVM_HOME目录直接拷贝或压缩带走,到了新机器再解压到同样的位置。这里有个很多人踩过的坑:不要直接去复制C:\Program Files\nodejs那个目录,它是 nvm 创建的符号链接,指向的是当前选中的版本,本身不包含实际文件。要复制就复制 nvm 目录底下v20.19.3这种真实版本文件夹。

新机器上如果没有联网或不想现场下载,可以有两种还原方式。一是同样安装一份 nvm,然后把拷来的版本目录直接放进NVM_HOME,最后执行nvm use 20.19.3让 nvm 重建符号链接。二是如果两台机器的NVM_HOME路径完全一致,甚至可以直接把整个 nvm 目录解压过去,补上NVM_HOME和NVM_SYMLINK两个环境变量,运行nvm version验证后执行nvm use即可。需要注意 nvm 在切换 node 时会修改NVM_SYMLINK指向的符号链接,这一步需要通过nvm use或者nvm reload触发,不能跳过。

5.3 迁移 pnpm store 实现项目离线安装

pnpm 的 store 是整个离线迁移中最有价值也最容易出错的部分。只要新机器的 store 和 lockfile 能对上,项目里执行pnpm install --offline就能完全不联网完成依赖安装。迁移 store 有两种方式。

第一种是直接拷贝 store 目录。把旧机器的D:\dev\pnpm-store整个打包,到新机器后先设置pnpm config set store-dir "D:\dev\pnpm-store",然后把内容放进去。这种方式最直接,但 store 目录通常体积不小,传输时需要压缩分卷或者用移动硬盘。

第二种是使用 pnpm 官方的导出导入机制。在旧机器上执行:

pnpm store export --output-dir D:\pnpm-export

这会从 store 中导出所有包对应的 tarball 文件到指定目录,文件体积比直接拷贝 store 小很多,适合网络传输。到了新机器,先设置好 store-dir,再执行:

pnpm store import --input-dir D:\pnpm-export

导入后 store 会重建。之后在项目目录执行:

pnpm install --offline --frozen-lockfile

--frozen-lockfile保证严格依据pnpm-lock.yaml安装,所有依赖版本和结构完全一致;--offline强制 pnpm 只从本地 store 取包,不访问任何远程源。如果某些包在 store 里缺失,pnpm 会明确提示,这时就说明导出的 store 不完整,需要回到旧机器补齐或者调整导出范围。这套机制非常稳,我自己验证过好几次,项目从一台机器搬到另一台机器,依赖安装结果完全一致。

5.4 迁移后的体检命令与环境校验

搬完之后不能直接跑业务代码,先做一轮体检,把所有环节快速过一遍:

nvm ls nvm current node -v npm -v pnpm -v npm config get registry pnpm config get store-dir

然后到项目里执行一次pnpm install --frozen-lockfile(如果 store 已迁移)或pnpm install(如果没有 store),确保可以正常安装。还有一个细节:如果项目里有需要 node-gyp 编译的原生模块,比如 sqlite3、node-sass 这类,直接拷贝node_modules跨机器可能因为编译产物不匹配而报错。Windows 平台上一般只要系统架构一致问题不大,但稳妥起见,原生模块依赖建议在新机器上重新pnpm rebuild一次:

pnpm rebuild

这一步会重新编译原生扩展,将环境相关的东西补齐。做完全套检查,离线迁移就真正完成了。

6. 常见问题速查表

问题现象常见原因解决方法
npm 或 pnpm 的 .ps1 脚本被禁止运行PowerShell 执行策略为 RestrictedSet-ExecutionPolicy -Scope CurrentUser RemoteSigned,或改用 cmd
pnpm 不是内部或外部命令PNPM_HOME 或全局目录未加入 PATH,或未重开终端检查where.exe pnpm,补全 PATH 后重开终端
切换 node 版本后全局命令失效npm 全局包装在当前 node 版本目录下用独立安装方式管理常用 CLI,或给每个 node 版本都补装依赖
ERR_PNPM_INVALID_WORKSPACE_CONFIGURATION存在空白或缺失 packages 字段的 pnpm-workspace.yaml删除多余文件,或补全packages配置
pnpm shim 指向 shim 本身global-bin-dir 配置不当,或 PNPM_HOME 里重复安装 pnpm清理干净后重新独立安装 pnpm,保持目录单一用途
离线 install 提示缺失部分包导出的 store 不完整,或多包仓库未包含全部依赖在旧机器上执行完整 install 后再导出,确保 lockfile 与 store 对得上
直接拷贝 node_modules 后原生模块报错node-gyp 编译产物与当前机器不匹配执行pnpm rebuild重新编译原生依赖

这套环境我反复搭过好多次,后来逐渐养成了一个习惯:新机器到手后不是急着装软件,而是先把 nvm 路径、npm 前缀、pnpm store 这几个关键目录全部规划到非系统盘,再统一安装和配置。这样以后无论换电脑还是重装系统,都只需要盯着那几个目录打包和解压,迁移成本会低非常多。最后再分享一个小技巧——在旧机器上把上面第 5.1 节的路径查询命令输出保存一份,和打包文件放在一起。等到了新机器核对环境时,对照旧机器的记录逐项验收,整个过程会清晰很多,不会出现"明明都装了但就是哪里不对"的烦躁时刻。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询