Skip to main content

How React Compiler Works

React Compiler transforms React components through a multi-phase pipeline, converting your code into an optimized version with automatic memoization.

High-Level Architecture

The compiler pipeline consists of seven major phases:

Phase 1: HIR Construction

Lowering to HIR

The compiler first converts Babel’s Abstract Syntax Tree (AST) to a High-level Intermediate Representation (HIR). HIR represents code as a control-flow graph (CFG) with:
  • Basic blocks: Sequences of instructions
  • Instructions: Individual operations
  • Terminals: Control flow between blocks (if, return, loop, etc.)

SSA Conversion

The HIR is converted to Static Single Assignment (SSA) form, where:
  • Each variable is assigned exactly once
  • Phi nodes represent values from different control flow paths
  • Enables precise dataflow analysis

Phase 2: Optimization

Early optimization passes improve code quality:

Constant Propagation

Replaces variables with known constant values:

Dead Code Elimination

Removes unreferenced instructions:

Phase 3: Type & Effect Inference

Type Inference

The compiler infers types using constraint-based unification:
  • Primitives (string, number, boolean, null, undefined)
  • Objects (with shape information)
  • Functions (including hooks)
  • Arrays and JSX elements

Effect Inference

Infers aliasing and mutation effects through abstract interpretation: Effect Types:
Example:

Phase 4: Reactive Scope Construction

Inferring Reactive Scopes

The compiler groups instructions that should invalidate together into reactive scopes:

Scope Alignment

Scopes are aligned to:
  • Control flow boundaries (if/else, loops)
  • Method call receivers
  • Object method declarations
  • Block scopes

Scope Merging

Overlapping or always-invalidating-together scopes are merged:

Phase 5: HIR → Reactive Function

The control-flow graph is converted to a tree structure (ReactiveFunction) where:
  • Scopes become explicit nodes
  • Dependencies are tracked
  • Nesting relationships are preserved

Phase 6: Reactive Function Optimization

Pruning Passes

Multiple passes prune unnecessary scopes:
  • Non-escaping scopes: Values that don’t escape the function
  • Scopes with hooks: Can’t memoize hook calls
  • Always-invalidating: Scopes that always recompute
  • Unused scopes: Empty or unreferenced scopes

Dependency Optimization

Phase 7: Code Generation

The final phase generates optimized JavaScript:

Memoization Cache

Scope Structure

When the Compiler Bails Out

The compiler may bail out (skip compilation) when:

Unsupported JavaScript Features

  • eval() calls
  • with statements
  • var declarations (use let/const)
  • Nested class declarations

Rule Violations

  • Conditional hook calls
  • Unconditional setState during render
  • Direct ref.current access in render
  • Mutations after value is frozen

Complex Patterns

  • Deeply nested closures capturing mutable state
  • Dynamically created components
  • Certain imperative patterns

Compiler Error Types

Todo Errors (Graceful Bailout)

Known limitation, compiler skips this function but continues.

Invariant Errors (Hard Failure)

Unexpected state, indicates compiler bug or invalid input.

Validation Errors

Code violates Rules of React, must be fixed.

Performance Characteristics

Compilation Time

  • Typically 1-5ms per component
  • Scales linearly with code size
  • Cached between builds

Runtime Overhead

  • Memoization checks: O(1) per scope
  • Cache storage: ~1-2 slots per reactive value
  • Minimal memory overhead

Optimization Benefits

  • Reduces re-renders by 50-90% in typical apps
  • Eliminates need for manual memoization
  • Improves performance on low-end devices

Next Steps

Architecture

Deep dive into compiler passes

HIR

Understanding the intermediate representation

Optimization Passes

Learn about specific optimizations

Configuration

Configure compiler behavior