聊聊NPM镜像那些险象环生的坑

2020-06-15 15:28:38 浏览数 (1)

前言

由于国内网络环境的原因,在执行npm i安装项目依赖过程中,肯定会遇上安装过慢安装失败的情况。有经验的同学通常会在安装完「Node」时顺便把「NPM镜像」设置成国内的淘宝镜像。

代码语言:javascript复制
npm config set registry https://registry.npm.taobao.org/

这样就能爽歪歪应付大部分npm i的安装情况了。当然,这只是解决了大部分的安装过慢安装失败的情况,随着项目的深入开发,肯定还会遇上一些比较奇葩的情况,这也是笔者为什么要写本文的原因。

管理镜像

你还可能会遇上这种情况,开发项目时使用淘宝镜像,但是发布「NPM第三方模块」时就必须使用原镜像了。在着手解决那些奇葩情况前,先推荐大家使用一个「NPM镜像管理工具」

  • 「原镜像」https://registry.npmjs.org/
  • 「淘宝镜像」https://registry.npm.taobao.org/

主角就是nrm,它是一个可随时随地自由切换「NPM镜像」的管理工具。有了它,上面所说的何时使用什么镜像的问题就迎刃而解了。下面对其进行安装并简单讲解如何使用。

安装

代码语言:javascript复制
npm i -g nrm

查看镜像

代码语言:javascript复制
nrm ls

增加镜像

代码语言:javascript复制
nrm add <name> <url>

移除镜像

代码语言:javascript复制
nrm del <name>

测试镜像

代码语言:javascript复制
nrm test <name>

使用镜像

代码语言:javascript复制
nrm use <name>

查看当前镜像

代码语言:javascript复制
nrm current

熟悉命令后一波操作如下,原镜像淘宝镜像之间随意切换。当然,如果你记性好也不需要用这个工具了,哈哈。

nrm操作

遇坑填坑

有了nrm切换到淘宝镜像上,安装速度会明显加快,但是遇上安装的模块依赖了C 模块那就坑爹了。在安装过程中会隐式安装node-gypnode-gyp可编译这些依赖C 模块的模块。

那么问题来了,node-gyp在首次编译时会依赖Node源码,所以又悄悄去下载Node。虽然在前面已设置了淘宝镜像,但是在这里一点卵用都没有。这样又因为国内网络环境的原因,再次遇上安装过慢安装失败的情况。

还好npm config提供了一个参数disturl,它可设置Node镜像地址,当然还是将其指向国内的淘宝镜像。这样又能爽歪歪安装这些依赖C 模块的模块了。

代码语言:javascript复制
npm config set disturl https://npm.taobao.org/mirrors/node/

问题一步一步解决,接下来又出现另一个问题。平常大家都会使用node-sass作为项目开发依赖,但是node-sass的安装一直都是一个令人头疼的问题。

安装node-sass时,在install阶段会从Github上下载一个叫binding.node的文件,而「GitHub Releases」里的文件都托管在s3.amazonaws.com上,这个网址被Q了,所以又安装不了。

然而办法总比困难多,从node-sass的官方文档中可找到一个叫sass_binary_site的参数,它可设置Sass镜像地址,毫无疑问还是将其指向国内的淘宝镜像。这样又能爽歪歪安装node-sass了。

代码语言:javascript复制
npm config set sass_binary_site https://npm.taobao.org/mirrors/node-sass/

其实还有好几个类似的模块,为了方便,笔者还是把它们源码里的镜像参数和淘宝镜像里对应的镜像地址扒出来,统一设置方便安装。以下是笔者常用的几个模块镜像地址配置,请收下!

分别是:SassSharpElectronPuppeteerPhantomSentrySqlitePython

镜像地址配置

npm config set <name> <url>,赶紧「一键复制,永久使用」。特别注意,别漏了最后面的/

代码语言:javascript复制
npm config set sass_binary_site https://npm.taobao.org/mirrors/node-sass/
npm config set sharp_dist_base_url https://npm.taobao.org/mirrors/sharp-libvips/
npm config set electron_mirror https://npm.taobao.org/mirrors/electron/
npm config set puppeteer_download_host https://npm.taobao.org/mirrors/
npm config set phantomjs_cdnurl https://npm.taobao.org/mirrors/phantomjs/
npm config set sentrycli_cdnurl https://npm.taobao.org/mirrors/sentry-cli/
npm config set sqlite3_binary_site https://npm.taobao.org/mirrors/sqlite3/
npm config set python_mirror https://npm.taobao.org/mirrors/python/

