Налаштування завантаження 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 перетворює цей стектрейс на читабельний: onButtonPress @ src/screens/PaymentScreen.tsx:147:23.
Як працює деобфускація
React Native в production бандлі мініфікує та об'єднує весь 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 у pipeline збірки → скрипт compose для Hermes → додання кроку завантаження в CI → перевірка деобфускації на тестовому краші → документація формату версій.
Тривалість: 4 години — 2 дні залежно від поточного налаштування CI та наявності Hermes. Вартість розраховується індивідуально.







