RE:NODE

Cannot find module @rollup/rollup-linux-x64-gnu: как исправить

Автор Команда RE:NODE
13 мин чтения


Error: Cannot find module @rollup/rollup-linux-x64-gnu означает, что нативный бинарный файл Rollup для вашей платформы так и не был установлен. Rollup 4 поставляет свой парсер отдельным скомпилированным пакетом для каждой платформы, npm ставит только подходящий, и есть три причины, по которым нужный пакет не доезжает: package-lock.json, созданный в другой операционной системе версией npm с известной ошибкой в optional-зависимостях; папка node_modules, скопированная с другой машины (очень часто прямо в образ Docker); либо установка с отключёнными optional-зависимостями. В подавляющем большинстве случаев помогает то, что Rollup советует сам: удалите и node_modules, и package-lock.json, снова выполните npm install и закоммитьте новый lockfile. Остальная часть статьи о том, как сделать это, ничего не сломав, и как не получить ту же ошибку при следующем деплое.

Что на самом деле говорит ошибка#

Полное сообщение выдаёт загрузчик самого Rollup, и оно на удивление честное:

code
Error: Cannot find module @rollup/rollup-linux-x64-gnu. npm has a bug relatedto optional dependencies (https://github.com/npm/cli/issues/4828). Please try`npm i` again after removing both package-lock.json and node_modules directory.

Самое важное здесь - имя пакета в начале. Оно говорит, на какой платформе, по мнению Rollup, он запущен, а значит, какой бинарный файл он искал. Имя складывается из трёх частей: операционная система, архитектура процессора и, в Linux, библиотека C.

Суффикс пакетаГде встречается
linux-x64-gnuОбычные серверы Linux, образы Debian и Ubuntu, большинство CI-раннеров
linux-x64-muslAlpine Linux, включая образы node:*-alpine
linux-arm64-gnuARM-серверы, Docker на Apple Silicon, 64-разрядная Raspberry Pi
linux-arm64-muslAlpine на ARM
darwin-arm64 / darwin-x64macOS на Apple Silicon / Intel
win32-x64-msvcWindows на x64
win32-arm64-msvcWindows на ARM

Есть и другие - FreeBSD, RISC-V, s390x, 32-разрядный ARM и так далее, - но таблица покрывает почти все случаи, которые вам попадутся. Суффикс сам по себе уже диагноз. Если ваш сервер - обычная машина с Linux на x64, а ошибка называет linux-x64-musl, значит, вы работаете внутри Alpine, хотите вы того или нет. Если вы на Windows, а ошибка называет win32-x64-msvc, в игру вступил чужой lockfile.

В Linux Rollup выбирает между gnu и musl во время выполнения, запрашивая у Node отчёт о процессе: если в отчёте есть версия glibc, это gnu, иначе musl. Настроить это нельзя, и переменной окружения этот выбор тоже не переопределить.

Зачем Rollup вообще нужен бинарный файл и почему его приносит Vite#

Rollup 3 был чистым JavaScript. В Rollup 4, вышедшем в октябре 2023 года, JavaScript-парсер заменили парсером SWC, скомпилированным из Rust в нативный модуль для Node. Парсинг стал заметно быстрее, но появилась проблема, общая для всех нативных пакетов: один скомпилированный файл не может работать на всех платформах.

Rollup использует схему, общую для esbuild, SWC, Lightning CSS, @tailwindcss/oxide из Tailwind CSS 4 и пакетов @img/* у sharp: публикуется по одному крошечному пакету на каждую платформу, и все они перечисляются в optionalDependencies главного пакета. Каждый платформенный пакет в своём package.json указывает, какую операционную систему и процессор он поддерживает (а для Linux-сборок ещё и какую библиотеку C):

node_modules/@rollup/rollup-linux-x64-gnu/package.json (excerpt)
{  "name": "@rollup/rollup-linux-x64-gnu",  "os": ["linux"],  "cpu": ["x64"],  "libc": ["glibc"],  "main": "./rollup.linux-x64-gnu.node"}

Когда npm устанавливает rollup, он видит два десятка с лишним optional-зависимостей, сверяет os и cpu каждой с текущей машиной, молча пропускает неподходящие и ставит подходящую. Optional значит, что сбой или несоответствие не считаются ошибкой. Именно это позволяет схеме работать на разных платформах, и именно поэтому при поломке всё происходит молча: npm не сообщает, что пропустил единственный нужный вам пакет.

Большинство людей Rollup специально не ставит. Он приходит вместе с Vite. Vite 5, 6 и 7 зависят от Rollup 4 при production-сборке, поэтому ошибка появляется на vite build, как правило в CI-задаче или на сервере и редко - на ноутбуке, где проект создавали. Переход с Vite 4 на Vite 5 стал для многих проектов первой встречей с этой ошибкой: Vite 4 ещё использовал Rollup 3 и не имел собственных платформенных бинарных файлов, кроме esbuild.

Vite 8 заменяет Rollup на Rolldown, который работает по тому же принципу под другими именами: @rolldown/binding-linux-x64-gnu и аналогичные. Если вы на Vite 8 и видите от Rolldown ошибку об отсутствующем нативном модуле, всё из этой статьи применимо - меняются только названия пакетов.

Ошибка npm, стоящая за большинством случаев#

Проблема, на которую ссылается Rollup, npm/cli#4828, открыта в апреле 2022 года и описывает следующее: когда npm пересоздаёт package-lock.json при уже существующей папке node_modules, он строит lockfile по тому, что установлено, а не по полным метаданным зависимостей. На Mac это значит, что в lockfile попадает @rollup/rollup-darwin-arm64 и ни одного другого платформенного пакета, потому что их там никогда не ставили.

Затем этот lockfile коммитят. В Linux npm ci ставит ровно то, что перечислено в lockfile, - а Linux-бинарного файла там нет - и завершается без единого предупреждения. Первый признак беды - сборка.

rm lockfile, npm installкоммитнет linux-x64-gnuНоутбук (macOS)есть node_modulespackage-lock.jsonтолько darwin-arm64Git-репозиторийСервер Linuxnpm civite buildнет бинарного файла
Как lockfile с Mac ломает сборку на Linux

Обычный повод невинен: кто-то удаляет только lockfile, чтобы решить конфликт слияния или убрать предупреждение о peer-зависимостях, запускает npm install при оставшейся node_modules и коммитит результат. То же бывает и на Windows - так lockfile, созданные в Linux, ломают работу разработчикам на Windows с win32-x64-msvc.

Ситуация с исправлением неоднозначная. По сообщениям в issue, свежие релизы npm 11 больше не отбрасывают остальные платформы для пакетов, которые объявляют их все как optional-зависимости, как раз случай Rollup, но другие сообщения описывают возвращение проблемы в более поздних релизах 11.x. Не рассчитывайте, что какая-то конкретная версия npm всё починит. Рассчитывайте на lockfile, который вы проверили сами.

Убедитесь в причине, прежде чем что-то менять#

Две команды покажут, какая из трёх причин ваша. Сначала на машине, где сборка падает, спросите у npm, установлен ли бинарный файл:

bash
$ npm ls @rollup/rollup-linux-x64-gnumy-app@1.0.0 /srv/app└── (empty)

Пустой результат значит, что npm не установил его нигде в дереве зависимостей. Затем посмотрите на сам lockfile и посчитайте записи о платформах:

bash
$ grep -c '"node_modules/@rollup/rollup-' package-lock.json1

В здоровом lockfile перечислены все платформы, которые публикует Rollup, так что число около двадцати, в зависимости от версии Rollup. Один-два - это та самая ошибка npm: lockfile создали на одной машине, и он знает бинарный файл только этой машины. Чтобы увидеть, какой именно ему известен:

bash
$ grep '"node_modules/@rollup/rollup-' package-lock.json    "node_modules/@rollup/rollup-darwin-arm64": {

Если lockfile полный, а пакета всё равно нет, дело не в lockfile. Смотрите, как node_modules попала на машину и не отключены ли optional-зависимости - оба случая разобраны в следующих разделах.

Решение: правильно пересоздайте lockfile#

На машине, где вы обычно меняете зависимости:

bash
$ rm -rf node_modules package-lock.json$ npm install$ grep -c '"node_modules/@rollup/rollup-' package-lock.json$ git add package-lock.json$ git commit -m "fix: regenerate lockfile with all platform binaries"

В Windows в PowerShell первая строка выглядит так: Remove-Item -Recurse -Force node_modules, package-lock.json. Важна именно очистка node_modules. Удаление одного лишь lockfile при оставшейся node_modules - это как раз то, что запускает ошибку.

У этого решения есть цена, и о ней стоит сказать прямо. Без lockfile npm install заново разрешает все диапазоны версий из package.json по тому, что опубликовано сегодня. ^5.4.0, закреплённый на 5.4.2, может превратиться в 5.4.19. Для большинства проектов это безвредно, но и не пустяк: прочитайте diff lockfile, прогоните тесты и сборку и выкатывайте это как обновление зависимостей, а не как правку в одну строку. Если обновление нужно сделать меньше, в следующем разделе есть более узкие варианты.

На сервере ничего не меняется, кроме того, что теперь он получает правильный lockfile. Продолжайте использовать там npm ci - он ставит ровно то, что написано в lockfile, а теперь это именно то, что нужно. В статье npm ci vs npm install объясняется, почему сервер никогда не должен быть машиной, которая пишет lockfile. Если у вас возникло искушение перевести сервер на npm install, чтобы ошибка ушла, не делайте этого: при существующем сломанном lockfile он часто всё равно пропустит бинарный файл, а когда не пропускает - потому что переписал дерево зависимостей прямо на сервере.

Более узкие решения, когда нельзя пересоздать всё#

Иногда полный повторный resolve недопустим: релизная ветка, заморозка на время аудита, монорепозиторий, где один lockfile обслуживает сорок пакетов. Есть два более узких варианта, у обоих есть компромиссы.

Объявите недостающий бинарный файл сами. Добавьте платформенный пакет в optionalDependencies в своём package.json ровно той же версии, что и rollup в вашем lockfile:

package.json
{  "optionalDependencies": {    "@rollup/rollup-linux-x64-gnu": "4.40.0"  }}

Затем выполните npm install, чтобы lockfile это записал. Поскольку пакет optional, на Mac npm его пропустит, а в Linux установит. Подвох в версии: бинарный файл должен соответствовать релизу Rollup, к которому он относится, а когда Vite в следующий раз подтянет более новый Rollup, ваша привязка устареет. Считайте это временной мерой и уберите её, как только правильно пересоздадите lockfile.

Используйте WebAssembly-сборку Rollup. Rollup публикует @rollup/wasm-node - тот же сборщик, но с парсером, скомпилированным в WebAssembly вместо нативного кода. Платформенных пакетов у него нет вовсе, так что весь этот класс ошибок исчезает. Направьте на него каждую копию rollup в дереве с помощью override в npm:

package.json
{  "overrides": {    "rollup": "npm:@rollup/wasm-node"  }}

После добавления выполните npm install. Платить придётся скоростью: парсинг на WebAssembly заметно медленнее нативного, что важно для больших сборок и почти не чувствуется на маленьких. Это разумный выбор для платформ, для которых у Rollup нет нативной сборки, и для команд, которые предпочтут медленную сборку хрупкой.

node_modules, скопированная между машинами, и Docker#

Вторая причина к ошибке npm не имеет отношения. Папка node_modules, установленная на одной платформе, содержит бинарный файл этой платформы и больше ничего, поэтому при переносе на другую платформу новая машина остаётся без своего бинарного файла. Lockfile при этом может быть идеальным - и всё равно так случится.

Типичные способы, как это происходит:

  • Загрузка папки проекта вместе с `node_modules` по SFTP или zip-архивом. Папка, установленная на Mac, содержит darwin-arm64 и ничего для Linux.
  • Коммит `node_modules` в Git. Её всегда нужно держать в .gitignore: она большая, зависит от платформы и за секунды восстанавливается из lockfile.
  • `COPY . .` в Docker после `npm ci`. Классика. Образ ставит правильный Linux-бинарный файл, а следующей строкой поверх него копируется node_modules с вашего ноутбука.

Шаблон Dockerfile, который этого избегает, сначала копирует только файлы манифеста, устанавливает зависимости внутри образа, а исходный код копирует потом:

Dockerfile
FROM node:22-bookworm-slim AS buildWORKDIR /appCOPY package.json package-lock.json ./RUN npm ciCOPY . .RUN npm run build

Одного этого мало, потому что COPY . . всё равно скопирует локальную node_modules, если она есть. Остановит это файл .dockerignore рядом с Dockerfile:

.dockerignore
node_modulesdist.git

Он же ускоряет сборку, ведь Docker перестаёт отправлять сотни мегабайт контекста сборки, который всё равно был бы выброшен.

У окружений разработки на Docker Compose есть своя версия этой проблемы. Bind mount вроде .:/app заменяет /app из образа папкой вашего проекта, включая node_modules с ноутбука. Обычное решение - анонимный том для этой папки, который сохраняет собственную копию контейнера:

compose.yaml
services:  web:    build: .    volumes:      - .:/app      - /app/node_modules

Более общие приёмы разобраны в статьях Docker на VDS и Docker Compose для небольших стеков.

Alpine, musl и ARM#

Образы на основе Alpine используют musl вместо glibc, поэтому Rollup запрашивает linux-x64-musl. При полном lockfile и установке внутри образа Alpine всё просто работает: npm выбирает сборку для musl. Ломается это тогда, когда установка шла в другом месте: node_modules, собранная на Debian или на CI-раннере и скопированная в стадию Alpine, содержит linux-x64-gnu, а Rollup внутри Alpine его не загрузит. Правило то же, что и в остальных случаях: запускайте npm ci в том же образе, или хотя бы на той же базе, что и сама сборка.

Если у вас нет веской причины использовать Alpine, образ на Debian, например node:22-bookworm-slim, убирает целую категорию неприятных сюрпризов с нативными модулями ценой чуть большего размера образа.

С ARM люди сталкиваются через Apple Silicon. Docker на Mac с процессором серии M по умолчанию собирает образы linux/arm64, поэтому установка внутри сборки подтягивает linux-arm64-gnu. Такой образ не запустится на x64-сервере по многим причинам, не связанным с Rollup. Собирайте под целевую платформу явно:

bash
$ docker build --platform linux/amd64 -t my-app .

Есть похожий сбой, который не является ошибкой npm. Если в трассировке стека встречается что-то вроде GLIBC_2.xx not found, значит, бинарный файл установлен, но библиотека C в системе слишком старая, чтобы его загрузить, - обычно это древний образ для сборки. Обновите базовый образ, а не переустанавливайте пакеты.

Не отключайте optional-зависимости#

Третья причина - собственная ошибка. npm ci --omit=optional, устаревший --no-optional, omit=optional в .npmrc или NPM_CONFIG_OMIT=optional в окружении CI - всё это велит npm пропускать все optional-зависимости, включая бинарный файл Rollup, который optional лишь в том смысле, что npm должен выбрать один из них. Тогда Rollup падает точно так же, как при ошибке npm.

Такие настройки обычно приходят из старого руководства по уменьшению образов или из обходного пути для сломанного скрипта установки какого-нибудь другого пакета. Проверьте, что npm использует на самом деле:

bash
$ npm config get omit

Он должен ничего не вывести или вывести dev, если вы намеренно пропускаете dev-зависимости. Если в выводе есть optional, проблема в этом. Отметьте и отдельную ловушку: Vite и Rollup - это devDependencies, поэтому npm ci --omit=dev с последующим vite build тоже упадёт, но с vite: not found, а не с этой ошибкой. Установите всё, соберите проект, а затем очистите через npm prune --omit=dev.

На хостинге приложений#

На контейнерном хостинге действуют те же правила, а единственное реальное решение - где выполняется сборка. На RE:NODE тарифы для приложений Node.js деплоятся из GitHub через Git deploy в панели, и если ваша команда запуска выполняет npm ci и сборку, они идут на сервере - так что lockfile со всеми платформами Rollup и есть всё решение: закоммитьте его и задеплойте снова. Консоль панели показывает вывод установки и сборки без фильтрации, поэтому отсутствующий бинарный файл проявляется как ошибка выше, а не как тихий сбой, - на что смотреть, описано в статье чтение консоли. Не загружайте локальную node_modules через файловый менеджер или SFTP - пусть npm ci соберёт её на сервере.

Если сервер постоянно перезапускается, потому что сборка падает внутри команды запуска, исправьте lockfile, прежде чем перезапускать снова. Наблюдатель считает аварийные перезапуски: три за час - на странице сервера появляется предупреждение и создаётся тикет, шесть - сервер приостанавливается. Команду запуска и последовательность «сборка, затем очистка» разбирает статья деплой приложения Node из GitHub. Если ваш проект на Vite собирается в обычные статические файлы, Node в рантайме вам вообще не нужен - соберите его в CI и раздавайте папку dist, об этом рассказывает статья хостинг статических сайтов.

FAQ#

Почему сборка работает на моём ноутбуке, но не на сервере?

На ноутбуке есть бинарный файл для его собственной платформы, он был установлен при первом npm install. Серверу нужен другой, и либо lockfile его не перечисляет, либо что-то помешало npm его установить. Проверка grep -c выше покажет, что именно.

Поможет ли удаление node_modules на сервере?

Нет, если проблема в lockfile. npm ci и так удаляет node_modules и ставит всё заново по lockfile, поэтому lockfile без Linux-бинарного файла каждый раз даёт один и тот же результат. Lockfile нужно пересоздать и закоммитить.

Стоит ли ставить @rollup/rollup-linux-x64-gnu как обычную зависимость?

Нет. Обычная зависимость не optional, поэтому установка на любом Mac и Windows сразу завершится ошибкой EBADPLATFORM. Если нужна временная мера, добавьте пакет в optionalDependencies точно той же версии, что и Rollup.

Избавляет ли от этого pnpm или Yarn?

Конкретная ошибка lockfile - это ошибка npm, поэтому смена менеджера пакетов часто её убирает. Копирование node_modules между платформами и отключение optional-зависимостей ломают любой менеджер пакетов одинаково, так что совет про Docker и .dockerignore остаётся в силе.

Это та же причина, что стоит за ошибками платформ у esbuild и SWC?

Да, механизм тот же: по одному optional-пакету на каждую платформу. Сообщение esbuild другое - там сказано, что вы установили esbuild для другой платформы, чем та, которой пользуетесь, - но способы решения те же: полный lockfile, установка на целевой платформе и никаких скопированных node_modules.