React NativeExpoMetroJSITurboModulesFabricCodegenHermesMobile Development

React Native + Expo + Metro + Implémentation Native : Guide Complet

Sloth255
Sloth255
·10 min read·2,089 words

Quand on commence à utiliser React Native, on a vite l'impression que « ça marche, sans trop savoir pourquoi ». Dans cet article, nous allons tout expliquer : de Metro, JSI, Codegen et le fonctionnement d'Expo, jusqu'à l'implémentation native pour les domaines que le SDK Expo ne peut pas couvrir.


1. La Philosophie Fondamentale de React Native

Le cœur de React Native est de traduire la logique UI écrite en JS en composants UI natifs. Tout comme ReactDOM convertit <div> en éléments DOM, React Native mappe <View> et <Text> vers UIView sur iOS et android.view.View sur Android.

Cela signifie qu'il génère une vraie UI native — il ne rend pas du HTML dans une WebView. C'est la différence fondamentale avec Cordova ou Ionic.


2. Évolution de l'Architecture : Du Bridge au JSI

React Native dispose actuellement de deux architectures coexistantes.

flowchart LR
  subgraph OLD["Ancienne Architecture (Bridge)"]
    direction TB
    J1["Thread JS<br/>Logique React"] -->|"Sérialisation JSON"| BR["Bridge<br/>File d'attente async ⚠"]
    BR -->|"Désérialisation JSON"| N1["Thread Natif<br/>UIKit / Android View"]
    SH["Thread Shadow<br/>Layout Yoga"] --> N1
  end
  subgraph NEW["Nouvelle Architecture (JSI)"]
    direction TB
    HM["Moteur Hermes<br/>Exécution bytecode"] -->|"Référence C++ directe"| JSI["JSI<br/>Host Objects"]
    JSI --> FAB["Fabric<br/>Rendu synchrone"]
    JSI --> TM["TurboModules<br/>Initialisation paresseuse"]
    FAB --> N2["Couche Native"]
    TM --> N2
  end

Problèmes de l'Ancienne Architecture (Bridge)

L'ancien React Native faisait communiquer le thread JS et le thread natif via un seul goulot d'étranglement asynchrone appelé le Bridge. Chaque échange devait être sérialisé en JSON, envoyé, puis désérialisé — rendant les opérations fréquentes comme les animations et les gestes sujettes à des problèmes de performance.

Innovations de la Nouvelle Architecture (JSI + Hermes)

JSI (JavaScript Interface) est une couche de liaison C++ légère qui permet au moteur JS de référencer directement et de façon synchrone les objets natifs. Cela a rendu possible :

  • Fabric — Un nouveau rendu qui construit et met à jour les arbres UI de façon synchrone
  • TurboModules — Initialisation paresseuse qui reporte le chargement des modules natifs jusqu'au besoin
  • Hermes — Le moteur de Facebook qui pré-compile le JS en bytecode, réduisant drastiquement le temps de démarrage

3. Codegen : La Clé de la Communication Native Type-Safe

Codegen est le mécanisme le plus souvent négligé dans la nouvelle architecture. Tandis que JSI et TurboModules rendent la communication possible, Codegen garantit qu'elle est correcte au moment de la compilation.

flowchart TD
  SPEC["Fichier JS Spec<br/>Définitions de types en TypeScript / Flow"]

  subgraph BUILD["Temps de Compilation"]
    CG["Codegen<br/>S'exécute lors du pod install / Gradle"]
  end

  subgraph OUT["Artefacts Générés"]
    CPP["Classes C++ Abstraites<br/>Interfaces de base TurboModule"]
    DESC["Component Descriptor<br/>Pour les composants natifs Fabric"]
    JSTYPE["Définitions de Types JS<br/>Équivalent .d.ts"]
  end

  subgraph NATIVE["Implémentation Native"]
    IOS["iOS<br/>Implémenté en Swift / Obj-C"]
    AND["Android<br/>Implémenté en Kotlin / Java"]
  end

  RUNTIME["Via JSI<br/>Type-safe, appels synchrones"]

  SPEC --> CG
  CG --> CPP & DESC & JSTYPE
  CPP --> IOS & AND
  DESC --> IOS & AND
  IOS --> RUNTIME
  AND --> RUNTIME

