Belajar Hermes JS Engine - Debugging Native Integration
Episode 18 of 23

Belajar Hermes JS Engine - Debugging Native Integration

Episode ini membongkar debugging jembatan JS-native di React Native dengan Hermes: memahami alur panggilan melintasi batas native module, melihat internal calls lewat profiler, lalu menangani crash reporting dan native stack traces dari produksi.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Pendahuluan

Episode 17 menutup dengan GC tuning: memahami heap behavior, mengurangi pause time, dan memburu memory leaks di perangkat low-memory. Kali ini kita masih di Fase 5, tapi pandangannya bergeser dari memori ke batas antar bahasa: jembatan JS-native di React Native. Kalau aplikasi kalian memanggil modul native, di sinilah error paling misterius lahir — error yang tidak muncul sebagai stack trace JavaScript biasa.

Roadmap episode 18: peta jembatan JS-native dengan Hermes, cara men-debug panggilan melintasi batas tersebut, melihat internal calls lewat profiler, memahami serialisasi data di boundary, lalu menangani crash reporting dan native stack traces.

Peta Jembatan JS-Native di React Native

React Native bukan aplikasi JavaScript murni. Kode JS kalian dieksekusi oleh Hermes, sedangkan UI, sensor, dan layanan sistem hidup di native. Keduanya dihubungkan oleh bridge (arsitektur lama) atau TurboModule + JSI (New Architecture).

  • Di arsitektur lama, setiap panggilan lintas bahasa melalui message queue — asinkron dan terserialisasi.
  • Di New Architecture dengan JSI, Hermes bisa memegang referensi langsung ke objek native dan memanggilnya secara sinkron tanpa serialisasi dulu.
JSMembungkus modul native dengan tap pengukuran
import { NativeModules } from "react-native";
 
const { FileScanner } = NativeModules;
 
async function scanWithLog(path) {
  const started = Date.now();
  try {
    const result = await FileScanner.scanDirectory(path);
    console.log(`[native-bridge] scan selesai dalam ${Date.now() - started}ms`);
    return result;
  } catch (err) {
    console.warn("[native-bridge] scan gagal:", err.message);
    throw err;
  }
}

Perhatikan pola di atas: setiap kali kode menyeberang ke native, ada titik di mana hal-hal bisa melambat atau gagal. Menempelkan log dan pengukuran tepat di boundary akan sangat membantu debugging.

Men-debug Panggilan Melintasi Batas

Debugging jembatan dimulai dari log. Di dev, jalankan Metro di satu terminal dan aplikasi di terminal lain:

Log React Native dan Hermes via adb
npx react-native run-android
adb logcat -s ReactNativeJS:V ReactNative:V

Tag ReactNativeJS memuat console.log dari kode JS yang berjalan di Hermes, sedangkan tag ReactNative memuat log dari sisi native. Kalau panggilan modul native kalian diam tanpa output, cek dulu apakah log JS muncul — jika tidak, masalahnya ada di Hermes atau bundle, bukan di modul native. Ini trik isolasi yang sangat murah; npx react-native info juga membantu memastikan versi Hermes, React Native, dan toolchain konsisten sebelum menyalahkan kode.

Info

Kalau kalian menggunakan New Architecture, JSI membuat panggilan sinkron dan log muncul dalam urutan yang lebih natural. Di arsitektur bridge lama, antrean asinkron bisa membuat urutan log tampak acak — jangan terkecoh, urutkan dengan timestamp, bukan dengan urutan kemunculan.

Melihat Internal Calls di Native Module Boundary

Kadang log tidak cukup — kalian perlu melihat tumpukan panggilan lengkap, termasuk di mana eksekusi keluar dari JavaScript. Chrome DevTools / React Native DevTools yang terhubung ke Hermes menampilkan panggilan JS, tapi untuk melihat sisi native, gunakan profiler native:

  • React Native Flipper (atau penggantinya, React Native DevTools) bisa membuka profiling JS Hermes.
  • Untuk sisi native, gunakan profiler bawaan Android Studio (CPU Profiler) atau Instruments di iOS.
  • Di perangkat Android, adb shell dumpsys gfxinfo <package> framestats memberi angka frame rendering yang bisa disandingkan dengan profil JS.

Kunci memahaminya: sebuah fungsi JS yang terlihat "sebentar" bisa saja membuang ratusan milidetik di dalam modul native di bawahnya. Profiler yang hanya melihat JS akan menunjukkan call stack yang berakhir di titik _nativeCall — itu petunjuk bahwa waktu sebenarnya dihabiskan di luar Hermes.

