关于NPM安装模块失败的问题

Npm 安装模块失败的问题

npm install gulp-imagemin 为例。

在这里插入图片描述

在这里插入图片描述

复盘整个安装过程

npm install xxx 的过程:

  1. 先调用一个 npm registry 的接口,获取一段 JSON 数据

npm registry 的地址在 NPM 配置中。

调用的接口是 <npm registry>/<模块名>,例如 https://registry.npmjs.com/gulp-imagemin

  1. dist-tags 里面找到要下载的模块的版本。

dist-tags 存储的是这个 JS 包的版本的标签。

安装 NPM 包的时候如果没有指定具体版本,默认会安装 dist-tags 中的 latest存储的版本,即最新的版本。

在这里插入图片描述

  1. 再去 versions 中找对应版本的信息

versions 中存储的信息基本和 package.json差不多。

在这里插入图片描述

  1. versions 中找到 dist.tarball

这是该模块的安装压缩包的下载地址。

在这里插入图片描述

如果配置的是淘宝的镜像源,这个地址就是淘宝的安装包下载地址,这就是有些模块安装快的原因。

  1. 下载安装包

下载完安装包后,会在 node_modules 下以模块名为名创建一个目录,将安装包内容解压到该目录下。

然后以此类推,安装依赖的模块,将依赖的模块的安装包全部下载下来。

整个过程只访问了 npm registry 的地址,所以可以通过配置 npm registry 提高 npm 模块下载速度。

  1. 安装依赖

模块安装完后,NPM 会检查package.json中的 scripts,看是否有需要执行的脚本。

NPM 约定执行脚本的顺序:npm run install > npm run postinstall

以示例为例,gulp-imagemin 依赖的 gifsicle 模块安装完成后,执行了它配置的 postinstall,下载对应的安装包查看 package.json

"scripts": {
  "postinstall": "node lib/install.js",
  "test": "xo && ava --timeout=60s"
}

npm 安装其它依赖

为什么需要 postinstall 去安装一些模块,而不是使用 npm 自身的依赖关系(dependencies)?

node 中提供了很多 API,主要是文件操作(fs)和网络操作(net)的 API。

这两类 API 基本上可以完成我们日常开发中的绝大多数功能。

对于一些功能,如图片处理、node-sass等,它们本身并没有用 JS 语言去实现,或者 JS 实现的效率并不高。

也就是仍有一些 node 提供的 API 不足以实现或不适合实现的功能。

对于这类功能,node 建议使用 C++ 开发的原生模块(native module),这些原生模块最终会被编译成二进制文件单独发布。

如示例中 gifsicle 模块最终需要下载的二进制文件:https://github.com/imagemin/gifsicle-bin/tree/master/vendor/win/x64/gifsicle.exe

而 node 提供的功能是支持通过 API 命令式的调用下载的 C++ 开发的模块。

npm 只会维系 npm 模块,而依赖的二进制模块就需要通过 scripts 去配置安装。

gifsicle 模块 postinstall 执行内容

gifsicle 安装包目录:

在这里插入图片描述

// lib/install.js
'use strict';
const path = require('path');
const binBuild = require('bin-build');
const log = require('logalot');
const bin = require('.');

(async () => {
	try {
    // 执行 bin 加载的文件内容,即 lib/index.js
    // 实际上就是去下载一个二进制文件
		await bin.run(['--version']);
    // 提示预编译成功
		log.success('gifsicle pre-build test passed successfully');
	} catch (error) {
		log.warn(error.message);
		log.warn('gifsicle pre-build test failed');
		log.info('compiling from source');

		const config = [
			'./configure --disable-gifview --disable-gifdiff',
			`--prefix="${bin.dest()}" --bindir="${bin.dest()}"`
		].join(' ');

    // 如果下载失败,就会尝试编译一个二进制文件
		try {
      // 这个本地的压缩包就是模块的源代码
			await binBuild.file(path.resolve(__dirname, '../vendor/source/gifsicle-1.92.tar.gz'), [
				'autoreconf -ivf',
				config,
				'make install'
			]);

			log.success('gifsicle built successfully');
		} catch (error) {
			log.error(error.stack);

			// eslint-disable-next-line unicorn/no-process-exit
			process.exit(1);
		}
	}
  })();

下载二进制模块一般有两个选择:

  1. (优先)从网络上下载编译好的二进制文件
  2. 直接在本地编译这个模块的源码