Dans l'ancienne architecture, l'interface entre JS et natif était essentiellement un « accord de gentleman ». Appeler NativeModules.MyModule.doSomething(42) depuis JS ne produirait une erreur qu'au moment d'un crash à l'exécution si le côté natif attendait un String.

Codegen résout ce problème au moment de la compilation. Les développeurs écrivent une Spec (spécification) en TypeScript ou Flow décrivant quels types un module natif accepte. Lors du pod install (iOS) ou de la compilation Gradle (Android), Codegen génère automatiquement des classes C++ abstraites, un échafaudage natif pour iOS et Android et des définitions de types JS.


4. Le Pipeline du Metro Bundler

Metro est le bundler JavaScript dédié de React Native. Il remplit le même rôle que webpack, mais est optimisé pour le développement mobile.

flowchart LR
  SRC[".js / .ts / .tsx<br/>Fichiers source"]
  RES["Résolution<br/>Résolution des chemins d'import"]
  TRA["Transformation<br/>Transforms ES via Babel"]
  SER["Sérialisation<br/>Génération du bundle"]
  DEV["Livraison à l'appareil<br/>HTTP :8081"]
  HMR["HMR<br/>Renvoie only les modules modifiés"]
  CACHE["Cache FS<br/>Plus rapide dès le 2e lancement"]

  SRC --> RES --> TRA --> SER --> DEV
  TRA -.->|"Détection de changement"| HMR
  HMR -.->|"Reflète sans perdre le state"| DEV
  TRA <-.->|"Met en cache les résultats"| CACHE

Le pipeline de Metro comporte trois phases :

  • Résolution — Résolution de modules à la Node.js avec sélection automatique des fichiers spécifiques à la plateforme (foo.ios.ts / foo.android.ts)
  • Transformation — Babel convertit JSX, TypeScript et ES moderne ; les résultats sont mis en cache pour accélérer les démarrages suivants
  • Sérialisation — Génère le bundle final et le livre à l'appareil via un serveur HTTP

À noter particulièrement, le HMR (Hot Module Replacement). Il ne met à jour que le graphe des modules modifiés, reflétant les changements de code immédiatement tout en préservant l'état des composants React. Les builds de production combinent --minify et la compilation Hermes en bytecode pour réduire à la fois la taille du fichier et le temps de démarrage.


5. La Structure en Couches d'Expo et les Outils de Développement

flowchart TD
  APP["Votre App<br/>App.tsx / screens / hooks"]
  SDK["Expo SDK<br/>expo-camera / expo-location / expo-router …"]
  EMC["expo-modules-core<br/>DSL type-safe en Swift / Kotlin"]
  RN["React Native Core<br/>View / Text / Animated / StyleSheet"]
  IOS["iOS<br/>UIKit / AVFoundation / CoreLocation"]
  AND["Android<br/>View / Camera2 / LocationManager"]
  GO["Expo Go<br/>Lancement immédiat par scan QR"]
  EASB["EAS Build<br/>Générer .ipa / .apk dans le cloud"]
  EASU["EAS Update<br/>Livrer uniquement le JS bundle via OTA"]
  STORE["App Store / Google Play"]

  APP --> SDK
  EMC --> SDK
  SDK --> RN
  RN --> IOS & AND
  GO -->|"Héberge en développement"| APP
  EASB --> STORE
  EASU -->|"Sans review App Store"| APP

Le SDK Expo fournit des fonctionnalités natives — caméra, localisation, notifications, système de fichiers — sous forme de packages versionnés. Pas besoin d'écrire son propre code natif — c'est sa plus grande valeur.

expo-modules-core est un système plus récent permettant le développement déclaratif de modules natifs avec des DSLs Swift et Kotlin, intégré à Codegen. Plus type-safe et moins verbeux que l'ancien bridge NativeModules, presque tous les SDKs tiers d'Expo l'utilisent.

EAS (Expo Application Services) est un terme générique pour Build, Update, Submit, Workflows, etc. Cet article se concentre sur EAS Build et EAS Update. EAS Build compile des binaires natifs dans le cloud, permettant des builds iOS sans Mac. EAS Update livre des bundles JS et des assets via OTA, permettant une livraison rapide des changements qui ne touchent pas le code natif.


6. Les Domaines Non Couverts par Expo et le Choix du Workflow

