Знакома ситуация, когда решаешь запустить проект на новеньком ноутбуке, но видишь в консоли страшное:
npm ERR! code 1
npm ERR! path /Users/username/Desktop/my-project/node_modules/bcrypt
npm ERR! command failed
npm ERR! command sh -c node-pre-gyp install --fallback-to-build
Что за node-gyp?
Node.js написан на C++, а мы пишем код на JavaScript. Большинство пакетов в npm собирается в чистый JS. Но иногда JavaScript недостаточно быстр (например, для криптографии, обработки изображений или работы с сетью на низком уровне). В таких случаях разработчики пакетов пишут код на C++, чтобы он работал максимально быстро.
Чтобы Node.js понял этот C++ код, его нужно скомпилировать под конкретную операционную систему и архитектуру процессора.
node-gyp (Generate Your Projects) - это инструмент, который берет исходный код на C++ внутри npm-пакета и компилирует его в бинарный файл (.node), который уже может использовать JavaScript.
node-gyp сам ничего не компилирует. Он написан на Python и использует компиляторы операционной системы. То есть это своеобразный диспетчер, связывающий несколько точек для получения результата.
Поэтому при установке проекта на чистый ноутбук node-gyp падает по двум причинам:
- Нет Python. (Сам инструмент не может запуститься).
- Нет компилятора C++. (Инструмент запустился, но ему нечем собирать код).
Обойти ошибку можно с помощью флага --ignore-scripts:
npm install --ignore-scripts
Зависимости установятся, но нескомпилированный код, конечно же, работать не будет. Однако такая возможность может быть полезна. Например, если нужно починить что-то другое, или пакет нужен только для типов.
Жизненный цикл установки зависимостейЧтобы понять нюансы, нужно заглянуть в жизненный цикл пакетных менеджеров.
Когда делаешь npm install <пакет>, запускаются так называемые скрипты жизненного цикла (lifecycle scripts), описанные в package.json этого пакета:
preinstall: Скрипт выполняется до установки пакета. Обычно используется для проверок (например, проверяет версию Node.js).install/build: Здесь пакет может скачивать дополнительные бинарники или компилировать код.postinstall: Выполняется после того, как пакет распакован.
Именно на этапе postinstall или install чаще всего запускается node-gyp rebuild.
Многие современные пакеты (например, bcrypt из лога в начале статьи) используют утилиты вроде node-pre-gyp или prebuild-install.
В фазе postinstall такой пакет сначала пытается скачать уже готовый, скомпилированный бинарник под текущую ОС и архитектуру процессора.
При запуске npm install, утилита node-pre-gyp смотрит на систему, формирует ссылку и скачивает нужный файл напрямую с серверов авторов. Это сделано специально, чтобы избавить от необходимости ставить компиляторы и ждать долгой сборки C++ кода.
Если скачивание не удалось (нет интернета, редкая ОС, старая версия пакета), пакет делает fallback и вызывает node-gyp rebuild (отсюда и флаг --fallback-to-build в логе ошибки). И вот тут появляется ошибка отсутствия Python или компилятора.
Получается, флаг --ignore-scripts просто запрещает запуск preinstall, install и postinstall во всем дереве зависимостей. Установка пройдет на удивление быстро и без ошибок node-gyp, однако нативные модули просто не будут работать.
Разница в пакетных менеджерах и работа с кэшем
Пакетные менеджеры работают с нативными модулями по-разному, и это порождает свои нюансы.
NPMОн складывает всё в node_modules и кэширует скачанные архивы (но не собранные бинарники!). Если сборка упала, NPM может оставить битые артифакты в папке. Для того чтобы очистить кэш и удалить битые артифакты, можно использовать следующие команды:
rm -rf node_modules
rm package-lock.json
npm cache clean --force
npm install
Yarn
Yarn более агрессивно работает с кэшем. Иногда он запоминает, что модуль не собрался, и при повторном yarn install может сразу выдать ошибку, даже не пытаясь собрать снова. Это решается очисткой кэша и удалением папки node_modules.
yarn cache clean
rm -rf node_modules yarn.lock
yarn install
PNPM
PNPM использует жесткие ссылки и единое хранилище на жестком диске. И с node-gyp тут скрывается главная опасность.
Если PNPM собрал нативный модуль один раз, он положит бинарный файл в свое общее хранилище и будет шарить его между всеми проектами. Но если обновить версию Node.js, старый бинарник перестанет работать (об этом ниже). В PNPM придется принудительно заставить его пересобрать модули:
pnpm rebuild
Другие проблемы и решения
Если Python и C++ настроены, но node-gyp все равно сыплет ошибками, стоит обратить внимание на следующие нюансы:
Нативные C++ модули очень чувствительны к версии движка V8 (на котором работает Node.js).
Например, старый проект использует node-sass версии 4.x. Эта версия умеет собираться только на Node.js 14 и ниже. Покупаешь новый ноутбук, ставишь свежую Node.js 20, запускаешь npm install — и всё взрывается огромным логом C++ ошибок.
Решение: Использовать NVM (Node Version Manager) или FNM, чтобы переключаться на старую версию Node.js при работе с легаси проектами. Либо обновлять зависимости до современных аналогов.
Проблема Apple Silicon (M1/M2/M3)При запуске старого проекта на новом Mac, node-gyp может пытаться собрать модуль под архитектуру x64, хотя процессор arm64. Зачастую это лечится обновлением пакета до актуальной версии или запуском терминала в режиме Rosetta.
Особенно актуально для Windows. Если назвать пользователя "Павел", путь будет выглядеть как C:\Users\Павел\Appdata.... node-gyp не понимает кириллицу и пробелы в путях. Стоит убедиться, что Python и сам проект лежат в директориях с английскими названиями без пробелов.
Если Python установлен, но npm его не видит, можно указать путь явно:
npm config set python /path/to/executable/python3
Если коротко
Ошибки node-gyp это сигнал о том, что проект использует нативные модули напрямую, и ему не хватает инструментов для их сборки.
Обычно для быстрого решения проблемы обычно достаточно:
- Поставить менеджер версий Node.js.
- Поставить компилятор C++ (в зависимости от системы: Visual Studio Build Tools / Xcode-select / build-essential).
- Убедиться, что терминал видит команду
python3. - Удалить
node_modulesи lock-файлы, а также очистить кэш пакетов, если до этого были неудачные попытки. - Запустить
npm install.