node

或许能帮你解开 node-sass 的所有疑问?

2023-02-04  本文已影响0人  越前君
配图源自 Freepik

那个是不是 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 编译器

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-gypGYP 在 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/

如果是管理镜像源,个人推荐使用 nrmyrm

2.5 为什么修改为国内镜像源还慢,甚至失败?

node-sasspackage.json 中,我们可以看到有两个命令:

{
  "scripts": {
    "install": "node scripts/install.js",
    "postinstall": "node scripts/build.js"
  }
}

所以在安装依赖的时候,会先后执行 installpostinstall 对应命令,它们所做的事情大致是:

  1. 下载对应平台的 binding.node 文件;
  2. 下载完成,执行 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,方式有以上四种。

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 cleannode-gyp configurenode-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 的可执行文件,因此报错了。

解决方法思路很简单:

安装完之后,设置 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.jsisSupportedEnvironment() 方法:

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_PATHSASS_BINARY_DIRSASS_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_PATHSASS_BINARY_DIRSASS_BINARY_NAME 来指定该路径(具体配置方法请看:Binary configuration parameters)。

尽管至今 Node Sass 还未支持 ARM 架构,但 ARM Mac 在使用 Node 12 时,对应的 darwin-x64-72/binding.node 是没问题的,因此我猜下载 darwin-x64-xx_binding.node 也是 OK 的,没亲测,有兴趣可以自行尝试。

上一篇下一篇

猜你喜欢

热点阅读