Serialisasi di Boundary: Sumber Error dan Lambat

Setiap data yang menyeberang jembatan harus diserialkan (di arsitektur lama lewat JSON). Objek besar atau struktur aneh sering menjadi biang error:

  • Objek dengan nilai undefined bisa berubah menjadi null atau hilang setelah serialisasi.
  • Date, Map, dan Set tidak ter-serialisasi secara natural — mereka berubah menjadi objek kosong.
  • Error yang dilempar native tiba di JS sebagai objek dengan properti yang belum tentu ada.
JSMenangkap error dan bentuk data di boundary
try {
  const stats = await NativeModules.DeviceStats.read();
  console.log("DeviceStats keys:", Object.keys(stats ?? {}));
} catch (err) {
  console.error("DeviceStats gagal:", err.code, err.userInfo ?? err.message);
}

Untuk panggilan yang menyeberang sering, uji dulu bentuk data di dev dengan log Object.keys seperti di atas sebelum membangun asumsi. Lebih baik lagi: pindahkan logika berat yang hanya butuh akses native ke sisi native, sehingga jembatan tidak bolak-balik.

Crash Reporting dan Native Stack Traces

Error JavaScript punya stack trace JavaScript. Tapi crash di modul native — segmentation fault, kesalahan memori di sisi JNI atau ObjC — tidak menghasilkan stack trace JS. Hermes tetap bisa lapor, tapi untuk native stack trace kita perlu tool khusus:

Menyimbolkan native stack trace dengan ndk-stack
adb logcat -d | ndk-stack -sym build/app/intermediates/merged_native_libs/release/mergeReleaseNativeLibs/out/lib > crash-resolved.txt

ndk-stack memetakan alamat pada log crash ke nama fungsi di library native. Pastikan simbol berasal dari build yang sama dengan yang dirilis — build berbeda berarti alamat berbeda dan hasilnya menyesatkan. Kalian juga bisa memakai addr2line dengan binary .so yang tepat:

Menyimbolkan satu alamat dengan addr2line
addr2line -f -C -e build/app/intermediates/merged_native_libs/release/mergeReleaseNativeLibs/out/lib/arm64-v8a/libreactnative.so 0x4a3f2c

Untuk production, integrasikan crash reporter seperti Sentry dalam mode Hermes + React Native. Sentry memetakan native stack trace ke fungsi asli dan source map ke kode JS:

JSKonfigurasi Sentry untuk React Native dengan Hermes
import * as Sentry from "@sentry/react-native";
 
Sentry.init({
  dsn: "https://dsn.kalian@sentry.io/1",
  tracesSampleRate: 0.25,
  environment: __DEV__ ? "development" : "production",
});

Warning

Source map dan simbol native adalah pasangan yang tidak boleh terpisah dari versi build. Simpan keduanya di arsip CI yang diindeks oleh versi rilis — tanpa itu, stack trace produksi yang diterima setahun lagi tidak bisa disimbolkan dengan benar.

Kebiasaan penting: sebelum merilis, uji crash di build rilis buatan dengan adb shell am crash <package> dan pastikan laporan yang masuk terbaca — baik sisi JS maupun sisi native.

Penutup

Debugging native integration adalah keterampilan paling mahal di stack React Native karena error-nya sering tidak punya wajah JavaScript. Dengan memetakan jembatan JS-native, menempelkan pengukuran di boundary, memakai profiler dua sisi, dan menyiapkan symbolication yang benar, kalian mengubah crash misterius menjadi laporan yang bisa ditindaklanjuti.

Inti yang harus dibawa pulang:

  • Pahami bahwa setiap panggilan lintas bahasa adalah titik kegagalan dan titik lambat — ukur di boundary.
  • Pisahkan log JS (ReactNativeJS) dari log native (ReactNative) untuk mengisolasi sisi yang bermasalah.
  • Profiler JS dan profiler native adalah dua alat yang saling melengkapi, bukan pilihan salah satu.
  • Serialisasi di jembatan mengubah bentuk data — verifikasi bentuk data di dev sebelum mengasumsikan di production.
  • Simpan source map dan simbol native per versi rilis agar stack trace produksi bisa disimbolkan kapan saja.

Di episode 19, kita memasuki Fase 6: Build Systems, CI & Release — mendefinisikan pipeline CI untuk Hermes builds, memvalidasi bytecode dan source maps, dan menegakkan deployment checks untuk Android serta iOS. Sampai jumpa!

Belajar Hermes JS Engine - Debugging Native Integration | Belajar Hermes JS Engine