
那個是不是 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-gyp 是 GYP 在 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/
2.5 為什么修改為國內(nèi)鏡像源還慢,甚至失???
在 node-sass 的 package.json 中,我們可以看到有兩個命令:
{
"scripts": {
"install": "node scripts/install.js",
"postinstall": "node scripts/build.js"
}
}
所以在安裝依賴的時候,會先后執(zhí)行 install、postinstall 對應(yīng)命令,它們所做的事情大致是:
- 下載對應(yīng)平臺的
binding.node文件; - 下載完成,執(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 configure、node-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.js 的 isSupportedEnvironment() 方法:
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_PATH、SASS_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 的,沒親測,有興趣可以自行嘗試。