赞
踩
以 npm install gulp-imagemin
为例。
npm install xxx
的过程:
npm registry
的接口,获取一段 JSON 数据npm registry
的地址在 NPM 配置中。
调用的接口是 <npm registry>/<模块名>
,例如 https://registry.npmjs.com/gulp-imagemin
。
dist-tags
里面找到要下载的模块的版本。dist-tags
存储的是这个 JS 包的版本的标签。
安装 NPM 包的时候如果没有指定具体版本,默认会安装 dist-tags
中的 latest
存储的版本,即最新的版本。
versions
中找对应版本的信息versions
中存储的信息基本和 package.json
差不多。
versions
中找到 dist.tarball
这是该模块的安装压缩包的下载地址。
如果配置的是淘宝的镜像源,这个地址就是淘宝的安装包下载地址,这就是有些模块安装快的原因。
下载完安装包后,会在 node_modules
下以模块名为名创建一个目录,将安装包内容解压到该目录下。
然后以此类推,安装依赖的模块,将依赖的模块的安装包全部下载下来。
整个过程只访问了 npm registry 的地址,所以可以通过配置 npm registry 提高 npm 模块下载速度。
模块安装完后,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"
}
为什么需要 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 安装包目录:
// 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); } } })();
下载二进制模块一般有两个选择:
// 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 地址,它允许通过几种方式指定下载地址,优先级如下:
SASS_BINARY_SITE
获取sass_binary_site
获取package.json
的 nodeSassConfig.binarySite
获取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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。