我们在安装依赖的时候,经常会附带安装node-sass,而node-sass会经常安装报错,一般都是gyp的error
我们来分析下安装过程
安装过程
- npm下载到node-sass这个包
-
触发了node-sass这个包的install的钩子,或者说生命周期,然后执行了install.js
-
install.js里会尝试去手动下载它的一个c++模块,也就是一个.node文件,他会先尝试去环境里获取这个c++模块的镜像地址,如果你配置了镜像地址的话,优先从你的镜像地址下载
- 而这个sass.getBinaryUrl方法里会尝试用各种手段获取镜像
比如
-
--sass-binary-site
就是命令行里的参数 -
process.env.SASS_BINARY_SITE
就是环境变量里的 -
npm_config_sass_binary_site
,就是.npmrc文件里的sass_binary_site
,.npmrc里面的变量会自动被注入到临时环境变量里,转换成以npm_config带头的变量名 -
pkg.nodeSassConfig
或者pkg.nodeSassConfig.binarySite
则是写在package.json中的镜像地址
所以我们在网上经常能搜到的解决方案就是安装报错时,去.npmrc文件中配置镜像,使得该binary可以下载到,一般是这么配置
sass_binary_site=https://www.npmmirror.com/mirrors/node-sass
如果这些都没有配置的话,就有一个默认的github的下载地址 https://github.com/sass/node-sass/releases/download,这个地址现在点进去看是不存在的,因为路径还没拼完
从上方的图里大概可以猜到,完整地址应该是
https://github.com/sass/node-sass/releases/download/v版本号/binary的名字
现在我们缺少版本号和bindary名字版本号很容易得知,就是安装的node-sass的版本号
-
后面的binaryName,直接看下图框起来的部分即可,因为正常情况下图里其他的
if
条件都不会满足,正常情况下不会特地去配这些变量
-
binaryName我们可以根据图里的规则自己拼下,直接在自己电脑上获取下, 输入
node -p [process.platform,process.arch,process.versions.modules].join('-')"
所以完整下载地址,应该是https://github.com/sass/node-sass/releases/download/v4.14.1/win32-x64-108_binding.node,也就是说node-sass在安装过程中会拼出这个地址来,然后从这个地址去下载binary
但是会发现,这个地址点进去404了,不存在该下载地址
-
因为node-sass,在4.14.1版本,没有win32-x64-108这个型号的.node文件,我们去它github的release里找到这个4.14.1的版本号,再往下找assets,会发现,最多就到83,没有到108的
所以即便你配置了阿里云镜像,也没用,因为这个版本号对应的.node文件就那么少
-
而下载不到.node文件时,不会报错,但是会触发另一个hook,postinstall,这是完成安装后触发的
在这个build.js里他会去各种判断binary是否存在,然后尝试编译一行sass语法,如果有任何报错,都会尝试重新本地build出这个.node文件
-
而这个build函数里,我们会发现,他需要用node-gyp编译,而node-gyp需要python环境,没安装的时候就报错了
-
回到上面的话题,再往上翻,我们会发现,只有v8.0.0版本的node-sass,才有到108的binary
但是v8.0.0版本的node-sass,未必能和项目当前的sass-loader能兼容,即便sass-loader能升上去,但项目里用的是webpack4,webpack4未必能和这么高版本的sass-loader兼容
所以我们需要把后面的数值降下去,把win32-x64-108后面的-108给降下去,这个和当前的node版本有关,我现在用的是node18,所以是win32-x64-108
-
使用nvm切换到node16版本,切换后我们再试下刚才的命令
-
可以看到已经降到93了,93的最低要求版本是v6.0.0
然后继续安装依赖,发现还是报错,这.npmrc文件配置了镜像,版本也对了,手动去下载这个.node文件也能下载到,为什么还报错??
-
于是手动把node-sass的git仓库clone下来,加了.npmrc,然后执行npm i,发现是地址不对,然后在关键位置打了log后发现
-
.npmrc里明明配置的是阿里云镜像,为什么这边获取到的是淘宝镜像??
- 经过排查发现,是因为我按照node-sass的官方的readme文件,执行了
npm install -g mirror-config-china
配置镜像地址
后面的--registry配置的是这个mirror-config-china的下载地址,而mirror-config-china下载到了后,都不用手动执行,直接自动往用户目录下的全局.npmrc文件里写入一堆的淘宝镜像地址,这是写死的
淘宝镜像地址2024年证书已经过期了,这个包写入的全是错的,所以把用户目录下的.npmrc文件清空就行了
所以可以分析出,npm install过程中,会先读取项目目录下.npmrc的配置,并转换成npm_config带头的变量,临时写入到shell的环境变量中,然后再读取用户目录下的.npmrc文件,再转临时变量写入shell,此时如果有两个相同的临时变量名,项目里.npmrc里的就会被覆盖
-
清空该文件内容
-
然后安装成功,安装成功是clone下来的node-sass的git仓库安装依赖成功了,并且postinstall也成功了,
此时我们清掉缓存重新回到项目里安装依赖,.node缓存有两个位置,一个当前项目里的vender文件夹里,还有一个是全局的
-
清掉这两个缓存后再回到项目里安装,发现又报了个错,此时怀疑不是node-sass的问题,因为单独把node-sass拎出来是可以的,而且报的错看着和node-sass没啥关系
9886 verbose stack Error: command failed
9886 verbose stack at ChildProcess.<anonymous> (C:\Users\Administrator\AppData\Roaming\nvm\v16.18.1\node_modules\npm\node_modules\@npmcli\promise-spawn\lib\index.js:63:27)
9886 verbose stack at ChildProcess.emit (node:events:513:28)
9886 verbose stack at maybeClose (node:internal/child_process:1100:16)
9886 verbose stack at Process.ChildProcess._handle.onexit (node:internal/child_process:304:5)
9887 verbose pkgid node-sass@6.0.1
报错后回到全局缓存目录查看,发现node-sass的binary是已经下载到了的,因为之前确定把这些都清空了
改成pnpm安装后成功,而npm安装就是失败
再仔细看报错位置,是执行node-sass在执行postinstall的时候,node build.js抛出的错,所以仔细观察build.js这个文件
-
.node文件确定是已经下载到了的,那么也就是说可能是在这段代码里执行报错了
但是再仔细想想也不对,如果执行报错,会走到下面的catch,起码会log输出
Binary has a problem
或者Building the binary locally
这两句话来,可npm error里并没有这些log-
所以只能是前面的代码执行有问题
-
先看第一行if里的options.force,然后找到options.force 是怎么来的,这下破案了
也就是说,
.npmrc
里面如果有force=true,或者install 的命令后面跟着--force或者-f的话,直接会先用node-gyp强行build,而我这个老项目里,因为各种依赖不对等导致安装报错,然后用了 npm install --force,想忽略依赖不对等问题直接安装,然后这个--force刚好被node-sass解析到,尝试强行build一个.node文件,因为没有配置环境,就失败了
而pnpm安装时没有报错,所以没有加--force,单独拉node-sass仓库install时也没有加--force,所以这两个情况下反而可以成功知道是--force命令带来的问题就好办了,把
npm install --force
换成npm install --legacy-peer-deps
即可
总结
node-sass下载不到时,.npmrc文件里面可以配置对应的.node文件的地址,但是要注意node版本和对应的node-sass版本必须匹配,否则即便配置了国内镜像地址也下载不到
千万别看node-sass的readme文件去执行
npm install -g mirror-config-china
,这命令会往全局的.npmrc里写入sass_binary_site为淘宝镜像地址,这个配置会在运行时覆盖项目里的.npmrc,而淘宝镜像的https证书过期了有node-sass依赖的项目,install时别加--force,否则会强行本地编译一个.node文件,也会报错