Настройка Source Map загрузки для деобфускации крашей React Native
В Firebase Crashlytics крэш выглядит примерно так:
Fatal Exception: com.facebook.react.common.JavascriptException
[email protected]:1:92847
[email protected]:1:15234
Строка 1:92847 в минифицированном бандле. Без source maps невозможно понять, в каком файле и функции произошёл крэш. Source map mapping превращает этот стектрейс в читаемый: onButtonPress @ src/screens/PaymentScreen.tsx:147:23.
Как работает деобфускация
React Native в production bundle минифицирует и объединяет весь JS-код в один файл (index.android.bundle / main.jsbundle). Параллельно генерируется source map (index.android.bundle.map) — таблица соответствий между позициями в бандле и исходным кодом.
Crashlytics и Sentry принимают эти source maps и хранят на своих серверах. Когда приходит крэш — автоматически применяют маппинг и показывают исходный стектрейс.
Генерация source maps
# Android
react-native bundle \
--platform android \
--dev false \
--entry-file index.js \
--bundle-output android/app/src/main/assets/index.android.bundle \
--sourcemap-output android/app/src/main/assets/index.android.bundle.map
# iOS
react-native bundle \
--platform ios \
--dev false \
--entry-file index.js \
--bundle-output ios/main.jsbundle \
--sourcemap-output ios/main.jsbundle.map
В стандартном ./gradlew bundleRelease source map генерируется автоматически в app/build/generated/sourcemaps/react/release/. Но у него есть проблема: при hermes-компиляции нужен составной source map — hermes создаёт второй уровень маппинга (bytecode → JS bundle), который нужно скомпоновать с первым (JS bundle → TypeScript).
Hermes и составной source map
Для проектов с Hermes (включён по умолчанию с RN 0.70+):
# Compose source maps: hermes-engine + JS bundle
node_modules/react-native/scripts/compose-source-maps.js \
android/app/build/generated/sourcemaps/react/release/index.android.bundle.packager.map \
android/app/build/generated/sourcemaps/react/release/index.android.bundle.compiler.map \
-o android/app/build/generated/sourcemaps/react/release/index.android.bundle.map
Без этого шага Firebase Crashlytics покажет стектрейс, ссылающийся на строки JS-бандла, а не на исходный TypeScript-файл.
Загрузка в Firebase Crashlytics
# Установка Firebase CLI
npm install -g firebase-tools
# Загрузка source map
firebase crashlytics:mappingfile:upload \
--app "$FIREBASE_APP_ID" \
android/app/build/generated/sourcemaps/react/release/index.android.bundle.map
В CI это шаг после сборки:
- name: Upload Source Maps to Crashlytics
run: |
# Compose Hermes source maps
node node_modules/react-native/scripts/compose-source-maps.js \
android/app/build/generated/sourcemaps/react/release/index.android.bundle.packager.map \
android/app/build/generated/sourcemaps/react/release/index.android.bundle.compiler.map \
-o /tmp/composed.map
firebase crashlytics:mappingfile:upload \
--app "$FIREBASE_APP_ID_ANDROID" \
/tmp/composed.map
env:
FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
Загрузка в Sentry
Sentry использует CLI sentry-cli:
npm install -g @sentry/cli
sentry-cli releases new "$RELEASE_VERSION"
sentry-cli sourcemaps upload \
--org "$SENTRY_ORG" \
--project "$SENTRY_PROJECT" \
--release "$RELEASE_VERSION" \
android/app/build/generated/sourcemaps/react/release/
В Sentry нужно передавать release в SDK:
Sentry.init({
dsn: Config.SENTRY_DSN,
release: `${DeviceInfo.getBundleId()}@${DeviceInfo.getVersion()}+${DeviceInfo.getBuildNumber()}`,
});
Версия должна совпадать с тем, что передаётся при загрузке source maps.
Проблема с версионированием
Частая ошибка: source maps загружаются для одной версии (1.2.3), а в SDK передаётся другая (1.2.3+42). Крэши не деобфусцируются. Нужно зафиксировать единый формат версии и использовать его в обоих местах — CI переменная VERSION=$APP_VERSION+$BUILD_NUMBER.
Процесс
Проверка включения Hermes → настройка генерации source maps в build pipeline → скрипт compose для Hermes → добавление шага загрузки в CI → проверка деобфускации на тестовом крэше → документация формата версий.
Срок: 4 часа — 2 дня в зависимости от текущей настройки CI и наличия Hermes. Стоимость рассчитывается индивидуально.