Principaux domaines que le SDK Expo ne couvre pas :

  • Bluetooth LE / NFC — Communication avec des périphériques spécifiques à la plateforme
  • SDKs de paiement/biométrie personnalisés — Intégration directe de .framework / .aar fournisseurs
  • Traitement audio/vidéo en temps réel — WebRTC bas niveau ou opérations avec des codecs personnalisés
  • Bibliothèques natives internes — Assets Swift / Kotlin extraits d'applications existantes
  • Vues caméra personnalisées — Rendu personnalisé avec OpenGL / Metal
flowchart TD
  START["Quelle fonctionnalité native vous faut-il ?"]

  START --> Q1{"Couverte par le SDK Expo<br/>ou une bibliothèque publique ?"}
  Q1 -->|"Oui"| MANAGED["Managed Workflow<br/>Pas de code natif nécessaire<br/>Juste npx expo start"]
  Q1 -->|"Non"| Q2{"Seulement des changements de config ?<br/>(Ajouts Info.plist / AndroidManifest)"}
  Q2 -->|"Oui"| PLUGIN["Créer un Config Plugin<br/>Ajouter à app.json<br/>Appliqué automatiquement par prebuild"]
  Q2 -->|"Non"| Q3{"Volume de code natif personnalisé ?"}
  Q3 -->|"Léger<br/>(wrapper d'un SDK existant)"| BARE["Bare Workflow<br/>npx expo prebuild<br/>→ Gérer ios/ android/ directement"]
  Q3 -->|"Complet<br/>(module / UI personnalisé)"| MODULE{"Qu'est-ce qui est construit ?"}
  MODULE -->|"Logique<br/>Bluetooth / paiement / crypto"| TM["TurboModule<br/>+ Codegen"]
  MODULE -->|"Composant UI<br/>Vue caméra personnalisée, etc."| FAB["Fabric Component<br/>+ Codegen"]
  BARE --> TM & FAB
  PLUGIN --> BARE

Les changements de configuration mineurs peuvent être gérés avec des Config Plugins. Mais si du code doit être écrit, générer les répertoires ios/ et android/ avec npx expo prebuild et passer au Bare Workflow est le point de départ pratique.


7. Architecture Complète : JSI + Codegen + Implémentation Native

flowchart TB
  subgraph JS["Couche JS (Hermes)"]
    APP["Code App / Composants React"]
    SPEC["JS Spec<br/>(TypeScript + TurboModuleRegistry)"]
  end

  subgraph CODEGEN["Codegen (Auto-généré au moment de la compilation)"]
    GEN_CPP["Classes C++ Abstraites<br/>TurboModule / ComponentDescriptor"]
    GEN_SHADOW["Définitions ShadowNode / Props"]
  end

  subgraph JSI_LAYER["Couche JSI (C++)"]
    JSI["JSI Host Object"]
    FABRIC["Fabric Renderer"]
  end

  subgraph NATIVE_IOS["Implémentation Native iOS"]
    SWIFT["Implémentation Swift / Obj-C<br/>(Hérite de la classe C++ abstraite)"]
    SDK_IOS["SDK Interne / .framework Externe<br/>ex: CoreBluetooth / Stripe"]
    UIVIEW["Sous-classe UIView<br/>(Composant UI Personnalisé)"]
  end

  subgraph NATIVE_AND["Implémentation Native Android"]
    KOTLIN["Implémentation Kotlin / Java<br/>(Hérite de la classe C++ abstraite)"]
    SDK_AND["SDK Interne / .aar Externe<br/>ex: BluetoothGatt / Stripe"]
    ANDROIDVIEW["Sous-classe View<br/>(Composant UI Personnalisé)"]
  end

  SPEC -->|"Lire les définitions de types"| CODEGEN
  GEN_CPP --> JSI
  GEN_SHADOW --> FABRIC
  APP -->|"Appels sync / async"| JSI
  JSI --> SWIFT & KOTLIN
  FABRIC --> UIVIEW & ANDROIDVIEW
  SWIFT --> SDK_IOS
  KOTLIN --> SDK_AND

La clé est la dépendance unidirectionnelle : Spec → Codegen → Implémentation Native. Écrire des définitions de types côté JS, et Codegen génère des classes C++ abstraites au moment de la compilation. Les implémentations Swift / Kotlin n'ont qu'à les hériter — la cohérence des types est garantie par le compilateur, éliminant l'« accord de gentleman » comme source de crashes à l'exécution.


