Architecture - rahulpandita/react-term GitHub Wiki
react-term separates VT parsing and rendering from the main thread using Web Workers and SharedArrayBuffer. The main thread acts only as an event dispatcher — it receives DOM input events, forwards them to the terminal, and never blocks on terminal I/O.
Rendering Strategies
Strategy selection is automatic at runtime based on browser capability.
| Strategy | Requirements | Parser Location | Renderer Location |
|---|---|---|---|
| Full Worker | SharedArrayBuffer + OffscreenCanvas | Parser Worker | Render Worker (WebGL2) |
| Parser Worker | SharedArrayBuffer only | Parser Worker | Main Thread (WebGL2) |
| Main Thread | None | Main Thread | Main Thread (Canvas 2D) |
Data Flow
The following diagram shows how data flows from a PTY or WebSocket through the off-main-thread pipeline to the canvas.
flowchart LR
PTY["PTY / WebSocket"] --> MT["Main Thread\nWebTerminal"]
MT -->|"Uint8Array chunks"| WB["WorkerBridge\n(flow control)"]
WB -->|"postMessage"| PW["Parser Worker\n(VTParser)"]
PW -->|"Atomics.store"| SAB["SharedArrayBuffer\nCellGrid"]
PW -->|"FlushMessage"| MT
MT -->|"update cursor"| RB["RenderBridge"]
SAB --> RW["Render Worker\n(WebGL2)"]
RB -->|"OffscreenCanvas init"| RW
RW -->|"drawFrame"| CANVAS["Canvas Element"]
MT -->|"DOM events"| IH["InputHandler"]
IH -->|"onData callback"| APP["Application"]
SharedArrayBuffer Cell Grid
The CellGrid is the shared state between all threads. It is backed by a SharedArrayBuffer so both the parser worker and the render worker can access terminal state without serialization.
Each cell is stored as 2 × Uint32 (8 bytes):
- Word 0: Unicode codepoint
- Word 1: attributes — color, bold/italic/underline/inverse flags, and
ATTR_WIDE(bit 15) for wide characters
A second SharedArrayBuffer (dirtyRows) tracks which rows need repainting. The parser marks rows dirty after writing cells; the renderer reads and clears dirty bits each frame.
Wide characters (CJK, emoji, fullwidth forms) set the ATTR_WIDE flag on the left cell and write a spacer cell (codepoint 0) in the next column. All renderers detect and skip spacer cells via CellGrid.isSpacerCell(), and render wide characters at 2× cell width.
Worker Lifecycle
sequenceDiagram
participant MT as Main Thread
participant PW as Parser Worker
participant RW as Render Worker
MT->>PW: init(SAB, cols, rows)
MT->>RW: init(OffscreenCanvas, SAB, cols, rows)
loop Data
MT->>PW: write(chunk)
PW->>PW: VTParser.parse()
PW-->>MT: FlushMessage
MT->>RW: update(cursor, selection)
RW->>RW: drawFrame (rAF)
end
MT->>PW: dispose()
MT->>RW: dispose()
Child Pages
- Core Package — CellGrid, Buffer, VTParser, wcwidth
- Web Package — WebTerminal, workers, renderers, addons
- React Package — Terminal and TerminalPane React components
- Native Package — NativeTerminal, Skia renderer, gestures