或许能帮你解开 node-sass 的所有疑问?
那个是不是 node-sass
的安装就能难倒一批前端同学,看完这篇文章,就能解开所有 node-sass 安装疑惑?
开头先总结几点:
新项目首选 Dart Sass(即
sass
包),Node Sass 不再提供 CSS 新特性的支持。尽管 Node Sass 的性能目前最佳,但它被弃用已成事实。而且跟 Dart Sass 的性能差异,相信 99% 的同学都无感知。
大家都知道改为国内镜像源,可以加快 npm 包的下载。但
node-sass
包较为特殊,在安装时还要从 GitHub 中下载对应平台的binding.node
文件,因此还要将 Sass Binary Site 指定为国内镜像源使其也在国内镜像源中下载,才能彻底解决网络不稳定导致安装失败的问题。node-sass
基于 LibSass 构建,后者使用 C++ 开发。因此需要用到node-gyp
,在较新版本的 Node.js 中会自带node-gyp
,因此大部分情况下无需额外安装。node-gyp
是 GYP 的 Node 实现,是用来编译 C++ 模块的跨平台工具。而 GYP 是基于 Python 开发,所以需要安装 Python。node-gyp
除了需要安装 Python 之外,在不同平台还要安装其他一些东西,比如 macOS 的 Xcode、Windows 的 VC++ 编译器等,下文会介绍。在 Node 中调用其他语言编写的模块,需要用
node-gyp
生成平台相关的项目文件,然后调用 gcc、vsbuild、xcode 等编译平台来进行编译。在
node-gyp
构建项目文件的过程中,需要指定 Python 路径,在未配置的情况下,默认从环境变量PATH
查找名为python2
的可执行文件,找不到就会报错。通常做法是用npm config set python /path/to/your/python
去指定,特别是本机有多个 Python 版本。镜像源问题只能解决下载慢的问题,如果
node-sass
还安装失败,原因无非就几个:- 一是,未安装平台相关的编译器。比如 macOS 的 Xcode 等。
- 二是,当前
node-sass
与 Node 版本不兼容,这个版本对应关系可以在node-sass
官网中查看; - 三是,当前
node-sass
所依赖的node-gyp
不支持你本机安装的 Python 版本,可根据实际情况降低/升级 Python 解决。
一、前言
自诞生以来,CSS 在语法上都较为简单。随着 Web 的飞速发展,Web 项目越来越复杂,原生 CSS 在应对复杂项目的时候似乎力不从心。后来社区上出现了很多 CSS 预处理器(CSS preprocessor),比如 Sass、Less、Stylus、PostCSS 等。它们提供了原生 CSS 不具备的特性,比如代码混合、嵌套选择器、继承选择器等,使得 CSS 更容易维护。CSS 预处理器可以理解为一门新的语言,都有着特定的语法,然后通过对应的编译器生成浏览器可识别的原生 CSS。
1.1 Sass 与其他预处理器的区别
此处不讨论语法上的差异。Less 和 Stylus 的编译器都是使用 JavaScript 编写的。而 Sass 则经历了 Ruby Sass、Node Sass、Dart Sass 三代编译器,且都不是基于 JavaScript 编写的。
1.2 Sass 编译器
- Ruby Sass:基于 Ruby 语言编写,性能最差,于 2019 年停止维护。
- Node Sass:基于 LibSass 构建并与 Node.js 进行集成,而 LibSass 是用 C++ 编写的。于 2020 年宣布不再提供新特性的支持。
- Dart Sass:基于 Dart 语言编写,Dart 是 Flutter 的编程语言,它可以编译为 JavaScript。
Node Sass 性能最佳,Dart Sass 次之,Ruby Sass 最拉。尽管 Node Sass 的性能最佳,但由于 LibSass 跟不上 CSS 及 Sass 快速发展的步伐,所以 Sass 团队决定放弃它,全面拥抱 Dart Sass。
1.3 Node Sass 与 Dart Sass 如何选择?
新项目首选 Dart Sass,这也是 Sass 团队所推荐的。由于 Node Sass 不再支持新特性,未来逐步被淘汰是很自然的事。
Dart Sass 提供了纯 JavaScript 的 npm 包 sass
(以前叫做 dart-sass
),它的安装可比 node-sass
省心多了,
从 Node Sass 迁移到 Dart Sass 也非常简单,只要把 package.json
中的 node-sass
依赖改为 sass
即可,两者提供的 JavaScript API 是相同的。
二、node-sass
再次提醒大家,不要再用 Node Sass 了:
Warning: LibSass and Node Sass are deprecated. While they will continue to receive maintenance releases indefinitely, there are no plans to add additional features or compatibility with any new CSS or Sass features. Projects that still use it should move onto Dart Sass.
2.1 node-gyp
由于 node-sass
构建在 LibSass 之上,LibSass 则是用 C++ 实现的,因此使用 node-sass
的话,node-gyp
是必需的。node-gyp
是 GYP 在 Node 中的实现,用来编译原生 C++ 模块的。其中 node-gyp
在较新版本的 Node.js 是自带的。
2.2 node-gyp 正常运行的前提
使用 node-gyp
之前,要安装对应平台的相关工具才能正常使用。更多安装介绍请看:node-gyp Installation。
Linux/Unix 平台:
- Python 3.x
- make
- A proper C/C++ compiler toolchain, like GCC
macOS 平台:
- Python 3.x
- XCode Command Line Tools
Windows 平台:
- Python
- VC++ 编译器
以 macOS 为例,这里不全量安装 XCode,只要安装 XCode Command Line Tools 即可。
# 安装 XCode Command Line Tools $ xcode-select --install # 安装 Python 3 $ brew install python
以 Windows 为例,先 Microsoft Store 下载安装 Python,接着以管理员身份打开 cmd 或 PowerShell 执行以下命令以安装 VC++ 编译器(更多请看 Environment setup and configuration)。
$ npm install --global --production windows-build-tools
2.3 指定 Python 版本
如果你安装了多个 Python 版本,可在 npm 或 yarn 的配置文件中指定。以 macOS 为例:
# 获取 Python 路径 $ which python3 /usr/local/bin/python3 # 配置 npm 或 yarn 的 python 路径 $ npm config set python /usr/local/bin/python3 $ yarn config set python /usr/local/bin/python3
请注意,低版本 node-gyp
可能仅支持 Python 2.x。
2.4 node-sass 安装慢是怎么回事?
首先是镜像源的问题,它不单是 node-sass
包才这样,所有包都一样。由于 npm 默认镜像源 https://registry.npmjs.org/
在境外,访问的时候慢或者不稳定是正常的。这个可以挂梯子或者修改为国内镜像源解决。
比如,修改为淘宝镜像源:
$ npm config set registry https://registry.npmmirror.com/ $ yarn config set reigstry https://registry.npmmirror.com/
2.5 为什么修改为国内镜像源还慢,甚至失败?
在 node-sass
的 package.json
中,我们可以看到有两个命令:
{ "scripts": { "install": "node scripts/install.js", "postinstall": "node scripts/build.js" } }
所以在安装依赖的时候,会先后执行 install
、postinstall
对应命令,它们所做的事情大致是:
- 下载对应平台的
binding.node
文件; - 下载完成,执行
node-gyp rebuild
命令进行构建。
在 script/index.js
会执行一个 checkAndDownloadBinary()
方法,以检查是否有缓存。若无,继续执行一个 download()
方法在指定 URL 中下载 binding.node
文件,而 URL 则通过 getBinaryUrl()
方法获取:
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('/'); }
从代码可知,先后顺序是:
- 命令行参数
--sass-binary-site
- 环境变量
SASS_BINARY_SITE
.npmrc
配置sass_binary_site
package.json
中的nodeSass.binarySite
字段- 若以上都没有指定,则从 Github 中下载,比如:
https://github.com/sass/node-sass/releases/download/v8.0.0/darwin-x64-83_binding.node
。
因此,仅仅指定国内镜像源还不够,还要指定 Sass Binary Site,方式有以上四种。
2.6 指定 Sass Binary Site
首先,从上面的 getBinaryUrl()
方法可知,可以有多种方式去指定,但个人推荐在 .npmrc
或 .yarnrc
中指定:
$ npm config set sass_binary_site https://npmmirror.com/mirrors/node-sass $ yarn config set sass_binary_site https://npmmirror.com/mirrors/node-sass
这样的话,其 binding.node
文件就会从 https://npmmirror.com/mirrors/node-sass/v8.0.0/darwin-x64-83_binding.node
下载,就不会龟速那么慢了。
还可以这样:
- 如果是使用命令行,可以在
--sass-binary-site
参数指定,比如:npm install node-sass --sass-binary-site=https://npmmirror.com/mirrors/node-sass
。 - 可以设置
SASS_BINARY_SITE
环境变量,有两种方式:- 全局环境变量(持久化),比如
echo 'export SASS_BINARY_SITE=https://npmmirror.com/mirrors/node-sass' >> ~/.zshrc
。 - 临时环境变量,每次安装的时候指定。比如
SASS_BINARY_SITE=https://npmmirror.com/mirrors/node-sass npm install
。
- 全局环境变量(持久化),比如
总的来讲,在 .npmrc
或 .yarnrc
中指定个人认为是最合适的。
三、问题排查
完成以上步骤之后,安装 node-sass
还是不成功?
3.1 检查 Node 版本
先检查当前 node-sass
版本所支持的 Node 版本,然后在安装对应的 Node 版本重试。详见 Node version support policy。
像我项目中 node-sass
版本号为 4.13.0
,使用 Node 16 就不行,因此我降到了 Node 12。
$ fnm install 12 $ fnm use 12
Node 多版本管理的话,个人推荐使用 fnm
。
除了 Node 版本过高之外,该版本仅支持 Python 2。而系统内置的 Python 2.x 在 macOS 12.3 之后被移除了,而且目前 Homebrew 不再支持安装 Python 2。然后参考这篇文章,找到了一个安装 Python 2 的方法,如下:
$ brew install pyenv $ pyenv install 2.7.18 $ echo 'export PATH="$(pyenv root)/shims:${PATH}"' >> ~/.zshrc $ source ~/.zshrc $ pyenv global 2.7.18
安装完之后,再将它的路径设置到 .npmrc
里面。
$ python --version Python 2.7.18 $ which python /Users/frankie/.pyenv/shims/python $ npm config set python /Users/frankie/.pyenv/shims/python $ yarn config set python /Users/frankie/.pyenv/shims/python
3.2 还不行?
那我想,问题多半是出现在这个过程中:
{ "scripts": { "postinstall": "node scripts/build.js" } }
它无非就是通过 Node 提供 child_process.spawn
去执行 Shell 命令。
function build(options) { var args = [require.resolve(path.join('node-gyp', 'bin', 'node-gyp.js')), 'rebuild', '--verbose'].concat( ['libsass_ext', 'libsass_cflags', 'libsass_ldflags', 'libsass_library'].map(function(subject) { return ['--', subject, '=', process.env[subject.toUpperCase()] || ''].join(''); })).concat(options.args); console.log('Building:', [process.execPath].concat(args).join(' ')); var proc = spawn(process.execPath, args, { stdio: [0, 1, 2] }); proc.on('exit', function(errorCode) { if (!errorCode) { afterBuild(options); return; } if (errorCode === 127 ) { console.error('node-gyp not found!'); } else { console.error('Build failed with error code:', errorCode); } process.exit(1); }); }
执行的命令行类似 node_modules/node-gyp/bin/node-gyp.js rebuild --verbose --libsass_ext= --libsass_cflags= --libsass_ldflags= --libsass_library=
。也就是 node-gyp rebuild
命令。也就是下面这个:
Command | Description |
---|---|
help | Shows the help dialog |
build | Invokes make /msbuild.exe and builds the native addon |
clean | Removes the build directory if it exists |
configure | Generates project build files for the current platform |
rebuild | Runs clean , configure and build all in a row |
install | Installs Node.js header files for the given version |
list | Lists the currently installed Node.js header versions |
remove | Removes the Node.js header files for the given version |
所以 node-gyp rebuild
就是先后执行 node-gyp clean
、node-gyp configure
、node-gyp build
三条命令而已。绝大多数问题,可能会出现在 node-gyp configure
上,也就是生成对应平台的项目构建文件。
3.3 示例分析一
yarn install v1.22.19
[1/5] Validating package.json...
[2/5] Resolving packages...
[3/5] Fetching packages...
[4/5] Linking dependencies...
warning " > styled-jsx@3.2.3" has incorrect peer dependency "react@15.x.x || 16.x.x".
warning "zent > react-beautiful-dnd > react-motion@0.5.2" has incorrect peer dependency "react@^0.14.9 || ^15.3.0 || ^16.0.0".
warning "zent > react-beautiful-dnd > react-redux@5.1.1" has incorrect peer dependency "react@^0.14.0 || ^15.0.0-0 || ^16.0.0-0".
[5/5] Building fresh packages...
[-/3] ⠁ waiting...
[2/3] ⠁ fsevents
error /Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/node-sass: Command failed.
Exit code: 1
Command: node scripts/build.js
Arguments:
Directory: /Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/node-sass
Output:
Building: /usr/local/bin/node /Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/node-gyp/bin/node-gyp.js rebuild --verbose --libsass_ext= --libsass_cflags= --libsass_ldflags= --libsass_library=
gyp info it worked if it ends with ok
gyp verb cli [
gyp verb cli '/usr/local/bin/node',
gyp verb cli '/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/node-gyp/bin/node-gyp.js',
gyp verb cli 'rebuild',
gyp verb cli '--verbose',
gyp verb cli '--libsass_ext=',
gyp verb cli '--libsass_cflags=',
gyp verb cli '--libsass_ldflags=',
gyp verb cli '--libsass_library='
gyp verb cli ]
gyp info using node-gyp@3.8.0
gyp info using node@16.15.0 | darwin | arm64
gyp verb command rebuild []
gyp verb command clean []
gyp verb clean removing "build" directory
gyp verb command configure []
gyp verb check python checking for Python executable "python2" in the PATH
gyp verb `which` failed Error: not found: python2
gyp verb `which` failed at getNotFoundError (/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/which/which.js:13:12)
gyp verb `which` failed at F (/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/which/which.js:68:19)
gyp verb `which` failed at E (/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/which/which.js:80:29)
gyp verb `which` failed at /Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/which/which.js:89:16
gyp verb `which` failed at /Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/isexe/index.js:42:5
gyp verb `which` failed at /Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/isexe/mode.js:8:5
gyp verb `which` failed at FSReqCallback.oncomplete (node:fs:198:21)
gyp verb `which` failed python2 Error: not found: python2
gyp verb `which` failed at getNotFoundError (/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/which/which.js:13:12)
gyp verb `which` failed at F (/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/which/which.js:68:19)
gyp verb `which` failed at E (/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/which/which.js:80:29)
gyp verb `which` failed at /Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/which/which.js:89:16
gyp verb `which` failed at /Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/isexe/index.js:42:5
gyp verb `which` failed at /Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/isexe/mode.js:8:5
gyp verb `which` failed at FSReqCallback.oncomplete (node:fs:198:21) {
gyp verb `which` failed code: 'ENOENT'
gyp verb `which` failed }
gyp verb check python checking for Python executable "python" in the PATH
gyp verb `which` failed Error: not found: python
gyp verb `which` failed at getNotFoundError (/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/which/which.js:13:12)
gyp verb `which` failed at F (/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/which/which.js:68:19)
gyp verb `which` failed at E (/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/which/which.js:80:29)
gyp verb `which` failed at /Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/which/which.js:89:16
gyp verb `which` failed at /Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/isexe/index.js:42:5
gyp verb `which` failed at /Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/isexe/mode.js:8:5
gyp verb `which` failed at FSReqCallback.oncomplete (node:fs:198:21)
gyp verb `which` failed python Error: not found: python
gyp verb `which` failed at getNotFoundError (/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/which/which.js:13:12)
gyp verb `which` failed at F (/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/which/which.js:68:19)
gyp verb `which` failed at E (/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/which/which.js:80:29)
gyp verb `which` failed at /Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/which/which.js:89:16
gyp verb `which` failed at /Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/isexe/index.js:42:5
gyp verb `which` failed at /Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/isexe/mode.js:8:5
gyp verb `which` failed at FSReqCallback.oncomplete (node:fs:198:21) {
gyp verb `which` failed code: 'ENOENT'
gyp verb `which` failed }
gyp ERR! configure error
gyp ERR! stack Error: Can't find Python executable "python", you can set the PYTHON env variable.
gyp ERR! stack at PythonFinder.failNoPython (/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/node-gyp/lib/configure.js:484:19)
gyp ERR! stack at PythonFinder.<anonymous> (/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/node-gyp/lib/configure.js:406:16)
gyp ERR! stack at F (/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/which/which.js:68:16)
gyp ERR! stack at E (/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/which/which.js:80:29)
gyp ERR! stack at /Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/which/which.js:89:16
gyp ERR! stack at /Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/isexe/index.js:42:5
gyp ERR! stack at /Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/isexe/mode.js:8:5
gyp ERR! stack at FSReqCallback.oncomplete (node:fs:198:21)
gyp ERR! System Darwin 22.3.0
gyp ERR! command "/usr/local/bin/node" "/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/node-gyp/bin/node-gyp.js" "rebuild" "--verbose" "--libsass_ext=" "--libsass_cflags=" "--libsass_ldflags=" "--libsass_library="
gyp ERR! cwd /Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/node-sass
gyp ERR! node -v v16.15.0
gyp ERR! node-gyp -v v3.8.0
gyp ERR! not ok
...
然后可以快速锁定到这几行:
Building: /usr/local/bin/node /Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/node-gyp/bin/node-gyp.js rebuild --verbose --libsass_ext= --libsass_cflags= --libsass_ldflags= --libsass_library=
gyp info it worked if it ends with ok
gyp verb cli [
gyp verb cli '/usr/local/bin/node',
gyp verb cli '/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/node-gyp/bin/node-gyp.js',
gyp verb cli 'rebuild',
gyp verb cli '--verbose',
gyp verb cli '--libsass_ext=',
gyp verb cli '--libsass_cflags=',
gyp verb cli '--libsass_ldflags=',
gyp verb cli '--libsass_library='
gyp verb cli ]
gyp info using node-gyp@3.8.0
gyp info using node@16.15.0 | darwin | arm64
gyp verb command rebuild []
gyp verb command clean []
gyp verb clean removing "build" directory
gyp verb command configure []
gyp verb check python checking for Python executable "python2" in the PATH
gyp verb `which` failed Error: not found: python2
gyp verb `which` failed at getNotFoundError (/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/which/which.js:13:12)
gyp verb `which` failed at F (/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/which/which.js:68:19)
gyp verb `which` failed at E (/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/which/which.js:80:29)
gyp verb `which` failed at /Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/which/which.js:89:16
gyp verb `which` failed at /Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/isexe/index.js:42:5
gyp verb `which` failed at /Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/isexe/mode.js:8:5
gyp verb `which` failed at FSReqCallback.oncomplete (node:fs:198:21)
看样子出现在 node-gyp configure
过程上,然后追溯到 node-gyp/lib/configure.js
中的 findPython()
方法上,其路径取决于:
var python = gyp.opts.python || process.env.PYTHON || 'python2'
其中 gyp.opts
可以是 Command Options 或者是 .npmrc
中的对应配置。由于执行 node-gyp rebuild
时没有传递 --python
参数,.npmrc
中没有设置 python
配置,因此默认使用 python2
。但由于我本机的环境变量 PATH
的路径中并没有名为 python2
的可执行文件,因此报错了。
解决方法思路很简单:
- 如果是较新版本的
node-sass
,它对应的node-gyp
版本也较新,此时应首选安装 Python 3。 - 如果当前
node-sass
版本仅支持 Python 2.x,那么安装该版本就行。
安装完之后,设置 npm 或 yarn 配置,那么它能从 var python = gyp.opts.python
中获取 Python 的路径了。以 macOS 为例:
# 若是 Python 2,则是 which python $ which python3 /usr/local/bin/python3 $ npm config set python /usr/local/bin/python3 $ yarn config set python /usr/local/bin/python3
3.4 示例分析二
yarn install v1.22.19
[1/5] Validating package.json...
[2/5] Resolving packages...
[3/5] Fetching packages...
[4/5] Linking dependencies...
warning " > styled-jsx@3.2.3" has incorrect peer dependency "react@15.x.x || 16.x.x".
warning "zent > react-beautiful-dnd > react-motion@0.5.2" has incorrect peer dependency "react@^0.14.9 || ^15.3.0 || ^16.0.0".
warning "zent > react-beautiful-dnd > react-redux@5.1.1" has incorrect peer dependency "react@^0.14.0 || ^15.0.0-0 || ^16.0.0-0".
[5/5] Building fresh packages...
[-/3] ⠂ waiting...
[2/3] ⠂ fsevents
warning Error running install script for optional dependency: "/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/fsevents: Command failed.
Exit code: 1
Command: node install
Arguments:
Directory: /Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/fsevents
Output:
node-pre-gyp info it worked if it ends with ok
node-pre-gyp info using node-pre-gyp@0.12.0
node-pre-gyp info using node@12.22.12 | darwin | x64
node-pre-gyp WARN Using request for node-pre-gyp https download
node-pre-gyp info check checked for \"/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/fsevents/lib/binding/Release/node-v72-darwin-x64/fse.node\" (not found)
node-pre-gyp http GET https://fsevents-binaries.s3-us-west-2.amazonaws.com/v1.2.9/fse-v1.2.9-node-v72-darwin-x64.tar.gz
node-pre-gyp http 403 https://fsevents-binaries.s3-us-west-2.amazonaws.com/v1.2.9/fse-v1.2.9-node-v72-darwin-x64.tar.gz
node-pre-gyp WARN Tried to download(403): https://fsevents-binaries.s3-us-west-2.amazonaws.com/v1.2.9/fse-v1.2.9-node-v72-darwin-x64.tar.gz
node-pre-gyp WARN Pre-built binaries not found for fsevents@1.2.9 and node@12.22.12 (node-v72 ABI, unknown) (falling back to source compile with node-gyp)
node-pre-gyp http 403 status code downloading tarball https://fsevents-binaries.s3-us-west-2.amazonaws.com/v1.2.9/fse-v1.2.9-node-v72-darwin-x64.tar.gz
gyp info it worked if it ends with ok
gyp info using node-gyp@3.8.0
gyp info using node@12.22.12 | darwin | x64
gyp info ok
gyp info it worked if it ends with ok
gyp info using node-gyp@3.8.0
gyp info using node@12.22.12 | darwin | x64
gyp ERR! configure error
gyp ERR! stack Error: Command failed: /usr/bin/python3 -c import sys; print \"%s.%s.%s\" % sys.version_info[:3];
gyp ERR! stack File \"<string>\", line 1
gyp ERR! stack import sys; print \"%s.%s.%s\" % sys.version_info[:3];
gyp ERR! stack ^
gyp ERR! stack SyntaxError: invalid syntax
gyp ERR! stack
gyp ERR! stack at ChildProcess.exithandler (child_process.js:308:12)
gyp ERR! stack at ChildProcess.emit (events.js:314:20)
gyp ERR! stack at maybeClose (internal/child_process.js:1022:16)
gyp ERR! stack at Process.ChildProcess._handle.onexit (internal/child_process.js:287:5)
gyp ERR! System Darwin 22.3.0
gyp ERR! command \"/Users/frankie/Library/Application Support/fnm/node-versions/v12.22.12/installation/bin/node\" \"/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/node-gyp/bin/node-gyp.js\" \"configure\" \"--fallback-to-build\" \"--module=/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/fsevents/lib/binding/Release/node-v72-darwin-x64/fse.node\" \"--module_name=fse\" \"--module_path=/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/fsevents/lib/binding/Release/node-v72-darwin-x64\" \"--napi_version=8\" \"--node_abi_napi=napi\" \"--napi_build_version=0\" \"--node_napi_label=node-v72\" \"--python=/usr/bin/python3\"
gyp ERR! cwd /Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/fsevents
gyp ERR! node -v v12.22.12
gyp ERR! node-gyp -v v3.8.0
gyp ERR! not ok
node-pre-gyp ERR! build error
node-pre-gyp ERR! stack Error: Failed to execute '/Users/frankie/Library/Application Support/fnm/node-versions/v12.22.12/installation/bin/node /Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/node-gyp/bin/node-gyp.js configure --fallback-to-build --module=/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/fsevents/lib/binding/Release/node-v72-darwin-x64/fse.node --module_name=fse --module_path=/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/fsevents/lib/binding/Release/node-v72-darwin-x64 --napi_version=8 --node_abi_napi=napi --napi_build_version=0 --node_napi_label=node-v72 --python=/usr/bin/python3' (1)
node-pre-gyp ERR! stack at ChildProcess.<anonymous> (/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/fsevents/node_modules/node-pre-gyp/lib/util/compile.js:83:29)
node-pre-gyp ERR! stack at ChildProcess.emit (events.js:314:20)
node-pre-gyp ERR! stack at maybeClose (internal/child_process.js:1022:16)
node-pre-gyp ERR! stack at Process.ChildProcess._handle.onexit (internal/child_process.js:287:5)
node-pre-gyp ERR! System Darwin 22.3.0
node-pre-gyp ERR! command \"/Users/frankie/Library/Application Support/fnm/node-versions/v12.22.12/installation/bin/node\" \"/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/fsevents/node_modules/node-pre-gyp/bin/node-pre-gyp\" \"install\" \"--fallback-to-build\"
node-pre-gyp ERR! cwd /Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/fsevents
node-pre-gyp ERR! node -v v12.22.12
node-pre-gyp ERR! node-pre-gyp -v v0.12.0
node-pre-gyp ERR! not ok
我们可以快速定位到:
gyp ERR! configure error
gyp ERR! stack Error: Command failed: /usr/bin/python3 -c import sys; print \"%s.%s.%s\" % sys.version_info[:3];
gyp ERR! stack File \"<string>\", line 1
gyp ERR! stack import sys; print \"%s.%s.%s\" % sys.version_info[:3];
gyp ERR! stack ^
gyp ERR! stack SyntaxError: invalid syntax
简单来说,就是使用 Python3 执行代码时候提示语法错误,因为 print "xxx"
是 Python2 的语法,而 Python3 的语法应该是 print("xxx")
。因此,我们可以猜到是目前 node-sass
所依赖的 node-gyp
版本过低,后者用的是 Python2 语法实现的。
解决思路很简单,安装 Python2 并将其路径添加到 npm 配置中来,具体操作不展开赘述,前文已介绍过了。
3.5 示例分析三
前面安装完成之后,执行 yarn start
构建项目的时候,出现问题:
$ yarn start
yarn run v1.22.19
$ webpack-dev-server --env.NODE_ENV=development --hot
wds: Project is running at http://0.0.0.0:3001/
wds: webpack output is served from /
wds: Content not from webpack is served from /Users/frankie/Web/ifanr/ifanr-wxlayout-editor/dist/
Browserslist: caniuse-lite is outdated. Please run next command `yarn upgrade`
[styled-jsx] Loading plugin from path: styled-jsx-plugin-sass
wdm: Hash: baa097cdaf43c729d164
Version: webpack 4.41.2
Time: 455ms
Built at: 2023/02/07 18:21:57
Asset Size Chunks Chunk Names
./index.html 812 bytes [emitted]
assets/main.baa097cd.js 923 KiB main [emitted] [immutable] [big] main
Entrypoint main [big] = assets/main.baa097cd.js
[0] multi (webpack)-dev-server/client?http://0.0.0.0:3001 (webpack)/hot/dev-server.js ./src/index.tsx 52 bytes {main} [built]
[./node_modules/strip-ansi/index.js] 161 bytes {main} [built]
[./node_modules/webpack-dev-server/client/index.js?http://0.0.0.0:3001] (webpack)-dev-server/client?http://0.0.0.0:3001 4.29 KiB {main} [built]
[./node_modules/webpack-dev-server/client/overlay.js] (webpack)-dev-server/client/overlay.js 3.51 KiB {main} [built]
[./node_modules/webpack-dev-server/client/socket.js] (webpack)-dev-server/client/socket.js 1.53 KiB {main} [built]
[./node_modules/webpack-dev-server/client/utils/createSocketUrl.js] (webpack)-dev-server/client/utils/createSocketUrl.js 2.89 KiB {main} [built]
[./node_modules/webpack-dev-server/client/utils/log.js] (webpack)-dev-server/client/utils/log.js 964 bytes {main} [built]
[./node_modules/webpack-dev-server/client/utils/reloadApp.js] (webpack)-dev-server/client/utils/reloadApp.js 1.59 KiB {main} [built]
[./node_modules/webpack-dev-server/client/utils/sendMessage.js] (webpack)-dev-server/client/utils/sendMessage.js 402 bytes {main} [built]
[./node_modules/webpack/hot sync ^\.\/log$] (webpack)/hot sync nonrecursive ^\.\/log$ 170 bytes {main} [built]
[./node_modules/webpack/hot/dev-server.js] (webpack)/hot/dev-server.js 1.59 KiB {main} [built]
[./node_modules/webpack/hot/emitter.js] (webpack)/hot/emitter.js 75 bytes {main} [built]
[./node_modules/webpack/hot/log-apply-result.js] (webpack)/hot/log-apply-result.js 1.27 KiB {main} [built]
[./node_modules/webpack/hot/log.js] (webpack)/hot/log.js 1.34 KiB {main} [built]
[./src/index.tsx] 1.21 KiB {main} [built] [failed] [1 error]
+ 20 hidden modules
ERROR in ./src/index.tsx
Module build failed (from ./node_modules/babel-loader/lib/index.js):
Error: /Users/frankie/Web/ifanr/ifanr-wxlayout-editor/src/index.tsx: Node Sass does not yet support your current environment: OS X Unsupported architecture (arm64) with Unsupported runtime (93)
For more information on which environments are supported please see:
https://github.com/sass/node-sass/releases/tag/v4.13.0
at module.exports (/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/node-sass/lib/binding.js:13:13)
at Object.<anonymous> (/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/node-sass/lib/index.js:14:35)
at Module._compile (node:internal/modules/cjs/loader:1105:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1159:10)
at Module.load (node:internal/modules/cjs/loader:981:32)
at Function.Module._load (node:internal/modules/cjs/loader:822:12)
at Module.require (node:internal/modules/cjs/loader:1005:19)
at require (node:internal/modules/cjs/helpers:102:18)
at Object.<anonymous> (/Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/styled-jsx-plugin-sass/index.js:1:14)
at Module._compile (node:internal/modules/cjs/loader:1105:14)
Child html-webpack-plugin for "index.html":
1 asset
Entrypoint undefined = ./index.html
[./node_modules/html-webpack-plugin/lib/loader.js!./src/index.html] 980 bytes {0} [built]
[./node_modules/lodash/lodash.js] 528 KiB {0} [built]
[./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 472 bytes {0} [built]
[./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 497 bytes {0} [built]
wdm: Failed to compile.
Node Sass does not yet support your current environment: OS X Unsupported architecture (arm64) with Unsupported runtime (93)
可以追查到 node-sass/lib/extensions.js
的 isSupportedEnvironment()
方法:
function isSupportedEnvironment(platform, arch, abi) { return ( false !== getHumanPlatform(platform) && false !== getHumanArchitecture(arch) && false !== getHumanNodeVersion(abi) ); }
对应 getHumanArchitecture()
方法如下:
function getHumanArchitecture(arch) { switch (arch || process.arch) { case 'ia32': return '32-bit'; case 'x86': return '32-bit'; case 'x64': return '64-bit'; default: return false; } }
并不支持 arm64
架构,因此报错了。相关 Issue:Apple ARM Support #3033。
解决方法是降低 Node 版本,比如:
$ fnm use 12
然后为什么 Node 12 没问题呢?翻查源码 node-sass/lib/binding.js
发现:
/** * Require binding */ module.exports = function (ext) { if (!ext.hasBinary(ext.getBinaryPath())) { if (!ext.isSupportedEnvironment()) { throw new Error(errors.unsupportedEnvironment()); } else { throw new Error(errors.missingBinary()); } } return require(ext.getBinaryPath()); };
首先 ext.hasBinary(ext.getBinaryPath())
会指定目录查找是否存在 binding.node
文件,指定路径类似: /Users/frankie/Web/ifanr/ifanr-wxlayout-editor/node_modules/node-sass/vendor/darwin-x64-72/binding.node
。这个路径可以通过 SASS_BINARY_PATH
、SASS_BINARY_DIR
、 SASS_BINARY_NAME
来指定。如果没有指定,其默认值由 platform-arch-versions.modules
组成(如 darwin-x64-72
),前面两个好理解,后面那个应该是由 Node Module 对应组成。
在使用 Node 12 的时候,本地可以找到 node_modules/node-sass/vendor/darwin-x64-72/binding.node
文件,因此跳过了 isSupportedEnvironment()
的检查,所以降低 Node 版本也是解决方法之一。当使用 Node 16 的时候,本地没有 node_modules/node-sass/vendor/darwin-arm64-93/binding.node
文件,因此跑去校验平台、架构去了,但由于本机是 ARM 架构的 Mac,而前面代码所示是不支持 arm64 架构的,因此就报错了。
解决方法是,前往 GitHub 下载对应版本的 binding.node
文件至本地,然后通过 SASS_BINARY_PATH
、SASS_BINARY_DIR
、 SASS_BINARY_NAME
来指定该路径(具体配置方法请看:Binary configuration parameters)。
尽管至今 Node Sass 还未支持 ARM 架构,但 ARM Mac 在使用 Node 12 时,对应的 darwin-x64-72/binding.node
是没问题的,因此我猜下载 darwin-x64-xx_binding.node
也是 OK 的,没亲测,有兴趣可以自行尝试。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: Git 操作与解决方法
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论