8. Flux d'Implémentation TurboModule (Exemple Bluetooth)

Note : Le code ci-dessous est un exemple simplifié basé sur les guides officiels React Native TurboModule / Codegen / Custom Events. Ce ne sont pas des citations directes des exemples officiels, mais une reconstruction utilisant Bluetooth comme exemple.

① Écrire la JS Spec

// src/specs/NativeBluetoothModule.ts
import type { TurboModule, CodegenTypes } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export type ScanResult = {
  id: string;
  name: string;
  rssi: number;
};

export interface Spec extends TurboModule {
  startScan(serviceUUIDs: string[]): void;
  stopScan(): void;
  connect(deviceId: string): Promise<boolean>;
  readonly onDeviceFound: CodegenTypes.EventEmitter<ScanResult>;
}

export default TurboModuleRegistry.getEnforcing<Spec>('NativeBluetoothModule');

② Ce que Codegen Génère (Automatiquement)

flowchart LR
  SPEC["NativeBluetoothModule.ts<br/>(Écrit par le développeur)"]

  subgraph AUTO["Code auto-généré"]
    CPP["Code C++ glue<br/>Sortie JSI / Codegen"]
    IOS_H["Code iOS glue<br/>Sortie Obj-C++ / header"]
    AND_J["Code Android glue<br/>Sortie JNI / classe Spec"]
  end

  subgraph IMPL["Implémenté par le développeur"]
    SWIFT_IMPL["NativeBluetoothModule.swift<br/>Implémente la Spec générée, appelle CoreBluetooth"]
    KOTLIN_IMPL["NativeBluetoothModule.kt<br/>Implémente la Spec générée, appelle BluetoothGatt"]
  end

  SPEC --> CPP
  SPEC --> IOS_H
  SPEC --> AND_J
  CPP --> SWIFT_IMPL & KOTLIN_IMPL
  IOS_H --> SWIFT_IMPL
  AND_J --> KOTLIN_IMPL

③ Implémentation iOS (Swift)

// ios/NativeBluetoothModule.swift
import CoreBluetooth

@objc(NativeBluetoothModule)
class NativeBluetoothModule: NativeBluetoothModuleSpec, CBCentralManagerDelegate {
  private var centralManager: CBCentralManager!

  override func startScan(_ serviceUUIDs: [String]) {
    let uuids = serviceUUIDs.map { CBUUID(string: $0) }
    centralManager.scanForPeripherals(withServices: uuids)
  }

  func centralManager(_ central: CBCentralManager,
                      didDiscover peripheral: CBPeripheral,
                      advertisementData: [String: Any], rssi: NSNumber) {
    emitOnDeviceFound([
      "id": peripheral.identifier.uuidString,
      "name": peripheral.name ?? "",
      "rssi": rssi
    ])
  }
}

④ Implémentation Android (Kotlin)

// android/src/main/java/NativeBluetoothModule.kt
class NativeBluetoothModule(reactContext: ReactApplicationContext)
    : NativeBluetoothModuleSpec(reactContext), BluetoothGatt.Callback {

  private val adapter = BluetoothAdapter.getDefaultAdapter()

  override fun startScan(serviceUUIDs: ReadableArray) {
    val filters = serviceUUIDs.toArrayList().map { uuid ->
      ScanFilter.Builder().setServiceUuid(ParcelUuid.fromString(uuid as String)).build()
    }
    adapter.bluetoothLeScanner.startScan(filters, ScanSettings.Builder().build(), scanCallback)
  }

  private val scanCallback = object : ScanCallback() {
    override fun onScanResult(callbackType: Int, result: ScanResult) {
      val params = Arguments.createMap().apply {
        putString("id", result.device.address)
        putString("name", result.device.name ?: "")
        putInt("rssi", result.rssi)
      }
      emitOnDeviceFound(params)
    }
  }
}

9. Flux d'Implémentation Fabric Component (Exemple Vue Caméra Personnalisée)

Si TurboModule gère la logique, Fabric Component sert à intégrer une UI native dans JSX. Nécessaire pour le rendu personnalisé avec OpenGL / Metal, ou la réutilisation de vues natives existantes.

