01.08.2027 202 материалов

Когда Expo SDK 54 встречает SDK 55: как несовместимость пакетов уронила сборку Android

Разработчик обнаружил, что установка отдельных Expo-пакетов «последней версии» привела к тихой несовместимости с основным SDK. TypeScript ничего не заметил, dev-сервер работал — а нативная сборка Android падала.

Когда Expo SDK 54 встречает SDK 55: как несовместимость пакетов уронила сборку Android

TypeScript не поймал ошибку, Expo-сервер запускался без нареканий — но нативная сборка Android рухнула из-за того, что в проект попали пакеты от другого SDK. Проблема, которую легко не заметить и ещё проще предотвратить.

Что произошло

Представьте рабочий проект на Expo SDK 54. Всё стабильно, код компилируется, разработка идёт полным ходом. Но однажды приходит время собрать релиз через EAS Build — и Android-сборка падает на этапе работы с нативным кодом.

Причина? Часть Expo-пакетов в package.json оказалась от SDK 55. Не потому что кто-то сознательно обновлял SDK — а потому что при установке каждого пакета отдельно менеджер пакетов подтянул «последнюю версию», которая предназначалась для следующей мажорной версии Expo.

Вот как выглядела проблемная часть зависимостей:

{
  "dependencies": {
    "expo": "~54.0.33",
    "expo-apple-authentication": "~55.0.13",
    "expo-dev-client": "^55.0.27",
    "expo-image-picker": "^55.0.18",
    "expo-linking": "^55.0.12",
    "expo-notifications": "^55.0.19",
    "expo-splash-screen": "^55.0.18"
  }
}

Ядро expo — на версии 54. Шесть окружающих пакетов — на версии 55. Выглядит как мелочь, но для нативной сборки это фатальная несовместимость.

Почему «на глаз» это сложно отловить

Здесь кроется одна из неочевидных ловушек Expo. Номера пакетов не совпадают с номером SDK. К примеру, в Expo SDK 54 пакет expo-notifications имеет версию 0.32, а expo-splash-screen — 31. Пакеты SDK 55, которые попали в проект, имели версии 55.x — и это выглядело правдоподобно для того, кто не проверяет совместимость по документации.

Угадать, какой пакет к какому SDK относится, сходу практически невозможно. А беглый взгляд на package.json не выдаёт никаких предупреждений.

Почему молчал TypeScript

Это второй подвох. Пакеты от SDK 55 по-прежнему экспортируют JavaScript-интерфейсы и TypeScript-типы, которые устраивают компилятор. Код с точки зрения типизации — корректен. Запуск tsc проходит чисто. Dev-сервер тоже не жалуется: он не занимается сборкой нативных модулей.

Проблема проявляется только на этапе, когда EAS Build начинает компоновать нативные конфигурации, конфигурационные плагины и модули для Android и iOS. Тут несовместимость версий бьёт сразу — и сборка падает.

Иными словами, TypeScript проверяет поверхность API, а не совместимость нативного слоя. Полагаться только на tsc при работе с Expo — недостаточно.

Как это исправить

Автор статьи нашёл решение с помощью двух команд Expo CLI, которые, по сути, должны быть в арсенале каждого разработчика на React Native с Expo.

Шаг 1: Проверить совместимость

npx expo install --check

Эта команда сравнивает установленные пакеты с теми версиями, которые ожидает текущий SDK. Она сразу покажет расхождения. Можно получить результат и в формате JSON для автоматизации:

npx expo install --check --json

Это куда надёжнее, чем вручную сверять версии в package.json с таблицами из документации.

Шаг 2: Исправить версии

Expo CLI умеет автоматически подставить правильные версии:

npx expo install --fix
npx expo-doctor

Автор предпочёл отсмотреть каждый пакет вручную и скорректировал package.json сам. Вот что изменилось:

- "expo-apple-authentication": "~55.0.13"
+ "expo-apple-authentication": "~8.0.8"

- "expo-dev-client": "^55.0.27"
+ "expo-dev-client": "~6.0.21"

- "expo-image-picker": "^55.0.18"
+ "expo-image-picker": "~17.0.11"

- "expo-linking": "^55.0.12"
+ "expo-linking": "~8.0.12"

- "expo-notifications": "^55.0.19"
+ "expo-notifications": "~0.32.17"

- "expo-splash-screen": "~55.0.18"
+ "expo-splash-screen": "~31.0.13"

Разница впечатляющая. Пакеты «версии 55» оказались пакетами с версиями 0.32, 6, 8, 17 и 31 — и это нормально для SDK 54.

Шаг 3: Для монорепозиториев — нюанс

В монорепо команды нужно запускать с фильтрацией по конкретному пакету, иначе Expo CLI проанализирует корневой package.json, а не тот, что используется мобильным приложением:

pnpm install
pnpm --filter @squadnote/mobile exec expo install --check
pnpm --filter @squadnote/mobile exec expo-doctor

Как не попасть в эту ловушку снова

Автор перешёл на простое правило: добавлять Expo-пакеты только через expo install, а не через npm install или yarn add:

npx expo install expo-notifications

Эта команда сама выберет версию, совместимую с установленным SDK. После каждого обновления SDK — перед первой сборкой через EAS — запускать проверки:

npx expo install --check
npx expo-doctor

Что стоит вынести из этой истории

Ситуация банальна по механике, но коварна по последствиям. Вот три ключевых вывода:

  1. Версии Expo-пакетов не интуитивны. Номер SDK и номер пакета — это разные вещи. Не пытайтесь определить совместимость визуально.

  2. TypeScript не проверяет нативную совместимость. Чистый tsc — не гарантия того, что проект соберётся. Expo SDK живёт на стыке JavaScript и нативного кода, и проблемы могут быть только на одной стороне.

  3. Инструменты проверки есть — пользуйтесь ими. expo install --check и expo-doctor — это буквально одна команда, которая может сэкономить часы отладки на этапе CI/CD.

Случай этот — хороший пример того, как современная экосистема мобильной разработки порождает новые классы ошибок. Не ошибки в коде, не баги в бизнес-логике — а несовместимость на уровне сборочного конвейера, которую невозможно поймать привычными инструментами. Ирония в том, что чем удобнее становятся инструменты автоматизации вроде EAS Build, тем выше цена невнимательности к версионированию.

Ссылки: