或許能幫你解開 node-sass 的所有疑問?

配圖源自 Freepik

原文鏈接

那個是不是 node-sass 的安裝就能難倒一批前端同學(xué),看完這篇文章,就能解開所有 node-sass 安裝疑惑?

開頭先總結(jié)幾點:

  • 新項目首選 Dart Sass(即 sass 包),Node Sass 不再提供 CSS 新特性的支持。

  • 盡管 Node Sass 的性能目前最佳,但它被棄用已成事實。而且跟 Dart Sass 的性能差異,相信 99% 的同學(xué)都無感知。

  • 大家都知道改為國內(nèi)鏡像源,可以加快 npm 包的下載。但 node-sass 包較為特殊,在安裝時還要從 GitHub 中下載對應(yīng)平臺的 binding.node 文件,因此還要將 Sass Binary Site 指定為國內(nèi)鏡像源使其也在國內(nèi)鏡像源中下載,才能徹底解決網(wǎng)絡(luò)不穩(wěn)定導(dǎo)致安裝失敗的問題。

  • node-sass 基于 LibSass 構(gòu)建,后者使用 C++ 開發(fā)。因此需要用到 node-gyp,在較新版本的 Node.js 中會自帶 node-gyp,因此大部分情況下無需額外安裝。

  • node-gyp 是 GYP 的 Node 實現(xiàn),是用來編譯 C++ 模塊的跨平臺工具。而 GYP 是基于 Python 開發(fā),所以需要安裝 Python。

  • node-gyp 除了需要安裝 Python 之外,在不同平臺還要安裝其他一些東西,比如 macOS 的 Xcode、Windows 的 VC++ 編譯器等,下文會介紹。

  • 在 Node 中調(diào)用其他語言編寫的模塊,需要用 node-gyp 生成平臺相關(guān)的項目文件,然后調(diào)用 gcc、vsbuild、xcode 等編譯平臺來進(jìn)行編譯。

  • node-gyp 構(gòu)建項目文件的過程中,需要指定 Python 路徑,在未配置的情況下,默認(rèn)從環(huán)境變量 PATH 查找名為 python2 的可執(zhí)行文件,找不到就會報錯。通常做法是用 npm config set python /path/to/your/python 去指定,特別是本機(jī)有多個 Python 版本。

  • 鏡像源問題只能解決下載慢的問題,如果 node-sass 還安裝失敗,原因無非就幾個:

    • 一是,未安裝平臺相關(guān)的編譯器。比如 macOS 的 Xcode 等。
    • 二是,當(dāng)前 node-sass 與 Node 版本不兼容,這個版本對應(yīng)關(guān)系可以在 node-sass 官網(wǎng)中查看;
    • 三是,當(dāng)前 node-sass 所依賴的 node-gyp 不支持你本機(jī)安裝的 Python 版本,可根據(jù)實際情況降低/升級 Python 解決。

一、前言

自誕生以來,CSS 在語法上都較為簡單。隨著 Web 的飛速發(fā)展,Web 項目越來越復(fù)雜,原生 CSS 在應(yīng)對復(fù)雜項目的時候似乎力不從心。后來社區(qū)上出現(xiàn)了很多 CSS 預(yù)處理器(CSS preprocessor),比如 Sass、Less、Stylus、PostCSS 等。它們提供了原生 CSS 不具備的特性,比如代碼混合、嵌套選擇器、繼承選擇器等,使得 CSS 更容易維護(hù)。CSS 預(yù)處理器可以理解為一門新的語言,都有著特定的語法,然后通過對應(yīng)的編譯器生成瀏覽器可識別的原生 CSS。

1.1 Sass 與其他預(yù)處理器的區(qū)別

此處不討論語法上的差異。Less 和 Stylus 的編譯器都是使用 JavaScript 編寫的。而 Sass 則經(jīng)歷了 Ruby Sass、Node Sass、Dart Sass 三代編譯器,且都不是基于 JavaScript 編寫的。

