
npm 镜像源的切换这事说小很小一条npm config set registry就完事说大也真大我见过不止一个团队因为源配错了CI 卡在npm ci上半小时最后查出来是项目目录里躺着一个谁也不记得的.npmrc。国内网络环境下装依赖镜像源基本是绕不开的第一道配置但真正把项目级、用户级、全局级三层配置的优先级搞清楚的人其实没那么多。这篇就按我自己踩过的顺序把 npm 镜像源的设置方法、切换姿势、以及切完之后反而报错的那几种情况从头捋一遍适合刚上手 Node 的同学也适合想把这套东西在团队里标准化下来的老手。1. 从卡在 0% 的安装说起镜像源到底改变了哪一段链路带新人的时候我特别爱看一个画面终端里敲下npm install进度条半天不动node_modules里孤零零躺着两三个文件夹--verbose打开一看一排请求全指向registry.npmjs.org状态是 pending。这时候很多人的第一反应是网断了第二反应是换个镜像源试试。换完确实好了但为什么好多数人说不上来。1.1 一次 npm install 背后到底发了几类请求把安装流程拆开看npm 做的事情大致是这么几步读取package.json和package-lock.json构建出完整的依赖树对树里每一个包向 registry 请求它的元数据社区里叫 packument就是registry/包名返回的那一大坨 JSON包含所有版本号、每个版本的dist.tarball地址、dist.integrity校验值拿到 tarball 地址之后下载 tgz 压缩包解压写进node_modules最后执行各个包的postinstall脚本。镜像源影响的是第三步和第四步也就是元数据请求和tarball 下载这两段。这一点很关键——很多人以为镜像源只是个下载加速器其实它同时接管了元数据查询。而且镜像站返回的 packument 里dist.tarball字段指向的是镜像站自己的 CDN 地址不是官方地址所以一旦切了源下载路径也跟着变了。这直接解释了一个常见现象切换镜像源之后package-lock.json里的resolved字段会跟着变而这个变化会在下一个章节里给你带来麻烦先记住这个伏笔。它管不了的部分也得说清楚不然你会在错误的地方使劲postinstall脚本里自己发起的网络请求Electron、Puppeteer、sharp 这些包的二进制下载走的是各自独立的域名跟 registry 一点关系没有githttps://形式的依赖走的是 git 协议file:和link:形式的本地依赖压根不出网npm 自身检查新版本的请求也是独立的。我碰到过最典型的误判就是有人给 Electron 项目换了三四个 registry二进制该下不下来还是下不下来白白折腾一上午。1.2 镜像同步延迟刚发布的包为什么装不到还有一个必须提前建立的心理预期镜像站是异步同步的不是实时反代。它靠定时任务增量拉取官方源的新包和新版本所以一个包刚在官方源发布镜像上搜不到、装不上是完全正常的事延迟从几分钟到几十分钟都有可能。具体的表现通常是这样npm view 某个新包 version返回 404或者返回的是旧版本号npm install 某个新包latest装出来的是上个版本。这时候千万别怀疑自己的配置写错了先拿官方源验证一下npm view 包名 version --registryhttps://registry.npmjs.org/如果官方源能看到、镜像看不到那就是同步没跟上。急着用的场景比如内部刚发的补丁包临时用--registry参数单次指定官方源就够了别为了一个包把全局配置改回去。2. .npmrc 的三层加载顺序项目级、用户级、全局级怎么选我敢说我明明改了源怎么还是慢这个问题九成以上是因为不知道 npm 的配置是有层级的而且层与层之间会互相覆盖。2.1 四个配置文件优先级从低到高npm 读取配置的顺序大致是这样的从低优先级到高优先级层级文件位置典型用途内置配置npm 安装目录下的npmrc基本不动全局配置$PREFIX/etc/npmrc机器级别的统一设置用户配置~/.npmrcWindows 是C:\Users\你\.npmrc个人开发机的默认源项目配置项目根目录的.npmrc团队统一配置可以提交进仓库优先级高的覆盖优先级低的同一层级里后出现的覆盖先出现的。这意味着只要项目根目录里有一个.npmrc写着registryhttps://registry.npmjs.org/你在终端里敲一百遍npm config set registry都不会生效——因为npm config set默认写的是用户级文件被项目级压住了。想确认当前到底哪个值在起作用两条命令足够npm config get registry npm config ls -l第一条看最终生效的值第二条列出所有配置项及其来源。排查这类问题时npm config ls -l比翻文件快得多。如果你确实想直接写进项目级配置加上--location参数npm config set registry https://registry.npmmirror.com --locationproject反过来想彻底删掉某个配置项让它回落到默认值用npm config delete registry而不是把它设成空字符串——设成空字符串会得到一个奇怪的中间状态。2.2 命令行、手写文件、环境变量三种写法怎么选设置方式其实就三种各有各的适用场合。命令行npm config set胜在快适合自己机器上随手改一下缺点是它默认写用户级多人协作时容易变成只有我这台机器是好的。手写.npmrc胜在可控、可版本化。项目根目录建一个.npmrc内容就一行registryhttps://registry.npmmirror.com/提交到仓库整个团队执行npm install时自动走这个源。但要记住一条铁律.npmrc里绝对不要写死认证 token。需要 token 的场景私有源、发布包用环境变量占位//registry.npmjs.org/:_authToken${NPM_TOKEN}npm 会在读取配置时做变量展开本地开发时把NPM_TOKEN放在系统环境变量里CI 里放在 secret 里代码仓库里干干净净。环境变量这种方式在 CI 里最好用因为它不落文件、不改镜像层、容器跑完就没了export NPM_CONFIG_REGISTRYhttps://registry.npmmirror.com/ npm cinpm 会把npm_config_前缀大小写不敏感的环境变量识别成配置项所以NPM_CONFIG_REGISTRY和npm_config_registry效果一样。我个人在流水线里优先用这种方式。还有一种只在单次命令生效的临时写法排查问题的时候特别顺手npm install --registryhttps://registry.npmjs.org/3. 国内主流镜像源清单与选型实测配置之前先把选项列清楚。下面这几个是我实际用过的都还稳定存在于现在。源名称地址特点官方源https://registry.npmjs.org/唯一权威支持发布、audit、搜索全量 APInpmmirror阿里https://registry.npmmirror.com/同步频率高覆盖面广个人开发机首选腾讯云https://mirrors.cloud.tencent.com/npm/云上机器访问延迟低华为云https://mirrors.huaweicloud.com/repository/npm/云上机器访问延迟低中科大https://npmreg.proxy.ustclug.org/教育网环境表现不错这里必须提醒一句历史包袱早年流传最广的淘宝镜像地址是registry.npm.taobao.org这个域名已经下线了还在用它的配置会直接连不上。如果你是从老项目、老教程里抄过来的配置记得换成registry.npmmirror.com。我见过好几个案例是照着三年前的博客配的源然后抱怨镜像源怎么全挂了。3.1 怎么验证一个源到底快不快别信体感测一下。三个层次的验证手段从轻到重# 1. 连通性只测能不能通不测速度 npm ping --registryhttps://registry.npmmirror.com/ # 2. 元数据请求延迟 time npm view react version --registryhttps://registry.npmmirror.com/ # 3. 真实安装耗时最准 rm -rf node_modules time npm install --registryhttps://registry.npmmirror.com/npm ping只验证连通性别拿它的耗时当性能指标它返回的那点延迟跟下载 tarball 完全是两回事。真正有参考价值的是第三步用同一个锁文件、同一台机器依次测几个源把耗时记下来对比十分钟能测完比你凭印象拍脑袋靠谱得多。3.2 选型上我给的建议个人开发机直接用 npmmirror 写进用户级.npmrc一次配置长期受益。企业内网不建议让所有人的机器各连各的公网镜像更稳的做法是在内网起一个 Verdaccio 或者 Nexus上游指到官方源做一层本地缓存。这样第一次拉包走公网之后全走内网既快又能在上游抖动时保持可用。这种私服还有个额外好处——可以托管内部私有包和公共包共存于同一个 registry 地址下。需要npm audit或者要发布包的场景临时切官方源。因为部分镜像站对 audit、search 这类 API 支持不完整审计时可能直接报错或者返回空结果这不是你配置错了是镜像本身的能力边界。4. 切源与还原命令行、nrm、环境变量三种姿势的利弊切过去容易切回来才是考验记忆力的时候。这一节专门讲切换和还原。4.1 切回官方源两种写法结果不一样最直观的写法是把官方地址设回去npm config set registry https://registry.npmjs.org/这样做的结果是用户级.npmrc里留下了一行registryhttps://registry.npmjs.org/。功能上没问题但它和没配置是两回事——如果你之后换了个工具或者某些工具在无配置时会有不同行为这行残留可能会造成困惑。更干净的做法是删掉这个配置项让 npm 回落到内置默认值npm config delete registry我个人习惯是后者配置项越少越好排查。当然如果你是全程用项目级.npmrc管理源那还原这个动作根本不需要——把项目文件删了就回到用户级配置了这也是我更喜欢项目级配置的原因之一。4.2 nrm 这类源管理工具方便但有它自己的坑nrm 是流传很广的一个源管理小工具装完之后一条nrm use npmmirror就能切源还能nrm ls列出所有源、nrm test批量测速。npm i -g nrm nrm ls nrm test nrm use npmmirror它本质上是帮你改用户级.npmrc的封装没有魔法。用之前有几个坑得知道第一装 nrm 这个动作本身也要走源。如果你的全局源已经是慢的那个装 nrm 会慢得让你怀疑人生这时候先临时指定一次npm i -g nrm --registryhttps://registry.npmmirror.com/第二老版本 nrm 内置的源列表里可能还留着已经下线的旧地址。装完之后先nrm ls看一眼如果看到的是过时域名用nrm add手动覆盖掉或者干脆跳过 nrm 直接改文件。这类工具的源列表是随包发布的不会自己更新。第三在一些较新的 Node 版本上nrm 这类老工具可能因为模块格式问题报错启动不了。这不是你环境坏了是工具的年代问题。真遇到这种情况退回手动npm config set反而更省事——一个只有一行配置的事情其实不太需要引入额外依赖。我自己的选择是不装 nrm。理由很简单npm config set registry这条命令我从没记错过而多一个全局包就多一个需要维护的东西。5. 切源之后反而报错的排查实录这部分是全文我最有表达欲的地方。下面几种报错我几乎每次带新人都会遇到而且它们的共同点是看起来像镜像源问题其实根本不是。5.1 npm.ps1 无法加载因为在此系统上禁止运行脚本完整报错长这样npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1因为在此系统上禁止运行脚本。新手看到这个第一反应就是镜像源配错了然后开始疯狂改 registry改到天亮也没用。排查链路应该是这样的先看报错里的关键词——禁止运行脚本这是 PowerShell 的执行策略问题跟网络、跟 registry 没有半毛钱关系。验证一下当前策略Get-ExecutionPolicy -List如果CurrentUser那一行是Restricted那就对上了。解决方式有两种我建议第一种# 方式一只放开当前用户影响范围最小 Set-ExecutionPolicy -Scope CurrentUser RemoteSigned # 方式二改用 cmd绕过 PowerShell # 直接在 cmd 里执行 npm installRemoteSigned的含义是本地写的脚本可以跑从网上下载的脚本必须有签名。对日常开发来说这个粒度够用也不至于把机器完全敞开。不要去用Set-ExecutionPolicy Unrestricted那个范围太大了。顺带说一句如果你在 CI 的 Windows runner 上遇到同一个报错处理方式是一样的在步骤最前面加一条执行策略设置即可。5.2 node-domexception 弃用警告跟镜像源毫无关系这个警告太常见了npm warn deprecated node-domexception1.0.0: use your platforms native DOMException instead因为热词榜上长期挂着它很多人把它跟镜像源关联起来。但它就是个弃用提示node-domexception这个包的作者发现自己没必要存在了因为新版 Node 已经内置了DOMException所以打了个弃用标记。它出现在你的安装日志里说明你的某条依赖链里有人大概率是某个 fetch 实现库还在引它。这个警告不影响安装结果包照样装、代码照样跑。真要消掉它正规做法是用 npm 的overrides字段把它顶掉或者在项目里接受它、别管它。为了消这个警告去改 registry属于典型的南辕北辙。我在项目里一般是直接无视因为这类传递依赖的弃用警告会随着上游库更新自然消失。5.3 package-lock.json 里残留的 resolved 地址这个坑比较隐蔽症状是你明明切到了新源npm install却还在往旧地址发请求或者速度并没有改善。根因就在第 1.1 节埋的那个伏笔package-lock.json里每个包都记着完整的resolved字段写着 tarball 的绝对地址。这个文件是上一次安装时生成的里面锁死了当时的源。切源之后npm 会优先按 lock 文件里的地址去取包你的新配置没被用上。处理方式按激进程度排# 温和只重建锁文件不装依赖 rm package-lock.json npm install --package-lock-only # 彻底锁文件和依赖全删重来 rm -rf node_modules package-lock.json npm installWindows 下第一条的rm换成del package-lock.json。至于选哪种看你在什么阶段开发机上随便删删完重装最干净如果是团队共用的锁文件重建锁文件会产生一大片 diff提交前最好跟同事打个招呼或者单独开一个提交说明为什么重建。另外 npm 较新版本里有replace-registry-host这个配置项可以影响 npm 在写resolved时对主机名的处理方式。这个选项的行为在不同 npm 大版本间有过调整如果你打算在团队里统一用建议先在自己机器上验证一遍再推给所有人——配置项的默认值变动这种事踩过一次就长了记性。5.4 私有包与公共镜像源的正面冲突报错长这样npm ERR! 404 Not Found - GET https://registry.npmmirror.com/yourcompany%2fshared-utilsyourcompany是你们内部的 scope公共镜像站上当然没有。这不是镜像坏了是路由问题。正确解法是在.npmrc里按 scope 分流让 npm 知道哪种包去哪个源拿registryhttps://registry.npmmirror.com/ yourcompany:registryhttps://your-private-registry.example.com/ //your-private-registry.example.com/:_authToken${NPM_TOKEN}这样公共包走镜像加速私有 scope 走内网私服两边互不干扰。这套配置我在三个不同规模的团队里推过是处理公私混合场景最干净的方式。要注意的是 scope 前面的和后面的冒号都不能省写成yourcompany:registry是不生效的。6. 二进制依赖、发私有包与 CI 构建的特例处理前面说的都是常规包但真实项目里总有那么几类特例它们不吃registry这一套。6.1 Electron、Puppeteer、sharp 这类包的独立下载源这些包的共同特点是npm 上发布的那个包只是个壳真正的二进制文件在postinstall阶段从另一个地址下载。它们的下载域名、环境变量名前缀各不相同registry 配置对它们完全无效。正确的处理方式是查对应版本官方文档里的镜像变量名然后写进.npmrcnpm 会把.npmrc里的小写配置项导出成npm_config_开头的环境变量所以这些包的 postinstall 脚本能读到electron_mirrorhttps://npmmirror.com/mirrors/electron/ electron_builder_binaries_mirrorhttps://npmmirror.com/mirrors/electron-builder-binaries/有个必须提醒的点Puppeteer 在不同大版本之间改过下载相关的环境变量名。你在网上搜到的配置极可能是给旧版本写的照抄会没效果还查不出原因。所以这类问题我的标准流程是先确定本地装的是哪个版本再去查这个版本的文档最后才动配置。顺序反了就是白费功夫。另一个排查技巧这类二进制下载失败时报错信息里通常会打出它实际请求的 URL 和用的环境变量名。把完整日志从头翻一遍比搜关键词快。6.2 npm publish 为什么必须切回官方源镜像站是只读缓存你往它上面发布包会收到类似 405 或者 403 的响应。所以发布流程里必须显式指定官方源。两种落地方式。第一种写在package.json里让包自己声明发布目标{ name: your-package, publishConfig: { registry: https://registry.npmjs.org/ } }这种方式的好处是跟着包走谁 clone 下来发布都走对地址。第二种是在.npmrc里单独配一条配合认证 token 使用。如果你的包是发到企业私服的那方向反过来——publishConfig指向私服地址。这里有个容易忽略的细节认证 token 是按 registry 主机名绑定的。你给官方源配的 token 不会自动用在私服上反之亦然。所以同时用到两个源的场景.npmrc里会有两行//主机名/:_authToken...各自独立。配错了的表现是 401而不是 404这个区分能帮你快速定位问题性质。6.3 CI 与 Docker 构建里怎么落地CI 环境我的建议是尽量用环境变量别改文件理由有两个一是容器是一次性的改文件不会留下任何好处二是环境变量不会被打进镜像层不存在泄漏风险。GitHub Actions 里的写法大致是- name: Install dependencies env: NPM_CONFIG_REGISTRY: https://registry.npmmirror.com/ run: npm ciDockerfile 里要注意构建缓存的问题。如果你把源地址写死在RUN里换源就会导致这一层缓存失效整个依赖安装重来。用ARG承接会灵活一些ARG NPM_REGISTRYhttps://registry.npmmirror.com/ RUN npm config set registry $NPM_REGISTRY npm ci另外 CI 场景下我更推荐npm ci而不是npm install。npm ci严格按锁文件安装不会顺手修改package-lock.json构建结果可复现速度也更快。如果 CI 上出现本地能装、流水线装不上的情况第一件事是确认流水线跑的是不是npm ci第二件事是确认锁文件有没有被提交。最后一条任何 token、密码、私服地址里的凭据都不要以明文形式出现在 Dockerfile、.npmrc、CI 配置文件里。用构建参数配合 secret 注入这是一条没有商量余地的红线。7. 顺带把 yarn、pnpm、corepack 的镜像也理顺一个项目里往往是好几个包管理器混着用只配 npm 不够。yarn 1.x 有自己的配置文件命令是yarn config set registry https://registry.npmmirror.com写进~/.yarnrc。yarn 2 及以上也就是 Berry换成 YAML 格式的.yarnrc.yml字段名也变了npmRegistryServer: https://registry.npmmirror.compnpm 相对省事它直接读.npmrc所以你在 npm 那边配好的源pnpm 开箱即用。当然它也有自己的命令pnpm config set registry效果等价看你习惯。corepack 这个要注意它管的是包管理器本身的下载。就算你的 registry 配好了用corepack enable去拉指定版本的 pnpm 或 yarn 时走的还是另一条路径需要单独设置export COREPACK_NPM_REGISTRYhttps://registry.npmmirror.com/这个配置我在 CI 上踩过一次现象是corepack prepare卡住不动日志里能看到它在往官方源请求。当时排查了半天以为是网络问题其实就是少了一个环境变量。至于为什么会出现一个项目三个包管理器那是另一个话题了。我个人的做法是在项目根目录的.npmrc里把 registry 配好然后无论团队里谁用什么工具至少这一层是一致的。工具可以有分歧源最好只有一个。最后分享一个我自己长期在用的习惯用户级.npmrc里只放最基础的默认源项目级.npmrc只放团队确实需要统一的那几行registry 加上 scope 分流任何带凭据的东西一律走环境变量用不同的 shell 别名去切换。发布包的场景我会单独留一个别名把 registry 覆盖成官方源再执行npm publish这样日常开发始终走镜像发布时那一次命令保持干净。这套习惯我用了好几年从没出现过发到镜像上或者token 被提交进仓库这类事故——而这两件事我在别人的项目里都亲眼见过。