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