1.2 Sass 編譯器

  • Ruby Sass:基于 Ruby 語言編寫,性能最差,于 2019 年停止維護(hù)。
  • Node Sass:基于 LibSass 構(gòu)建并與 Node.js 進(jìn)行集成,而 LibSass 是用 C++ 編寫的。于 2020 年宣布不再提供新特性的支持。
  • Dart Sass:基于 Dart 語言編寫,Dart 是 Flutter 的編程語言,它可以編譯為 JavaScript。

Node Sass 性能最佳,Dart Sass 次之,Ruby Sass 最拉。盡管 Node Sass 的性能最佳,但由于 LibSass 跟不上 CSS 及 Sass 快速發(fā)展的步伐,所以 Sass 團(tuán)隊決定放棄它,全面擁抱 Dart Sass。

1.3 Node Sass 與 Dart Sass 如何選擇?

新項目首選 Dart Sass,這也是 Sass 團(tuán)隊所推薦的。由于 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 構(gòu)建在 LibSass 之上,LibSass 則是用 C++ 實現(xiàn)的,因此使用 node-sass 的話,node-gyp 是必需的。node-gypGYP 在 Node 中的實現(xiàn),用來編譯原生 C++ 模塊的。其中 node-gyp 在較新版本的 Node.js 是自帶的。

2.2 node-gyp 正常運(yùn)行的前提

使用 node-gyp之前,要安裝對應(yīng)平臺的相關(guān)工具才能正常使用。更多安裝介紹請看: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 執(zhí)行以下命令以安裝 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 默認(rèn)鏡像源 https://registry.npmjs.org/ 在境外,訪問的時候慢或者不穩(wěn)定是正常的。這個可以掛梯子或者修改為國內(nèi)鏡像源解決。

比如,修改為淘寶鏡像源:

$ npm config set registry https://registry.npmmirror.com/
$ yarn config set reigstry https://registry.npmmirror.com/

如果是管理鏡像源,個人推薦使用 nrmyrm。

2.5 為什么修改為國內(nèi)鏡像源還慢,甚至失???

node-sasspackage.json 中,我們可以看到有兩個命令:

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

所以在安裝依賴的時候,會先后執(zhí)行 install、postinstall 對應(yīng)命令,它們所做的事情大致是:

  1. 下載對應(yīng)平臺的 binding.node 文件;
  2. 下載完成,執(zhí)行 node-gyp rebuild 命令進(jìn)行構(gòu)建。

script/index.js 會執(zhí)行一個 checkAndDownloadBinary() 方法,以檢查是否有緩存。若無,繼續(xù)執(zhí)行一個 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('/');
}

從代碼可知,先后順序是:

  • 命令行參數(shù) --sass-binary-site
  • 環(huán)境變量 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。

因此,僅僅指定國內(nèi)鏡像源還不夠,還要指定 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 參數(shù)指定,比如:npm install node-sass --sass-binary-site=https://npmmirror.com/mirrors/node-sass。
  • 可以設(shè)置 SASS_BINARY_SITE 環(huán)境變量,有兩種方式:
    • 全局環(huán)境變量(持久化),比如 echo 'export SASS_BINARY_SITE=https://npmmirror.com/mirrors/node-sass' >> ~/.zshrc
    • 臨時環(huán)境變量,每次安裝的時候指定。比如 SASS_BINARY_SITE=https://npmmirror.com/mirrors/node-sass npm install。

總的來講,在 .npmrc.yarnrc 中指定個人認(rèn)為是最合適的。

三、問題排查

完成以上步驟之后,安裝 node-sass 還是不成功?

3.1 檢查 Node 版本

先檢查當(dāng)前 node-sass 版本所支持的 Node 版本,然后在安裝對應(yīng)的 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。而系統(tǒng)內(nèi)置的 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

安裝完之后,再將它的路徑設(shè)置到 .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 還不行?