Note : Ce qui suit est également un exemple minimal pour la compréhension. Les chemins d'import Codegen et les noms de types varient selon la version de React Native, vérifiez toujours la documentation officielle de votre version lors de l'implémentation.

// src/specs/NativeCameraViewSpec.ts
import type { ViewProps } from 'react-native';
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
import type { DirectEventHandler, Float } from 'react-native/Libraries/Types/CodegenTypes';

type FrameCapturedEvent = Readonly<{
  uri: string;
}>;

interface NativeProps extends ViewProps {
  zoom?: Float;          // Codegen convertit Float → CGFloat / float
  torchEnabled?: boolean;
  onFrameCaptured?: DirectEventHandler<FrameCapturedEvent>;
}

export default codegenNativeComponent<NativeProps>('CameraView');

Codegen génère des définitions C++ pour ShadowNode, ComponentDescriptor et Props à partir de cette Spec. Sur iOS, implémenter une CameraViewComponentView héritant de RCTViewComponentView ; sur Android, hériter de ReactViewGroup. Du côté JS, c'est aussi simple que <CameraView zoom={2.0} torchEnabled />.


10. Intégration de SDKs Internes / .framework et .aar Externes

iOS : Référence Locale via CocoaPods

# ios/Podfile
pod 'CompanyPaymentSDK', :path => '../vendor/ios/CompanyPaymentSDK'
# ou pour xcframework
pod 'CompanyPaymentSDK', :podspec => '../vendor/ios/CompanyPaymentSDK.podspec'

Après ajout au Podfile et exécution de pod install, il peut être utilisé comme une classe Swift ordinaire avec import CompanyPaymentSDK. L'appeler directement depuis l'implémentation TurboModule.

Android : Référence Maven Locale / .aar

// android/app/build.gradle
dependencies {
  implementation files('../vendor/android/company-payment-sdk.aar')
  // ou un dépôt Maven interne
  implementation 'com.company:payment-sdk:1.4.0'
}

11. La Boucle de Développement : Du Démarrage à la Vérification

flowchart TD
  INIT["npx create-expo-app MyApp<br/>--template blank-typescript"]

  subgraph START["npx expo start"]
    METRO["Metro Bundler démarre<br/>port 8081"]
    QR["Code QR + raccourcis<br/>i = iOS  a = Android  w = Web"]
  end

  subgraph DEVICE["Choisir la cible"]
    SIM["iOS Simulator<br/>Inclus avec Xcode (Mac uniquement)"]
    EMU["Android Emulator<br/>Android Studio AVD"]
    GO["Appareil physique + Expo Go<br/>Via LAN ou Tunnel"]
  end

  subgraph NATIVE_BUILD["Première fois seulement : Build Natif"]
    POD["pod install<br/>iOS CocoaPods"]
    GRADLE["Gradle build<br/>Android"]
    CODEGEN_RUN["Codegen s'exécute<br/>Spec → Échafaudage natif"]
  end

  BUNDLE["Transférer le JS bundle à l'appareil<br/>via le serveur HTTP Metro"]
  RENDER["L'écran s'affiche"]

  INIT --> START
  METRO --> QR
  QR --> SIM & EMU & GO
  SIM --> NATIVE_BUILD
  EMU --> NATIVE_BUILD
  NATIVE_BUILD --> BUNDLE
  GO --> BUNDLE
  BUNDLE --> RENDER

Lancez npx expo start pour démarrer Metro, et un code QR avec des raccourcis clavier apparaît dans le terminal.

  • Touche i → iOS Simulator (fonctionne sur Mac avec Xcode installé)
  • Touche a → Android Emulator (appareil virtuel créé dans Android Studio AVD Manager)
  • Pour les appareils physiques, scanner le QR dans Expo Go — se connecte presque instantanément sur LAN

En déclenchant explicitement un build natif avec npx expo run:ios ou npx expo run:android, pod install (iOS) ou Gradle (Android) s'exécute la première fois et Codegen génère l'échafaudage natif. Les exécutions suivantes ne transfèrent que les différences du JS bundle et sont beaucoup plus rapides.


12. La Boucle de Changement dans le Bare Workflow