// lib/index.js
'use strict';
const path = require('path');
const BinWrapper = require('bin-wrapper');
const pkg = require('../package.json');

const url = `https://raw.githubusercontent.com/imagemin/gifsicle-bin/v${pkg.version}/vendor/`;

// bin-wrapper 是 npm 模块,内部封装了下载文件的操作
// 下面配置了每个环境下的下载文件地址
module.exports = new BinWrapper()
	.src(`${url}macos/gifsicle`, 'darwin')
	.src(`${url}linux/x86/gifsicle`, 'linux', 'x86')
	.src(`${url}linux/x64/gifsicle`, 'linux', 'x64')
	.src(`${url}freebsd/x86/gifsicle`, 'freebsd', 'x86')
	.src(`${url}freebsd/x64/gifsicle`, 'freebsd', 'x64')
	.src(`${url}win/x86/gifsicle.exe`, 'win32', 'x86')
	.src(`${url}win/x64/gifsicle.exe`, 'win32', 'x64')
	.dest(path.join(__dirname, '../vendor'))
	.use(process.platform === 'win32' ? 'gifsicle.exe' : 'gifsicle');

可以看到它优先从 https://raw.githubusercontent.com 这个地址下载。

但是由于国内访问限制,下载最终会失败,所以可以通过 hosts 配置 IP 或其它方法解决访问限制解决下载失败的问题。

为什么模块作者不把要下载的二进制文件直接放到安装包中,而是要通过 postinstall 去下载?

因为二进制文件也根据不同的操作环境编译不同的可执行文件,只有在安装模块的时候才能判断需要下载哪个环境的文件。

如 mac 和 win,win x86 和 win x64。

配置镜像的方式

有的模块支持通过镜像(mirror)地址下载。

npm i node-sass 为例(SASS 已弃用 node-sass ,以 Dart Sass 代替)

node-sass 安装完成后,执行它的脚本 install > postinstall:优先使用下载的方式,下载失败就采用本地编译。

但是 node-sass 本地编译需要配置 python 环境和 C++ 编译器,如果不满足条件,也会编译失败。

"scripts": {
  "build": "node scripts/build.js --force",
  "coverage": "nyc npm run test",
  "install": "node scripts/install.js",
  "lint": "eslint bin/node-sass lib scripts test",
  "postinstall": "node scripts/build.js",
  "prepublishOnly ": "scripts/prepublish.js",
  "test": "mocha test/{*,**/**}.js"
}

使用下载的方式:

// scripts/install.js
/*!
 * node-sass: scripts/install.js
 */

var fs = require('fs'),
  eol = require('os').EOL,
  mkdir = require('mkdirp'),
  path = require('path'),
  sass = require('../lib/extensions'),
  request = require('request'),
  log = require('npmlog'),
  downloadOptions = require('./util/downloadoptions');

/**
 * Download file, if succeeds save, if not delete
 */

function download(url, dest, cb) {...}

/**
 * Check and download binary
 *
 * @api private
 */

function checkAndDownloadBinary() {
  // ...

  // 下载地址:sass.getBinaryUrl()
  download(sass.getBinaryUrl(), binaryPath, function(err) {...});
}

/**
 * If binary does not exist, download it
 */

checkAndDownloadBinary();

// lib/extensions.js
function getBinaryUrl() {
  var 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';

  return [site, 'v' + pkg.version, getBinaryName()].join('/');
}

这个下载地址是由 site 地址、版本号、文件名拼接出来的。

主要就看 site 地址,它允许通过几种方式指定下载地址,优先级如下:

  1. 通过 npm 命令参数 `–sass-binary-site``
  2. 从环境变量 SASS_BINARY_SITE 获取
  3. 从 npm 配置文件中配置的的 sass_binary_site 获取
  4. 从当前项目 package.jsonnodeSassConfig.binarySite 获取
  5. 最后默认是 github 地址。

如果该模块由其它镜像下载地址,就可以使用这几种方式指定。

例如淘宝镜像地址:http://npm.taobao.org/mirrors/<模块名>(注意后面是否需要 /

命令参数:npm i node-sass --sass-binary-site=http://npm.taobao.org/mirrors/node-sass

npm 配置文件:

# 添加一行
sass_binary_site=https://npm.taobao.org/mirrors/node-sass
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值