Skip to main content

Architecture

TypeScript (consumer)

│ synchronous JSI call, no bridge, no JSON serialization

Nitro Module (C++ binding layer)

├──► Lexbor (C99)
│ ├── WHATWG-compliant HTML parsing
│ ├── In-memory DOM tree
│ ├── querySelector / querySelectorAll
│ ├── getAttribute / setAttribute
│ ├── textContent / innerHTML
│ └── serialize() → HTML string

└──► QuickJS (C)
├── Isolated JS runtime per instance
├── window / document stubs
├── DOM bindings → delegates to Lexbor
├── setTimeout / setInterval (own event loop)
├── console.log → forwarded to RN
└── Executes arbitrary user scripts

Why these technologies?

LayerChoiceReason
Native bindingNitro ModulesSynchronous, type-safe JSI bindings. Zero bridge overhead
HTML + DOMLexborFastest WHATWG-compliant HTML parser in C99, zero dependencies
JS EngineQuickJSBuilt for embedding. Lightweight, fully isolated, ES2023 support
GlueC++Connects QuickJS ↔ Lexbor ↔ Nitro/JSI
Why not reuse the Hermes instance React Native already uses?

The Hermes runtime is a single shared instance, not designed for multiple isolated environments. QuickJS was built specifically for embedding and isolation, so each JSDOM.create() call gets its own independent runtime.