昨天有个前端项目代码一直npm install 失败,提示找不到预编译包,直接调用node-gyp进行build。以前发生类似问题,如node-sass安装失败,只要配置国内镜像地址,直接拉取到编译后的包。但是这次包本来就在package中了……
错误表象
npm ERR! command sh -c node-gyp rebuild
npm ERR! gyp info it worked if it ends with ok
npm ERR! gyp info using node-gyp@7.1.2
一般看到这个错误,肯定想到是不是没有配置国内源。例如node-sass下载不了,可以在项目的根目录下的.npmrc 文件配置:
sass_binary_site=https://npmmirror.com/mirrors/node-sass
因为在node-sass的install前,会先尝试从github上下载二进制包,如果下载不到就执行编译。国内github连接超时,使得下载失败,进入编译流程。所以配置国内镜像,能下载到包就可以解决这个问题。有时已经配置了国内源,但还会失败?原因有可能是执行了npm i -f
,-f 参数会强制编译。
解决方法
但这次错误比较特殊,最后发现是 package-lock.json中缺失hasInstallScript配置项,导致npm在install的时候,不管三七二十一,直接就执行了node-gyp rebuild
,跳过了检测脚本。
只要删除掉package-lock.json文件,重新生成就可以了(node v16, npm v8)。
过程分析
hasInstallScript
首先发现问题后,直接对比package-lock文件,发现npm包配置缺少了hasInstallScript信息。
Github上也有相关的Issue:[BUG] package.json has "lockfileVersion": 2 but is missing "hasInstallScript": true · Issue #2606 · npm/cli (github.com)
具体原因可能是用了不同版本node npm,hasInstallScript 是lockfileVersion:2 后加入的,旧版本的npm生成可能会有不兼容的情况。
npm rebuild
那为什么没有hasInstallScript,就一定rebuild,而不执行install 或者 preInstall 中写的脚本呢?这就需要看一下npm代码了, 搜索hasInstallScript关键字,在 cli/workspaces/arborist/lib/arborist/rebuild.js at v8.3.1 · npm/cli · GitHub ,在分析package-lock文件时,如果有配置hasInstallScript,才会去读取package.json文件,拿到preinstall, install, postinstall, prepare,显然,没有配置就会跳过。
const { package: pkg, hasInstallScript } = node.target
const { gypfile, bin, scripts = {} } = pkg
const { preinstall, install, postinstall, prepare } = scripts
const anyScript = preinstall || install || postinstall || prepare
if (!refreshed && !anyScript && (hasInstallScript || this[_oldMeta])) {
set.add(node)
const pkg = await rpj(node.path + '/package.json').catch(() => ({}))
set.delete(node)
const { scripts = {} } = pkg
node.package.scripts = scripts
return this[_addToBuildSet](node, set, true)
}
接着往下看
const isGyp = gypfile !== false &&
!install &&
!preinstall &&
await isNodeGypPackage(node.path)
if (bin || preinstall || install || postinstall || prepare || isGyp) {
if (bin) {
await this[_checkBins](node)
}
if (isGyp) {
scripts.install = defaultGypInstallScript
node.package.scripts = scripts
}
set.add(node)
}
没有hasInstallScript,那就看一下是否是个NodeGyp包,isNodeGypPackage函数实际是检查npm包里是否有binding.gyp,当然文件肯定在的,isGyp = true
。这个包就被设置成install执行默认build脚本,也就是 scripts: { install: 'node-gyp rebuild' }
以下是分析后的node信息:
{
gypfile: undefined,
isGyp: true,
bin: undefined,
preinstall: undefined,
install: undefined,
postinstall: undefined,
prepare: undefined,
pkg: {
version: '0.1.28',
resolved: 'https://registry.npmmirror.com/deasync/-/deasync-0.1.28.tgz',
integrity: 'sha512-QqLF6inIDwiATrfROIyQtwOQxjZuek13WRYZ7donU5wJPLoP67MnYxA6QtqdvdBy2mMqv5m3UefBVdJjvevOYg==',
dev: true,
dependencies: { bindings: '^1.5.0', 'node-addon-api': '^1.7.1' },
engines: { node: '>=0.11.0' },
name: 'deasync',
_id: 'deasync@0.1.28',
scripts: { install: 'node-gyp rebuild' }
}
}