When you start using React Native, it's easy to feel like things are "just working" without fully understanding why. In this article, we'll walk through everything under the hood — from Metro, JSI, Codegen, and how Expo works, all the way to native implementation for areas the Expo SDK can't cover.
1. The Core Philosophy of React Native
The heart of React Native is translating UI logic written in JS into native UI components. Just as ReactDOM converts <div> into DOM nodes, React Native maps <View> and <Text> to UIView on iOS and android.view.View on Android.
This means it's generating real native UI — not rendering HTML in a WebView. That's the fundamental difference from Cordova or Ionic.
2. Architecture Evolution: From Bridge to JSI
React Native currently has two coexisting architectures.
flowchart LR
subgraph OLD["Old Architecture (Bridge)"]
direction TB
J1["JS Thread<br/>React Logic"] -->|"JSON serialize"| BR["Bridge<br/>Async Queue ⚠"]
BR -->|"JSON deserialize"| N1["Native Thread<br/>UIKit / Android View"]
SH["Shadow Thread<br/>Yoga layout"] --> N1
end
subgraph NEW["New Architecture (JSI)"]
direction TB
HM["Hermes Engine<br/>Bytecode Execution"] -->|"Direct C++ reference"| JSI["JSI<br/>Host Objects"]
JSI --> FAB["Fabric<br/>Sync Renderer"]
JSI --> TM["TurboModules<br/>Lazy Initialization"]
FAB --> N2["Native Layer"]
TM --> N2
endProblems with the Old Architecture (Bridge)
The legacy React Native had JS and native threads communicating through a single asynchronous bottleneck called the Bridge. Every interaction was serialized to JSON, sent across, then deserialized — making high-frequency operations like animations and gestures prone to performance issues.
Innovations of the New Architecture (JSI + Hermes)
JSI (JavaScript Interface) is a thin C++ binding layer that lets the JS engine directly and synchronously reference native objects. This enabled:
- Fabric — A new renderer that builds and updates UI trees synchronously
- TurboModules — Lazy initialization that defers loading native modules until needed
- Hermes — Facebook's engine that pre-compiles JS to bytecode, drastically reducing startup time
3. Codegen: The Key to Type-Safe Native Communication
Codegen is the most overlooked mechanism in the new architecture. While JSI and TurboModules make communication possible, Codegen guarantees it's correct at build time.
flowchart TD
SPEC["JS Spec File<br/>Type definitions in TypeScript / Flow"]
subgraph BUILD["Build Time"]
CG["Codegen<br/>Runs during pod install / Gradle"]
end
subgraph OUT["Generated Artifacts"]
CPP["C++ Abstract Classes<br/>TurboModule base interfaces"]
DESC["Component Descriptor<br/>For Fabric native components"]
JSTYPE["JS Type Definitions<br/>.d.ts equivalent"]
end
subgraph NATIVE["Native Implementation"]
IOS["iOS<br/>Implemented in Swift / Obj-C"]
AND["Android<br/>Implemented in Kotlin / Java"]
end
RUNTIME["Via JSI<br/>Type-safe, synchronous calls"]
SPEC --> CG
CG --> CPP & DESC & JSTYPE
CPP --> IOS & AND
DESC --> IOS & AND
IOS --> RUNTIME
AND --> RUNTIMEIn the old architecture, the interface between JS and native was essentially a "gentlemen's agreement." Calling NativeModules.MyModule.doSomething(42) from JS when native expected a String wouldn't fail until a runtime crash.
Codegen solves this at build time. Developers write a Spec (specification) in TypeScript or Flow describing what types a native module accepts. During pod install (iOS) or a Gradle build (Android), Codegen generates C++ abstract classes, native scaffolding for iOS and Android, and JS type definitions automatically.
4. The Metro Bundler Pipeline
Metro is React Native's dedicated JavaScript bundler. Like webpack, it bundles your code, but it's optimized for mobile development.
flowchart LR
SRC[".js / .ts / .tsx<br/>Source Files"]
RES["Resolution<br/>Import path resolution"]
TRA["Transformation<br/>ES transforms via Babel"]
SER["Serialization<br/>Bundle generation"]
DEV["Deliver to device<br/>HTTP :8081"]
HMR["HMR<br/>Resend only changed modules"]
CACHE["FS Cache<br/>Faster from 2nd run onward"]
SRC --> RES --> TRA --> SER --> DEV
TRA -.->|"File change detection"| HMR
HMR -.->|"Reflect without losing state"| DEV
TRA <-.->|"Cache transformed results"| CACHEMetro's pipeline has three phases:
- Resolution — Node.js-style module resolution with automatic platform-specific file selection (
foo.ios.ts/foo.android.ts) - Transformation — Babel converts JSX, TypeScript, and modern ES; results are cached so subsequent starts are faster
- Serialization — Generates the final bundle and delivers it to the device via an HTTP server
Notably, HMR (Hot Module Replacement) only updates the graph of changed modules, reflecting code changes instantly while preserving React component state. Production builds combine --minify and Hermes bytecode compilation to reduce both file size and startup time.
5. Expo's Layer Structure and Development Tools
flowchart TD
APP["Your App<br/>App.tsx / screens / hooks"]
SDK["Expo SDK<br/>expo-camera / expo-location / expo-router …"]
EMC["expo-modules-core<br/>Type-safe DSL in 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/>Launch instantly via QR scan"]
EASB["EAS Build<br/>Generate .ipa / .apk in the cloud"]
EASU["EAS Update<br/>Deliver JS bundle only via OTA"]
STORE["App Store / Google Play"]
APP --> SDK
EMC --> SDK
SDK --> RN
RN --> IOS & AND
GO -->|"Host during dev"| APP
EASB --> STORE
EASU -->|"No App Store review"| APPExpo SDK provides native features — camera, location, notifications, file system — as versioned packages. This is its biggest value: no need to write native code yourself.
expo-modules-core is a newer system enabling declarative native module development using Swift and Kotlin DSLs, integrated with Codegen. It's more type-safe and less verbose than the old NativeModules bridge, and nearly all third-party Expo SDKs are built with it.
EAS (Expo Application Services) is an umbrella for Build, Update, Submit, Workflows, and more. This article focuses on EAS Build and EAS Update. EAS Build compiles native binaries in the cloud, enabling iOS builds without a Mac. EAS Update delivers JS bundles and assets OTA, allowing rapid delivery of changes that don't touch native code.
6. Areas Expo Can't Cover and Choosing Your Workflow
Major areas the Expo SDK doesn't cover:
- Bluetooth LE / NFC — Platform-specific peripheral communication
- Custom payment / biometric SDKs — Direct integration of vendor
.framework/.aar - Real-time audio/video processing — Low-level WebRTC or custom codec operations
- Internal native libraries — Swift / Kotlin assets extracted from existing apps
- Custom camera views — Custom rendering using OpenGL / Metal
flowchart TD
START["What native capability do you need?"]
START --> Q1{"Covered by Expo SDK<br/>or a public library?"}
Q1 -->|"Yes"| MANAGED["Managed Workflow<br/>No native code needed<br/>Just npx expo start"]
Q1 -->|"No"| Q2{"Only config changes needed?<br/>(Info.plist / AndroidManifest additions)"}
Q2 -->|"Yes"| PLUGIN["Create Config Plugin<br/>Add to app.json<br/>Auto-applied by prebuild"]
Q2 -->|"No"| Q3{"How much custom native code?"}
Q3 -->|"Lightweight<br/>(wrapping existing SDK)"| BARE["Bare Workflow<br/>npx expo prebuild<br/>→ manage ios/ android/ directly"]
Q3 -->|"Full-scale<br/>(custom module / UI)"| MODULE{"What are you building?"}
MODULE -->|"Logic<br/>Bluetooth / payment / crypto"| TM["TurboModule<br/>+ Codegen"]
MODULE -->|"UI Component<br/>Custom camera view, etc."| FAB["Fabric Component<br/>+ Codegen"]
BARE --> TM & FAB
PLUGIN --> BAREMinor config changes can be handled with Config Plugins. But if you need to write code, generating ios/ and android/ directories via npx expo prebuild and moving to Bare Workflow is the practical starting point.
7. Full Architecture: JSI + Codegen + Native Implementation
flowchart TB
subgraph JS["JS Layer (Hermes)"]
APP["App Code / React Components"]
SPEC["JS Spec<br/>(TypeScript + TurboModuleRegistry)"]
end
subgraph CODEGEN["Codegen (Auto-generated at build time)"]
GEN_CPP["C++ Abstract Classes<br/>TurboModule / ComponentDescriptor"]
GEN_SHADOW["ShadowNode / Props Definitions"]
end
subgraph JSI_LAYER["JSI Layer (C++)"]
JSI["JSI Host Object"]
FABRIC["Fabric Renderer"]
end
subgraph NATIVE_IOS["iOS Native Implementation"]
SWIFT["Swift / Obj-C Implementation<br/>(Inherits C++ abstract class)"]
SDK_IOS["Internal SDK / External .framework<br/>e.g. CoreBluetooth / Stripe"]
UIVIEW["UIView Subclass<br/>(Custom UI Component)"]
end
subgraph NATIVE_AND["Android Native Implementation"]
KOTLIN["Kotlin / Java Implementation<br/>(Inherits C++ abstract class)"]
SDK_AND["Internal SDK / External .aar<br/>e.g. BluetoothGatt / Stripe"]
ANDROIDVIEW["View Subclass<br/>(Custom UI Component)"]
end
SPEC -->|"Read type definitions"| CODEGEN
GEN_CPP --> JSI
GEN_SHADOW --> FABRIC
APP -->|"Sync / async calls"| JSI
JSI --> SWIFT & KOTLIN
FABRIC --> UIVIEW & ANDROIDVIEW
SWIFT --> SDK_IOS
KOTLIN --> SDK_ANDThe key is the one-way dependency: Spec → Codegen → Native Implementation. Write type definitions on the JS side, and Codegen generates C++ abstract classes at build time. Swift / Kotlin implementations just inherit those — type consistency is guaranteed by the compiler, eliminating the "gentlemen's agreement" that caused runtime crashes.
8. TurboModule Implementation Flow (Bluetooth Example)
Note: The code below is a simplified example based on the React Native official TurboModule / Codegen / Custom Events guides. It is not a direct copy of the official samples, but a reconstruction using Bluetooth as the example.
① Write the 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');
② What Codegen Generates (Automatically)
flowchart LR
SPEC["NativeBluetoothModule.ts<br/>(Written by developer)"]
subgraph AUTO["Auto-generated code"]
CPP["C++ glue code<br/>JSI / Codegen output"]
IOS_H["iOS glue code<br/>Obj-C++ / header output"]
AND_J["Android glue code<br/>JNI / Spec class output"]
end
subgraph IMPL["Developer implements"]
SWIFT_IMPL["NativeBluetoothModule.swift<br/>Implements generated Spec, calls CoreBluetooth"]
KOTLIN_IMPL["NativeBluetoothModule.kt<br/>Implements generated Spec, calls BluetoothGatt"]
end
SPEC --> CPP
SPEC --> IOS_H
SPEC --> AND_J
CPP --> SWIFT_IMPL & KOTLIN_IMPL
IOS_H --> SWIFT_IMPL
AND_J --> KOTLIN_IMPL③ iOS Implementation (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
])
}
}
④ Android Implementation (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. Fabric Component Implementation Flow (Custom Camera View Example)
If TurboModule handles logic, Fabric Component is for embedding native UI into JSX. You'll need it for custom rendering with OpenGL / Metal, or when reusing existing native views from an existing app.
Note: The following is also a minimal example for understanding. Codegen import paths and type names vary by React Native version, so always check the official docs for your version when implementing.
// 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 maps Float → CGFloat / float
torchEnabled?: boolean;
onFrameCaptured?: DirectEventHandler<FrameCapturedEvent>;
}
export default codegenNativeComponent<NativeProps>('CameraView');
Codegen generates C++ definitions for ShadowNode, ComponentDescriptor, and Props from this Spec. On iOS, implement a CameraViewComponentView inheriting RCTViewComponentView; on Android, inherit ReactViewGroup. From the JS side, it's as simple as <CameraView zoom={2.0} torchEnabled />.
10. Integrating Internal SDKs / External .framework and .aar
iOS: Local Reference via CocoaPods
# ios/Podfile
pod 'CompanyPaymentSDK', :path => '../vendor/ios/CompanyPaymentSDK'
# or for xcframework
pod 'CompanyPaymentSDK', :podspec => '../vendor/ios/CompanyPaymentSDK.podspec'
After adding to Podfile and running pod install, you can use it as a regular Swift class with import CompanyPaymentSDK. Just call it from your TurboModule implementation.
Android: Local Maven / .aar Reference
// android/app/build.gradle
dependencies {
implementation files('../vendor/android/company-payment-sdk.aar')
// or an internal Maven repository
implementation 'com.company:payment-sdk:1.4.0'
}
11. The Development Loop: From Start to Verification
flowchart TD
INIT["npx create-expo-app MyApp<br/>--template blank-typescript"]
subgraph START["npx expo start"]
METRO["Metro Bundler starts<br/>port 8081"]
QR["QR code + shortcuts<br/>i = iOS a = Android w = Web"]
end
subgraph DEVICE["Choose target"]
SIM["iOS Simulator<br/>Included with Xcode (Mac only)"]
EMU["Android Emulator<br/>Android Studio AVD"]
GO["Physical device + Expo Go<br/>Via LAN or Tunnel"]
end
subgraph NATIVE_BUILD["First-time only: Native Build"]
POD["pod install<br/>iOS CocoaPods"]
GRADLE["Gradle build<br/>Android"]
CODEGEN_RUN["Codegen runs<br/>Spec → Native scaffolding"]
end
BUNDLE["Transfer JS bundle to device<br/>via Metro HTTP server"]
RENDER["Screen renders"]
INIT --> START
METRO --> QR
QR --> SIM & EMU & GO
SIM --> NATIVE_BUILD
EMU --> NATIVE_BUILD
NATIVE_BUILD --> BUNDLE
GO --> BUNDLE
BUNDLE --> RENDERRun npx expo start to start Metro, and a QR code with keyboard shortcuts appears in the terminal.
ikey → iOS Simulator (runs on Mac with Xcode installed)akey → Android Emulator (virtual device created in Android Studio AVD Manager)- For physical devices, scan the QR code in Expo Go — connects almost instantly on LAN
When you explicitly trigger a native build with npx expo run:ios or npx expo run:android, pod install (iOS) or Gradle (Android) runs on the first build, and Codegen generates the native scaffolding. Subsequent runs only transfer JS bundle diffs, so they're much faster.
12. The Change Loop in Bare Workflow
flowchart TD
subgraph CHANGE["Branch by type of change"]
JS_ONLY["JS only changes<br/>Components / logic / styles"]
NATIVE_CHANGE["Native code changes<br/>Swift / Kotlin / Spec / Podfile"]
end
subgraph JS_LOOP["JS Change Loop (Fast)"]
METRO_HMR["Metro HMR<br/>Transfer diff bundle<br/>~hundreds of ms"]
SIM_JS["Instantly reflected in emulator<br/>App state preserved"]
end
subgraph NATIVE_LOOP["Native Change Loop (Slow)"]
CODEGEN_RUN["Re-run Codegen<br/>pod install (iOS)<br/>Gradle sync (Android)"]
REBUILD["Native rebuild<br/>npx expo run:ios<br/>npx expo run:android"]
SIM_NATIVE["Verify on emulator / device"]
end
JS_ONLY --> METRO_HMR --> SIM_JS
NATIVE_CHANGE --> CODEGEN_RUN --> REBUILD --> SIM_NATIVE
SIM_JS -->|"No issues"| DONE["Verification OK"]
SIM_NATIVE -->|"No issues"| DONE
SIM_JS -->|"Bug found"| DEBUG_JS["Hermes Debugger<br/>React DevTools"]
SIM_NATIVE -->|"Native crash"| DEBUG_NATIVE["Xcode / Android Studio<br/>Native debugger"]
DEBUG_JS --> JS_ONLY
DEBUG_NATIVE --> NATIVE_CHANGEJS-only changes are handled by Metro HMR in hundreds of milliseconds, preserving state.
Native code changes require a rebuild. iOS first build takes 2–5 minutes, subsequent diffs take 30 seconds to a minute. Android with Gradle cache is similar; first build takes longer.
This asymmetry is key to development efficiency in Bare Workflow: lock down the native boundary (Spec) early and push most implementation to the JS side to reduce heavy native build frequency.
13. Debugging: Investigating Across the JS/Native Boundary
flowchart TD
SYMPTOM["Observe symptoms"]
SYMPTOM --> Q1{"Type of crash"}
Q1 -->|"JS error<br/>LogBox red screen"| JS_ERR["Investigate with Hermes Debugger<br/>Breakpoints, stack trace<br/>TS line numbers via Source Map"]
Q1 -->|"Native crash<br/>App freezes / crashes"| NAT_ERR["Check crash logs"]
Q1 -->|"Works but<br/>wrong result"| BORDER["Suspect JS↔Native boundary"]
NAT_ERR --> IOS_CRASH["iOS: Xcode<br/>Debug Navigator<br/>+ lldb attach"]
NAT_ERR --> AND_CRASH["Android: Android Studio<br/>Logcat stack trace<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["JS side: console.log<br/>Check TurboModule return values"]
IOS_CRASH --> INSTRUMENTS["Instruments<br/>Time Profiler / Allocations<br/>Identify memory leaks / CPU"]
AND_CRASH --> PROFILER["Android Studio<br/>Memory / CPU Profiler"]
JS_ERR --> FIXED["Fix → Instantly reflected via Metro HMR"]
INSTRUMENTS --> FIXED2["Fix → Verify with npx expo run:ios"]
PROFILER --> FIXED3["Fix → Verify with npx expo run:android"]
LOG_IOS & LOG_AND & JS_LOG --> BORDER2["Identify root cause<br/>→ Fix Spec / implementation"]JS Errors: Hermes Debugger
Cmd+D → "Open Debugger" opens Chrome DevTools. Metro provides Source Maps, so you can set breakpoints at original TypeScript line numbers — not bundled code. Exceptions in TurboModule calls show the full call stack back to the JS side.
Native Crashes: Attach Xcode / Android Studio Debugger
# iOS: Attach lldb to running simulator
npx expo run:ios --configuration Debug
# → Xcode opens automatically and attaches the debugger
# Android
npx expo run:android
# → "Run > Attach Debugger to Android Process"
For crashes that only reproduce on physical devices (especially Bluetooth, camera, sensors), you can attach the debugger to a USB-connected device the same way.
Boundary Investigation: Sandwich with Logs
When data gets corrupted at the JS↔Native boundary, sandwiching from both sides with logs is the fastest approach.
// Swift side
os_log("BluetoothModule.connect called: %{public}@", deviceId)
// → Filter with npx react-native log-ios
// Kotlin side
Log.d("BluetoothModule", "connect called: $deviceId")
// → Filter with npx react-native log-android
// JS side
const result = await BluetoothModule.connect(deviceId);
console.log('connect result:', result); // Output to Metro
React DevTools
npx react-devtools
# → Waits for device connection on port 8097
Visualize the component tree and inspect (and modify) props and state in real time.
14. Simulators vs. Physical Devices
| What you're testing | iOS Simulator | Android Emulator | Physical Device |
|---|---|---|---|
| UI / Layout | Sufficient | Sufficient | Final verification |
| JS logic / API | Sufficient | Sufficient | Not needed |
| Bluetooth / NFC | Not possible | Not possible | Required |
| Camera / Microphone | Partial | Not possible | Required |
| Push notifications | Partial | Partial | Recommended |
| Performance | Reference only | Reference only | Required |
| Biometric auth | Face ID sim available | Fingerprint sim available | Recommended |
For development with native modules, a two-stage approach works well: solidify JS behavior on emulators, verify native features on physical devices. Especially for Bluetooth and NFC — since emulators can't test them at all — the number of devices and OS version coverage directly impacts quality.
Summary
The Full Technology Stack
| Layer | Technology | Role |
|---|---|---|
| Bundler | Metro | Source transpilation, HMR, platform branching |
| JS Engine | Hermes | Bytecode execution for fast startup |
| JS↔Native Bridge | JSI | Synchronous binding via C++ |
| Code Generation | Codegen | Auto-generates type-safe native scaffolding from Spec |
| UI Renderer | Fabric | Synchronous UI rendering on top of JSI |
| Native APIs | TurboModules | Lazily initialized modules inheriting Codegen-generated classes |
| Dev Infrastructure | Expo / EAS | SDK, cloud builds, OTA updates |
Full Flow for Native Implementation
① Write JS Spec (type definitions in TypeScript)
↓
② pod install / gradle build → Codegen auto-generates C++ scaffolding
↓
③ Implement in Swift / Kotlin (call internal SDK / external libraries)
↓
④ npx expo run:ios / run:android → Native build & emulator verification
↓
⑤ Write JS calling code → Fast iteration via Metro HMR
↓
⑥ JS errors → Hermes Debugger
Native crashes → Xcode / Android Studio debugger
Boundary mismatches → Sandwich with log-ios / log-android + console.log
↓
⑦ Verify Bluetooth / camera / sensors on physical devices
↓
⑧ EAS Build generates store-ready binaries
React Native's move from Bridge to JSI + Codegen transformed it from "works but fragile" to "type-safe and high-performance." Stepping outside Expo brings costs — heavy builds and writing the same logic in two languages — but Codegen makes the Spec the single source of truth, eliminating the biggest risk: runtime crashes from type mismatches at design time.
Reference URLs
Note: This article summarizes and reconstructs information from the following sources. Examples and explanations have been simplified for clarity and are not direct quotes. Always refer to the official documentation for your version when implementing.
Referenced URLs
React Native Official
- React Native New Architecture (Overview): https://reactnative.dev/architecture/landing-page
- JSI & TurboModules Overview: https://reactnative.dev/docs/turbo-native-modules-introduction
- What is Codegen: https://reactnative.dev/docs/the-new-architecture/what-is-codegen
- How to use Codegen: https://reactnative.dev/docs/the-new-architecture/using-codegen
- Sending events from TurboModules: https://reactnative.dev/docs/the-new-architecture/native-modules-custom-events
- Codegen type definitions appendix: https://reactnative.dev/docs/appendix
- Hermes Engine: https://reactnative.dev/docs/hermes
- Metro (React Native official): https://reactnative.dev/docs/metro
Expo Official
- Why Metro (Expo): https://docs.expo.dev/guides/why-metro/
- Using Hermes (Expo): https://docs.expo.dev/guides/using-hermes/
- EAS Overview: https://docs.expo.dev/eas/
- EAS Build Overview: https://docs.expo.dev/build/introduction/
- EAS Update Overview: https://docs.expo.dev/eas-update/introduction/
