% rn-architecture.tex — React Native threads, the legacy Bridge vs the New
% Architecture (JSI · Fabric · TurboModules · Codegen · bridgeless), Hermes,
% and the render → commit → mount pipeline. Senior-interview level.
% Source: author's own knowledge (no repo source for RN) — versions only where sure.
% Build ONLY with: tools/print/print-sheet.py <this>.tex --dry-run
% @source: hiot monorepo, docs/school/sheets/react-native/rn-architecture.tex — the SOURCE OF TRUTH; a copy anywhere else (e.g. artur.gurgul.pro) is regenerated from it, never edited
% @labels: area=react-native kind=architecture level=core platform=cross-platform new=no round=react-native-2026-09-24 topic=architecture,performance,concurrency
% @tags: jsi, fabric, turbomodules, codegen, bridgeless, hermes, bridge, js-thread, yoga, shadow-tree, render-commit-mount, interop-layer
\documentclass[8pt]{extarticle}
\usepackage{printup-sheet}

\lstdefinelanguage{TSSheet}{
  morekeywords={const,let,var,function,return,if,else,import,export,from,type,
    interface,async,await,new,class,extends,default,true,false,null,undefined,
    void,string,number,boolean,Promise},
  morekeywords=[2]{TurboModule,TurboModuleRegistry,useState,useEffect},
  keywordstyle=[2]{\color{sheetOrange}\bfseries},
  sensitive=true, morecomment=[l]{//}, morecomment=[s]{/*}{*/},
  morestring=[b]", morestring=[b]', morestring=[b]`}

\tikzset{
  sb/.style={box, font=\scriptsize, inner sep=1.5pt, minimum height=5mm},
  js/.style={sb, draw=sheetOrange, fill=sheetOrange!10},
  ui/.style={sb, draw=sheetGreen!70!black, fill=sheetGreen!10},
  cpp/.style={sb, draw=sheetBlue, fill=sheetBlue!10},
  bad/.style={sb, draw=sheetRed, fill=sheetRed!7},
  lbl/.style={font=\tiny, text=black!75, inner sep=1pt, align=center},
  pt/.style={font=\bfseries\small, anchor=west},
  both/.style={<->, thick, draw=sheetGrey},
}

\begin{document}

\sheettitle{React Native architecture — Bridge vs New Architecture}{react-native · memo}

\oneliner{React runs on a \textbf{JS thread}; \textbf{real native views} live on the
\textbf{UI thread}. The legacy \textbf{Bridge} joined them with \emph{async, batched JSON};
the \textbf{New Architecture} (default since \textbf{RN 0.76}) uses \textbf{JSI} — JS calls
C++ directly, sync or async — with the \textbf{Fabric} renderer and lazy, typed
\textbf{TurboModules} generated by \textbf{Codegen}.}

\vspace{3pt}
\noindent\begin{tikzpicture}[sheet]
  \draw[sheetGrey!40] (8.25,3.55) -- (8.25,-0.5);
  % ── legacy ──
  \node[pt] at (0,3.4) {\textcolor{sheetRed}{Legacy} — the Bridge};
  \node[js, minimum width=18mm, minimum height=13mm] (js0) at (1.0,1.9)
       {\textbf{JS thread}\\React + app JS\\(JSC / Hermes)};
  \node[bad, minimum width=23mm, minimum height=9mm] (br) at (3.85,1.9)
       {\textbf{Bridge}\\MessageQueue};
  \foreach \i in {0,...,4} \node[cell, minimum width=3.6mm, minimum height=3mm,
       font=\tiny, fill=white] at (3.13+\i*0.36,1.25) {\{\}};
  \node[lbl, text=sheetRed] at (3.85,0.85) {JSON · async · batched\\flushed per JS tick};
  \draw[both] (js0) -- (br);
  \node[sb, minimum width=21mm] (nm0) at (6.9,2.95) {Native modules\\\tiny ALL init at startup};
  \node[sb, minimum width=21mm] (uim) at (6.9,1.9) {UIManager};
  \node[sb, minimum width=21mm] (sh0) at (6.9,0.95) {shadow thread\\\tiny Yoga layout};
  \node[ui, minimum width=21mm] (ui0) at (6.9,-0.1) {UI thread: UIView};
  \draw[both] (br.east) -- (nm0.west);
  \draw[both] (br.east) -- (uim.west);
  \draw[flow] (uim) -- (sh0); \draw[flow] (sh0) -- (ui0);
  \node[lbl, anchor=west, text=sheetRed] at (-0.1,0.35) {no sync return · every value\\copied twice ·
       one busy channel ·\\scroll events queue behind work};
  % ── new ──
  \node[pt] at (8.4,3.4) {\textcolor{sheetGreen!60!black}{New Architecture} — bridgeless};
  \node[js, minimum width=17mm, minimum height=13mm] (js1) at (9.4,1.9)
       {\textbf{JS thread}\\React\\(concurrent)\\Hermes};
  \node[cpp, minimum width=6mm, minimum height=31mm, font=\scriptsize\bfseries] (jsi) at (11.2,1.55)
       {J\\S\\I\\[2pt]\tiny C\\\tiny ++};
  \draw[both] (js1) -- (js1-|jsi.west);
  \node[cpp, minimum width=24mm] (tm) at (13.25,2.95) {\textbf{TurboModules}\\\tiny lazy: loaded on first use};
  \node[cpp, minimum width=24mm, minimum height=10mm] (fab) at (13.25,1.6)
       {\textbf{Fabric} (C++)\\\tiny shadow tree · Yoga · diff};
  \node[ui, minimum width=24mm] (ui1) at (13.25,0.25) {UI thread: \textbf{mount}};
  \draw[both] (tm.west -| jsi.east) -- (tm.west);
  \draw[both] (fab.west -| jsi.east) -- (fab.west);
  \draw[flow] (fab) -- node[lbl, right]{mutations} (ui1);
  \node[sb, dashed, draw=sheetBrown, fill=sheetBrown!8, minimum width=17mm] (cg) at (15.7,1.6)
       {\textbf{Codegen}\\\tiny TS/Flow spec $\to$\\\tiny C++ · ObjC++ · Java};
  \draw[->, dashed, draw=sheetBrown] (cg.north west) -- (tm.south east);
  \draw[->, dashed, draw=sheetBrown] (cg.west) -- (fab.east|-cg.west);
  \node[lbl, text=sheetBrown] at (15.7,0.75) {build time: typed\\glue both sides};
  \node[sb, minimum width=17mm] (imp) at (15.7,3.0) {your ObjC++ /\\Kotlin impl};
  \draw[flow] (tm.east) -- (tm.east-|imp.west);
  \node[lbl, anchor=west, text=sheetGreen!50!black] at (8.35,0.45) {host objects: no JSON,\\shared refs};
  \node[lbl, anchor=west, text=sheetGreen!50!black] at (8.35,-0.15) {sync where needed\\(layout, getters)};
\end{tikzpicture}

\begin{multicols}{2}

\section{Threads — who owns what}
\begin{itemize}
  \item \textbf{JS thread} (one): render, logic, handlers. Block it and JS-driven taps and
        animations freeze, yet native scrolling still works. Perf Monitor: \textbf{JS FPS} $\neq$ \textbf{UI FPS}.
  \item \textbf{Main/UI thread}: \texttt{UIView}s, gestures, native animations — iOS
        main-thread rules unchanged. iOS map: JS thread $\approx$ a serial queue owning
        the model; UI = \texttt{DispatchQueue.main}.
  \item \textbf{Background}: legacy \emph{shadow thread} ran Yoga; legacy modules ran on
        their own \texttt{methodQueue} (GCD). Fabric lays out on JS or a background thread.
\end{itemize}

\section{Why the Bridge bottlenecked}
\begin{itemize}
  \item Every call: serialise JSON $\to$ queue $\to$ batch-flush $\to$ parse. \textbf{Async
        only} — no native value in the same frame (\texttt{measure} $\to$ layout jump).
  \item One channel: list updates and \texttt{onScroll} floods compete. All modules
        \textbf{init at startup} (slow TTI); untyped $\to$ runtime crashes on bad args.
\end{itemize}

\section{New Architecture — the pieces}
\begin{itemize}
  \item \textbf{JSI}: engine-agnostic C++ API (\texttt{jsi::Runtime}, \texttt{HostObject},
        \texttt{HostFunction}). JS holds a C++ reference and calls it — no
        serialisation, \textbf{sync} possible.
  \item \textbf{Fabric}: C++ renderer shared iOS/Android. \emph{Immutable} shadow tree
        (clone-on-write, thread-safe), diff in C++, \textbf{view flattening} (layout-only
        Views get no native view). Enables \textbf{concurrent React} and
        \textbf{sync layout} reads (\texttt{useLayoutEffect} without flicker).
  \item \textbf{TurboModules}: native modules over JSI, \textbf{lazy} (first access), typed.
  \item \textbf{Codegen}: at build (pod install / Gradle) turns the TS/Flow spec into C++
        glue + ObjC++ protocol (\texttt{NativeFooSpec}) + Java class. Wrong type =
        \emph{compile} error.
  \item \textbf{Bridgeless}: no Bridge object at all. \textbf{Interop layer}: legacy
        \texttt{RCT\_EXPORT\_MODULE} modules and \texttt{requireNativeComponent} views
        still run — the migration path.
\end{itemize}

\section{Example — a TurboModule spec}
\begin{lstlisting}[language=TSSheet]
// specs/NativeHaptics.ts   (name MUST start "Native")
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
export interface Spec extends TurboModule {
  isSupported(): boolean;          // SYNC over JSI
  impact(style: string): void;     // fire-and-forget
  prepare(): Promise<void>;        // async, resolves later
}
export default TurboModuleRegistry.getEnforcing<Spec>('Haptics');
// + package.json codegenConfig; iOS .mm adopts <NativeHapticsSpec>
\end{lstlisting}

\section{Render → commit → mount}
\begin{tikzpicture}[sheet]
  \node[js, minimum width=22mm, minimum height=11mm] (r) at (0,0)
       {\textbf{Render}\\\tiny React runs components\\\tiny $\to$ C++ shadow nodes};
  \node[cpp, minimum width=22mm, minimum height=11mm] (c) at (2.75,0)
       {\textbf{Commit}\\\tiny Yoga layout · tree\\\tiny promoted as ``next''};
  \node[ui, minimum width=22mm, minimum height=11mm] (m) at (5.5,0)
       {\textbf{Mount}\\\tiny diff old/new tree $\to$\\\tiny create/update views};
  \draw[flow] (r) -- (c); \draw[flow] (c) -- (m);
  \node[lbl] at (0,-0.85) {JS thread\\interruptible (concurrent)};
  \node[lbl] at (2.75,-0.85) {JS or background\\thread};
  \node[lbl] at (5.5,-0.85) {UI thread only\\atomic, per frame};
\end{tikzpicture}
\begin{itemize}
  \item Several tree revisions: laid out off-main, swapped in atomically — never a
        half-applied UI. \textbf{Yoga} = C++ flexbox, Auto Layout's role, solved off-main.
  \item Finger-tracking UI must not round-trip through JS: native-driven animation
        (Reanimated worklets, \texttt{useNativeDriver}).
\end{itemize}

\section{Hermes — JS engine (default since RN 0.70)}
\begin{itemize}
  \item \texttt{hermesc} compiles JS to \textbf{bytecode at build time}; app mmaps it —
        no parse on device $\to$ faster TTI, less RAM. \textbf{No JIT} (JSC in an iOS
        app has none either). Concurrent GC \textbf{Hades}; debug via Chrome DevTools
        Protocol (React Native DevTools). Trades peak CPU throughput for startup.
\end{itemize}

\section{Interview traps}
\begin{itemize}
  \trap{``JSI makes RN fast'' — it cuts \emph{boundary} cost; JS is still 1 thread.}
  \trap{A slow \emph{sync} TurboModule method \textbf{blocks the JS thread} — I/O goes in
        \texttt{Promise} methods.}
  \trap{Bridge cost $\neq$ just JSON: \emph{async-only} + \emph{one queue} + \emph{eager init}.}
  \trap{Old remote debugging ran JS \emph{in Chrome} — impossible with JSI.}
  \trap{Swift-only module still needs ObjC++ glue — the spec is C++/ObjC++.}
\end{itemize}

\section{Remember}
\textbf{Bridge = mail (JSON letters). JSI = a phone line to C++.} Fabric draws,
TurboModules serve, Codegen types, Hermes pre-compiles.

\section{Likely questions}
\begin{enumerate}
  \item Why did the Bridge limit perf? — async JSON, one queue, eager init.
  \item Fabric adds? — C++ immutable tree, concurrent React, sync layout.
  \item Old module? — interop layer first; then TS spec + Codegen.
  \item Why Hermes? — AOT bytecode: faster start, less RAM.
\end{enumerate}

\end{multicols}

\noindent{\footnotesize\color{sheetGrey}\textit{Related:} rn-rendering-reconciliation ·
react-hooks-deep · Reanimated worklets · Expo Modules · GCD main queue · Instruments}

\end{document}