flowchart TD
  subgraph CHANGE["Branchement par type de changement"]
    JS_ONLY["Changements JS uniquement<br/>Composants / logique / styles"]
    NATIVE_CHANGE["Changements de code natif<br/>Swift / Kotlin / Spec / Podfile"]
  end

  subgraph JS_LOOP["Boucle de Changement JS (Rapide)"]
    METRO_HMR["Metro HMR<br/>Transférer le bundle différentiel<br/>~Centaines de ms"]
    SIM_JS["Visible immédiatement dans l'émulateur<br/>État de l'app préservé"]
  end

  subgraph NATIVE_LOOP["Boucle de Changement Natif (Lent)"]
    CODEGEN_RUN["Re-exécuter Codegen<br/>pod install (iOS)<br/>Gradle sync (Android)"]
    REBUILD["Rebuild natif<br/>npx expo run:ios<br/>npx expo run:android"]
    SIM_NATIVE["Vérifier sur émulateur / appareil"]
  end

  JS_ONLY --> METRO_HMR --> SIM_JS
  NATIVE_CHANGE --> CODEGEN_RUN --> REBUILD --> SIM_NATIVE

  SIM_JS -->|"Pas de problème"| DONE["Vérification OK"]
  SIM_NATIVE -->|"Pas de problème"| DONE
  SIM_JS -->|"Bug trouvé"| DEBUG_JS["Hermes Debugger<br/>React DevTools"]
  SIM_NATIVE -->|"Crash natif"| DEBUG_NATIVE["Xcode / Android Studio<br/>Débogueur natif"]
  DEBUG_JS --> JS_ONLY
  DEBUG_NATIVE --> NATIVE_CHANGE

Les changements uniquement JS sont gérés par Metro HMR en quelques centaines de millisecondes, en préservant l'état.

Les changements de code natif nécessitent un rebuild. Le premier build iOS prend 2–5 minutes, les builds différentiels environ 30 secondes à une minute. Android avec le cache Gradle est similaire ; le premier build prend plus longtemps.

Cette asymétrie est la clé de l'efficacité du développement en Bare Workflow : fixer tôt la limite native (Spec) et pousser la plupart de l'implémentation côté JS pour réduire la fréquence des lourds builds natifs.


13. Debugging : Investigation à Travers la Frontière JS/Natif

flowchart TD
  SYMPTOM["Observer les symptômes"]

  SYMPTOM --> Q1{"Type de crash"}

  Q1 -->|"Erreur JS<br/>Écran rouge LogBox"| JS_ERR["Investiguer avec Hermes Debugger<br/>Breakpoints, stack trace<br/>Numéros de ligne TS via Source Map"]
  Q1 -->|"Crash natif<br/>L'app freeze / crash"| NAT_ERR["Vérifier les logs de crash"]
  Q1 -->|"Fonctionne mais<br/>mauvais résultat"| BORDER["Suspecter la frontière JS↔Natif"]

  NAT_ERR --> IOS_CRASH["iOS: Xcode<br/>Debug Navigator<br/>+ attacher lldb"]
  NAT_ERR --> AND_CRASH["Android: Android Studio<br/>Stack trace Logcat<br/>+ Native Debugger"]

  BORDER --> LOG_IOS["iOS: NSLog / os_log<br/>npx react-native log-ios"]
  BORDER --> LOG_AND["Android: Log.d<br/>npx react-native log-android"]
  BORDER --> JS_LOG["Côté JS: console.log<br/>Vérifier les valeurs de retour TurboModule"]

  IOS_CRASH --> INSTRUMENTS["Instruments<br/>Time Profiler / Allocations<br/>Identifier fuites mémoire / CPU"]
  AND_CRASH --> PROFILER["Android Studio<br/>Memory / CPU Profiler"]

  JS_ERR --> FIXED["Corriger → Visible via Metro HMR"]
  INSTRUMENTS --> FIXED2["Corriger → Vérifier avec npx expo run:ios"]
  PROFILER --> FIXED3["Corriger → Vérifier avec npx expo run:android"]
  LOG_IOS & LOG_AND & JS_LOG --> BORDER2["Identifier la cause racine<br/>→ Corriger Spec / implémentation"]

Erreurs JS : Hermes Debugger

Cmd+D → « Open Debugger » ouvre Chrome DevTools. Metro fournit des Source Maps, permettant de placer des breakpoints aux numéros de ligne TypeScript originaux — pas dans le code bundlé. Les exceptions dans les appels TurboModule montrent la stack complète remontant jusqu'au JS.

