2026: сборка фронтенда на удалённом Mac —
нативные модули esbuild и SWC, optionalDependencies и матрица решений для CI
Для кого: фулстек и фронтенд, кто гоняет Vite, Next.js, Rspack или кастомные пайплайны на удалённом Mac в CI. Разрыв «у меня собралось» vs «на агенте упало» чаще всего в нативных биндингах esbuild, платформенных пакетах @swc/core, optionalDependencies и несовпадении архитектуры Node / libc++ на macOS. Дальше — таблица симптомов, матрица локально vs CI, команды, ключи кэша, приёмка параллельных джобов и FAQ. Ещё по теме: esbuild и SWC в monorepo, Rspack и воркеры, pnpm и Turborepo, Node и npm на remote Mac. Тарифы, аренда.
01 Таблица соответствия симптомов и причин
Нативный слой не проявляется в TypeScript-ошибках: он ломается при первом вызове трансформера или бандлера. Используйте таблицу как быстрый триаж перед долгим дебагом сети или кода приложения.
| Симптом | Вероятная причина | Первый шаг проверки |
|---|---|---|
dlopen / «wrong architecture» для .node |
Node запущен под одной архитектурой (например x64 в Rosetta), а модуль собран под arm64, или наоборот | node -p "process.arch + ' ' + process.platform" и сравнить с путём внутри node_modules |
Cannot find module для подпакета @swc/core-darwin-* |
optional-платформа не установилась: сбой скрипта postinstall, обрезанный кэш, перенос node_modules с Linux |
Переустановка на целевом Mac; проверить логи npm_config_platform / npm_config_arch |
| Сборка падает только в параллельном matrix, локально стабильна | Разные раннеры (arm vs intel pool), гонка за диском или кэшем Turbo/pnpm store | Зафиксировать pool и добавить в ключ кэша runner.arch + версию Node |
«Symbol not found», ссылки на libc++ |
Бинарник собран под более новый SDK, чем на агенте, или смешение toolchain | Выровнять версию Xcode CLT на удалённом Mac с документированным эталоном |
02 Матрица архитектуры: локально и CI
Паритет — это не «одинаковый package.json», а совпадение набора: бинарная архитектура Node, политика Rosetta, версия macOS для загрузчиком и набор нативных зависимостей, выбранных через optionalDependencies.
| Измерение | Локальный Mac разработчика | Удалённый Mac CI | Риск при расхождении |
|---|---|---|---|
process.arch |
arm64 «из коробки» на Apple Silicon | Может быть x64-раннер или смешанный пул | Неверный вариант esbuild/SWC из optional-пакета |
Источник node_modules |
Долгоживущий каталог, частичные обновления | Часто чистая установка из кэша слоёв | Скрытые артефакты старой платформы в кэше |
| libc++ / SDK | Xcode пользователя | Образ провайдера CI | ABI-мismatch на редких нативных цепочках |
| Конкуренция за CPU | 1–2 параллельных задачи | Несколько шардов + линтер + тесты | Таймауты postinstall, «битые» полуустановки |
Зафиксируйте в README эталон окружения и короткий assert в preinstall — агент падает сразу с диагностикой, а не на 15-й минуте сборки.
03 Чек-лист команд пересборки
Выполняйте на том же раннере, где падает пайплайн. Не копируйте node_modules между хостами с разной ОС.
- Диагностика среды:
node -p "process.version + ' ' + process.arch + ' ' + process.platform" which node xcodebuild -version 2>/dev/null | head -n 1
- Полная переустановка зависимостей (npm):
rm -rf node_modules npm cache verify npm ci
- Точечная пересборка нативных модулей без полного rm (если менялся только toolchain):
npm rebuild esbuild npm rebuild @swc/core --verbose
- pnpm (монорепо):
pnpm store prune rm -rf node_modules **/node_modules pnpm install --frozen-lockfile
- Проверка, какой бинарник реально грузится:
node -e "console.log(require('esbuild').execPath)"
find node_modules -name '*.node' -path '*esbuild*' | head
04 Ключи кэша и очистка node_modules
Кэш CI должен включать всё, что влияет на выбор optional-пакета: версия Node, архитектура, хэш lock-файла, идентификатор образа macOS. Иначе вы восстановите «чужой» node_modules и получите дрейф, который локально невоспроизводим.
- GitHub Actions пример ключа:
node-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ runner.os }}-${{ runner.arch }} - Turbo remote cache: отдельный префикс для веток с экспериментальным Node 22 vs LTS 20, чтобы не смешивать артефакты.
- Очистка: при смене пула раннеров инвалидируйте кэш установки целиком один раз; дальше держите тёплый кэш по стабильному ключу.
- Антипаттерн: таскать tarball
node_modulesс ноутбука на сервер через rsync — ломает optional-маршрутизацию.
Для монорепозиториев согласуйте политику с гайдом по pnpm и зеркалу: одна точка правды для store снижает число «призрачных» платформенных пакетов.
05 Параллельные джобы CI — приёмка
- Один тип раннера на workflow: не смешивайте в одном job-кэше arm и x64 даже при одинаковой версии Node.
- Лимит параллелизма установки: при восьми одновременных
npm ciна общем диске включите очередь или разнесите по volume, чтобы postinstall не умирал по таймауту. - После restore кэша:
node -e "require('esbuild')"и импорт@swc/coreв одном шаге job. - Метрики: фиксируйте медиану времени install и cold build; всплеск на 40%+ после смены образа — сигнал проверить нативный слой, а не только прикладной diff.
- Документация: приложите к runbook ссылку на эту матрицу и эталонную команду диагностики — сокращает эскалации на DevOps.
06 FAQ
Достаточно ли зафиксировать только версию Node в engines?
Недостаточно: добавьте явное требование архитектуры в документации CI и, по возможности, assert в скрипте, иначе менеджер пакетов формально удовлетворён, а загрузчик — нет.
Помогает ли npm rebuild всегда?
Только если исходники нативного модуля присутствуют и toolchain совместим. Для предсобранных бинарников надёжнее чистая установка на правильной платформе.
Связан ли libc++ с esbuild?
Напрямую редко, но косвенно — при смеси плагинов на C++ или кастомных нативных аддонов в том же процессе Node. Выровняйте Xcode CLT на удалённом Mac с эталоном команды.
Арендуйте удалённый Mac с той же архитектурой CPU и образом macOS, что в боевом CI: меньше сюрпризов optionalDependencies, быстрее воспроизведение. Тарифы, аренда, блог с чек-листами по фронтенду на Mac.
Выберите узел с архитектурой как в CI
Выделенный Apple Silicon с фиксированным образом macOS снимает расхождения esbuild, @swc/core и optionalDependencies. Арендуйте удалённый Mac под ту же связку Node + архитектура, что в пайплайне: тарифы, покупка и аренда без обязательного входа, помощь по доступу, главная с планами по регионам.