有了这波操作,再执行npm i安装以上模块时就能享受国内的速度了。如果有条件,建议把这些镜像文件搬到自己或公司的服务器上,将镜像地址指向自己的服务器即可。在公司内网搭建一个这样的镜像服务器,一直安装一直爽,目前笔者所在的团队就是如此处理。

代码语言:javascript复制
npm config set electron_mirror https://xyz/mirrors/electron/

源码分析

以经常卡住的node-sass为例,下面是坑爹货node-sass/lib/extensions.js的源码部分,可看出它会默认走「GitHub Releases」的托管地址,上面也分析过原因,在这里就不重复了。

代码语言:javascript复制
function getBinaryUrl() {
  const site = getArgument("--sass-binary-site")
    || process.env.SASS_BINARY_SITE
    || process.env.npm_config_sass_binary_site
    || (pkg.nodeSassConfig && pkg.nodeSassConfig.binarySite)
    || "https://github.com/sass/node-sass/releases/download";
  const result = [site, "v"   pkg.version, getBinaryName()].join("/");
  return result;
}

而其他模块也有类似的代码,例如puppeteer这个安装Chronium的源码部分,有兴趣的同学都去扒一下源码,如出一辙。

代码语言:javascript复制
async function download() {
  await compileTypeScriptIfRequired();
  const downloadHost =
    process.env.PUPPETEER_DOWNLOAD_HOST
    || process.env.npm_config_puppeteer_download_host
    || process.env.npm_package_config_puppeteer_download_host;
  const puppeteer = require("./index");
  const product =
    process.env.PUPPETEER_PRODUCT
    || process.env.npm_config_puppeteer_product
    || process.env.npm_package_config_puppeteer_product
    || "chrome";
  const browserFetcher = puppeteer.createBrowserFetcher({
    product,
    host: downloadHost,
  });
  const revision = await getRevision();
  await fetchBinary(revision);
  // 还有很多
}

坑货小结

由于node-sass是大家经常使用的项目开发依赖,也是安装时间较长和最常见到报错的模块,在这里笔者就花点篇章分析和解决下可能会遇到的问题。

node-sass安装失败的原因其实并不止上面提到的情况,我们可从安装过程中分析并获取突破口来解决问题。根据npm i node-sass的输出信息来分析,可得到下面的过程。

  • 检测项目node_modulesnode-sass是否存在且当前安装版本是否一致
    • 「Yes」:跳过,完成安装过程
    • 「No」:进入下一步
  • 「NPM」上下载node-sass
  • 检测全局缓存项目缓存中是否存在binding.node
    • 「Yes」:跳过,完成安装过程
    • 「No」:进入下一步
  • 「Github Releases」上下载binding.node并将其缓存到全局
    • 「Success」:将版本信息写入package-lock.json
    • 「Error」:进入下一步
  • 尝试本地编译出binding.node
    • 「Success」:将版本信息写入package-lock.json
    • 「Error」:输出错误信息

不难看出,node-sass依赖了一个二进制文件binding.node,不仅需要从「NPM」上下载本体还需要从「Github Releases」上下载binding.node


从实际情况来看,node-sass出现安装过慢安装失败的情况可能有以下几种:

NPM镜像托管在国外服务器

上面有提到,在这里不再叙述,解决办法如下。

代码语言:javascript复制
nrm use taobao

安装过程中悄悄下载node-gyp

上面有提到,在这里不再叙述,解决办法如下。

代码语言:javascript复制
npm config set disturl https://npm.taobao.org/mirrors/node/

binding.node文件托管在国外服务器

上面有提到,在这里不再叙述,解决办法如下。

代码语言:javascript复制
npm config set sass_binary_site https://npm.taobao.org/mirrors/node-sass/

Node版本与node-sass版本不兼容

node-sass版本兼容性好差,必须与Node版本对应使用才行,详情请参考node-sass-version-association,复用官方文档的版本对照表,如下。

NodeJS

Minimum node-sass version

Node Module

Node 14

4.14

83

Node 13

4.13

79

Node 12

4.12

72

Node 11

4.10

67

Node 10

4.9

64

Node 8

4.5.3

57

执行npm i安装依赖前请确保当前的Node版本和node-sass版本已兼容。

全局缓存中的binding.node版本与Node版本不兼容