那我想,問題多半是出現(xiàn)在這個過程中:

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

它無非就是通過 Node 提供 child_process.spawn 去執(zhí)行 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);
  });
}

執(zhí)行的命令行類似 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 就是先后執(zhí)行 node-gyp clean、node-gyp configurenode-gyp build 三條命令而已。絕大多數(shù)問題,可能會出現(xiàn)在 node-gyp configure 上,也就是生成對應(yīng)平臺的項目構(gòu)建文件。

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)

看樣子出現(xiàn)在 node-gyp configure 過程上,然后追溯到 node-gyp/lib/configure.js 中的 findPython() 方法上,其路徑取決于:

var python = gyp.opts.python || process.env.PYTHON || 'python2'

其中 gyp.opts 可以是 Command Options 或者是 .npmrc 中的對應(yīng)配置。由于執(zhí)行 node-gyp rebuild 時沒有傳遞 --python 參數(shù),.npmrc 中沒有設(shè)置 python 配置,因此默認(rèn)使用 python2。但由于我本機(jī)的環(huán)境變量 PATH 的路徑中并沒有名為 python2 的可執(zhí)行文件,因此報錯了。

解決方法思路很簡單:

  • 如果是較新版本的 node-sass,它對應(yīng)的 node-gyp 版本也較新,此時應(yīng)首選安裝 Python 3。
  • 如果當(dāng)前 node-sass 版本僅支持 Python 2.x,那么安裝該版本就行。

安裝完之后,設(shè)置 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 執(zhí)行代碼時候提示語法錯誤,因為 print "xxx" 是 Python2 的語法,而 Python3 的語法應(yīng)該是 print("xxx")。因此,我們可以猜到是目前 node-sass 所依賴的 node-gyp 版本過低,后者用的是 Python2 語法實現(xiàn)的。

解決思路很簡單,安裝 Python2 并將其路徑添加到 npm 配置中來,具體操作不展開贅述,前文已介紹過了。

3.5 示例分析三

前面安裝完成之后,執(zhí)行 yarn start 構(gòu)建項目的時候,出現(xiàn)問題:

$ 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)
  );
}

對應(yīng) 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 架構(gòu),因此報錯了。相關(guān) Issue:Apple ARM Support #3033。

解決方法是降低 Node 版本,比如:

$ fnm use 12

然后為什么 Node 12 沒問題呢?翻查源碼 node-sass/lib/binding.js 發(fā)現(xiàn):

/**
 * 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_DIR、 SASS_BINARY_NAME 來指定。如果沒有指定,其默認(rèn)值由 platform-arch-versions.modules 組成(如 darwin-x64-72),前面兩個好理解,后面那個應(yīng)該是由 Node Module 對應(yīng)組成。

在使用 Node 12 的時候,本地可以找到 node_modules/node-sass/vendor/darwin-x64-72/binding.node 文件,因此跳過了 isSupportedEnvironment() 的檢查,所以降低 Node 版本也是解決方法之一。當(dāng)使用 Node 16 的時候,本地沒有 node_modules/node-sass/vendor/darwin-arm64-93/binding.node 文件,因此跑去校驗平臺、架構(gòu)去了,但由于本機(jī)是 ARM 架構(gòu)的 Mac,而前面代碼所示是不支持 arm64 架構(gòu)的,因此就報錯了。

解決方法是,前往 GitHub 下載對應(yīng)版本的 binding.node 文件至本地,然后通過 SASS_BINARY_PATH、SASS_BINARY_DIR、 SASS_BINARY_NAME 來指定該路徑(具體配置方法請看:Binary configuration parameters)。

盡管至今 Node Sass 還未支持 ARM 架構(gòu),但 ARM Mac 在使用 Node 12 時,對應(yīng)的 darwin-x64-72/binding.node 是沒問題的,因此我猜下載 darwin-x64-xx_binding.node 也是 OK 的,沒親測,有興趣可以自行嘗試。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容