Crashes Natifs : Attacher le Débogueur Xcode / Android Studio

# iOS: Attacher lldb au simulateur en cours
npx expo run:ios --configuration Debug
# → Xcode s'ouvre automatiquement et attache le débogueur

# Android
npx expo run:android
# → "Run > Attach Debugger to Android Process"

Pour les crashes qui ne se reproduisent que sur des appareils physiques (surtout Bluetooth, caméra, capteurs), le débogueur peut être attaché de la même façon à un appareil connecté via USB.

Investigation de Frontière : Encadrer avec des Logs

Quand les données sont corrompues à la frontière JS↔Natif, les encadrer avec des logs des deux côtés est l'approche la plus rapide.

// Côté Swift
os_log("BluetoothModule.connect called: %{public}@", deviceId)
// → Filtrer avec npx react-native log-ios
// Côté Kotlin
Log.d("BluetoothModule", "connect called: $deviceId")
// → Filtrer avec npx react-native log-android
// Côté JS
const result = await BluetoothModule.connect(deviceId);
console.log('connect result:', result); // Sortie dans Metro

React DevTools

npx react-devtools
# → Attend la connexion de l'appareil sur le port 8097

Visualiser l'arbre de composants et inspecter (et modifier) les props et l'état en temps réel.


14. Simulateurs vs. Appareils Physiques

Ce qu'on teste iOS Simulator Android Emulator Appareil Physique
UI / Layout Suffisant Suffisant Vérification finale
Logique JS / API Suffisant Suffisant Pas nécessaire
Bluetooth / NFC Impossible Impossible Requis
Caméra / Microphone Partiel Impossible Requis
Notifications push Partiel Partiel Recommandé
Performance Référence seulement Référence seulement Requis
Authentification biométrique Sim Face ID disponible Sim empreinte disponible Recommandé

Pour le développement avec des modules natifs, une approche en deux étapes fonctionne bien : consolider le comportement JS sur les émulateurs, vérifier les fonctionnalités natives sur les appareils physiques. Surtout pour Bluetooth et NFC — les émulateurs ne pouvant pas les tester — le nombre d'appareils et la couverture des versions OS impacte directement la qualité.


Résumé

La Stack Technologique Complète

Couche Technologie Rôle
Bundler Metro Transpilation source, HMR, branchement plateforme
Moteur JS Hermes Exécution bytecode pour démarrage rapide
Bridge JS↔Natif JSI Liaison synchrone via C++
Génération de Code Codegen Auto-génération d'échafaudages natifs type-safe depuis la Spec
Rendu UI Fabric Rendu UI synchrone sur JSI
APIs Natives TurboModules Modules à initialisation paresseuse héritant des classes Codegen
Infrastructure Dev Expo / EAS SDK, builds cloud, mises à jour OTA

Flux Complet pour l'Implémentation Native

① Écrire la JS Spec (définitions de types en TypeScript)
       ↓
② pod install / gradle build → Codegen auto-génère l'échafaudage C++
       ↓
③ Implémenter en Swift / Kotlin (appeler SDK interne / bibliothèques externes)
       ↓
④ npx expo run:ios / run:android → Build natif & vérification émulateur
       ↓
⑤ Écrire le code d'appel JS → Itération rapide via Metro HMR
       ↓
⑥ Erreurs JS → Hermes Debugger
   Crashes natifs → Débogueur Xcode / Android Studio
   Discordances de frontière → Encadrer avec log-ios / log-android + console.log
       ↓
⑦ Vérifier Bluetooth / caméra / capteurs sur appareils physiques
       ↓
⑧ EAS Build génère les binaires prêts pour les stores

La transition de React Native de Bridge vers JSI + Codegen l'a transformé de « fonctionne mais fragile » à « type-safe et haute performance ». Sortir d'Expo entraîne des coûts — builds lourds et écrire la même logique en deux langues — mais Codegen fait de la Spec l'unique source de vérité, éliminant le plus grand risque : les crashes à l'exécution dus aux incompatibilités de types, dès la phase de conception.


URLs de Référence

Note : Cet article résume et reconstruit des informations provenant des sources suivantes. Les exemples et explications ont été simplifiés pour plus de clarté et ne sont pas des citations directes. Toujours consulter la documentation officielle de votre version lors de l'implémentation.

URLs Référencées

React Native Officiel

Expo Officiel