假如本地使用nvmn进行Node版本管理,并且已切换了Node版本,在安装过程中可能会出现Windows/OS X/Linux 64-bit with Node.js 12.x这样的提示,这种情况也是笔者经常遇上的情况(笔者电脑里安装了30多个Node版本并且经常来回切换?)。

这是因为node-sass版本和Node版本是关联的(看上面的表格),修改Node版本后在全局缓存中匹配不到对应的binding.node文件而导致安装失败。根据错误提示,清理NPM缓存且重新安装即可,解决办法如下。

代码语言:javascript复制
npm cache clean -f

npm rebuild node-sass

所以没什么事就别来回切换Node版本了,像笔者装这么多Node版本也是逼不得已,老项目太多了?。

安装失败后重新安装

有可能无权限删除已安装的内容,导致重新安装时可能会产生某些问题,建议将node_modules全部删除并重新安装。

在Mac系统和Linux系统上删除node_modules比较快,但是在Windows系统上删除node_modules就比较慢了,推荐大家使用rimraf删除node_modules,一个Node版的rm -rf工具。

代码语言:javascript复制
npm i -g rimraf

在项目的package.json中加入npm scriptsrimraf常驻。三大操作系统通用,非常推荐使用。

代码语言:javascript复制
{
  "scripts": {
    "reinstall": "rimraf node_modules && npm i"
  }
}

一有什么安装失败重新安装之类的操作,先执行npm run remove删除node_modulesnpm i

代码语言:javascript复制
npm run reinstall

终极操作

如果看得有点乱,那下面直接贴代码操作顺序,建议前端小白在安装完Node后立马处理这些NPM镜像问题,防止后续产生不必要的麻烦(解决这些问题是需要花费时间的?)。

代码语言:javascript复制
# 查看Node版本和NPM版本确认已安装Node环境
node -v
npm -v

# 安装nrm并设置NPM的淘宝镜像
npm i -g nrm
nrm use taobao

# 设置依赖安装过程中内部模块下载Node的淘宝镜像
npm config set disturl https://npm.taobao.org/mirrors/node/

# 设置常用模块的淘宝镜像
npm config set sass_binary_site https://npm.taobao.org/mirrors/node-sass/
npm config set sharp_dist_base_url https://npm.taobao.org/mirrors/sharp-libvips/
npm config set electron_mirror https://npm.taobao.org/mirrors/electron/
npm config set puppeteer_download_host https://npm.taobao.org/mirrors/
npm config set phantomjs_cdnurl https://npm.taobao.org/mirrors/phantomjs/
npm config set sentrycli_cdnurl https://npm.taobao.org/mirrors/sentry-cli/
npm config set sqlite3_binary_site https://npm.taobao.org/mirrors/sqlite3/
npm config set python_mirror https://npm.taobao.org/mirrors/python/

针对node-sass的情况:

代码语言:javascript复制
# 安装rimraf并设置package.json
npm i -g rimraf

# 安装前请确保当前的Node版本和node-sass版本已兼容

# 安装失败
npm cache clean -f
npm rebuild node-sass 或 npm run reinstall

package.json中加入npm scripts

代码语言:javascript复制
{
  "scripts": {
    "reinstall": "rimraf node_modules && npm i"
  }
}

总结

「NPM镜像问题」的坑确实很多,归根到底还是网络环境导致的。当然这些问题也阻碍不了乐于探索的我们,办法总比困难多,坚持下去始终能找到解决方式。

笔者总结出一个解决这种「NPM镜像问题」的好方法,遇到一些上面没有提到的模块,可尝试通过以下步骤去解决问题。

  • 执行npm i前设置淘宝镜像,保证安装项目依赖时都走国内网络
  • 安装不成功时,肯定是在安装过程中该模块内部又去下载了其他国外服务器的文件
  • 在Github上克隆一份该模块的源码进行分析,搜索包含base、binary、cdn、config、dist、download、host、mirror、npm、site、url等这样的关键词(自行探索,通常「mirror」的匹配度最高)
  • 在搜查结果里查找形态像「镜像地址」的代码块,再分析该代码块的功能并提取最终的「镜像地址」,例如node-sasssass_binary_site
  • 去淘宝镜像官网、百度、谷歌等网站查找你需要的镜像地址,如果实在找不到就规范上网把国外服务器的镜像文件拉下来搬到自己或公司的服务器上
  • 设置模块依赖的镜像地址:npm config set <registry name> <taobao url / yourself url>
  • 重新执行npm i安装项目依赖,大功告成

0 人点赞