Open-Source Wikis

/

React

/

React Compiler

/

Compiler passes

facebook/react

Compiler passes

A pass-by-pass walkthrough of the React Compiler pipeline. Source: compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts.

The pipeline is a single linear sequence run per function. Below the passes are grouped into phases for readability — the actual file just runs them one after another.

Phase 1: lower the Babel AST to HIR

Pass File What it does
findContextIdentifiers compiler/packages/babel-plugin-react-compiler/src/HIR/FindContextIdentifiers.ts Walks the surrounding scope and records which identifiers are defined outside the function but reachable inside. These become context Places on the resulting HIRFunction.
lower compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts (large) Converts a Babel FunctionDeclaration/ArrowFunctionExpression/FunctionExpression to an HIRFunction — basic blocks, instructions, terminals, phi nodes.

Phase 2: pre-SSA cleanup

Pass File What it does
pruneMaybeThrows compiler/packages/babel-plugin-react-compiler/src/Optimization/PruneMaybeThrows.ts Removes MaybeThrow terminals that turn out not to throw, simplifying the CFG.
validateContextVariableLValues Validation/ValidateContextVariableLValues.ts Catches assignments to let bindings used outside the function.
validateUseMemo Validation/ValidateUseMemo.ts Validates the shape of useMemo calls (callback + deps array).
dropManualMemoization (optional) Inference/DropManualMemoization.ts If enableDropManualMemoization is on, replaces user-written useMemo/useCallback with their inner expressions so the compiler can re-memoize uniformly.
inlineImmediatelyInvokedFunctionExpressions Inference/InlineImmediatelyInvokedFunctionExpressions.ts (() => x)()x.
mergeConsecutiveBlocks Inference/MergeConsecutiveBlocks.ts Combines blocks with single-successor / single-predecessor relationships.

Phase 3: SSA + early type info

Pass File What it does
enterSSA compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts Renames identifiers so each Identifier has a single defining instruction; introduces Phi nodes at joins.
eliminateRedundantPhi SSA/EliminateRedundantPhi.ts Deletes phi nodes whose operands are all the same.
constantPropagation Optimization/ConstantPropagation.ts Propagates compile-time constants.
inferTypes TypeInference/InferTypes.ts Lightweight type inference (Object, Array, Function, Primitive, etc.). Used by later passes to decide what's safe to memoize.

Phase 4: validations that need only types

Pass What it catches
validateHooksUsage Hooks called conditionally / in loops / outside React functions.
validateNoCapitalizedCalls Foo() where Foo looks like a component (capitalized identifier) — likely a misuse.

These run only if env.enableValidations is true (the default for components).

Phase 5: aliasing analysis

Pass File What it does
optimizePropsMethodCalls Optimization/OptimizePropsMethodCalls.ts Recognizes patterns like props.x.toString() and avoids treating them as full mutations.
analyseFunctions Inference/AnalyseFunctions.ts Computes per-instruction aliasing effects for nested function expressions (e.g. event handlers).
inferMutationAliasingEffects Inference/InferMutationAliasingEffects.ts Populates Instruction.effects with AliasingEffects — Capture, Alias, Mutate, Freeze, Render, etc.
optimizeForSSR (SSR mode only) Optimization/OptimizeForSSR.ts SSR-specific simplifications.
deadCodeElimination Optimization/DeadCodeElimination.ts Removes instructions whose results are never used.
pruneMaybeThrows (again) as above Another pass after DCE.
inferMutationAliasingRanges Inference/InferMutationAliasingRanges.ts Computes the range over which each mutation could be observed — a key input to memoization correctness.

Phase 6: deep validations (Rules of React)

This is where most of the user-visible diagnostics come from. They run in this order, each gated on its own config flag:

Validation File
validateLocalsNotReassignedAfterRender Validation/ValidateLocalsNotReassignedAfterRender.ts
validateNoRefAccessInRender Validation/ValidateNoRefAccessInRender.ts (~32 KB — many cases)
validateNoSetStateInRender Validation/ValidateNoSetStateInRender.ts
validateNoDerivedComputationsInEffects (or its experimental version) Validation/ValidateNoDerivedComputationsInEffects.ts / _exp.ts
validateNoSetStateInEffects (lint mode) Validation/ValidateNoSetStateInEffects.ts
validateNoJSXInTryStatement (lint mode) Validation/ValidateNoJSXInTryStatement.ts
validateNoFreezingKnownMutableFunctions Validation/ValidateNoFreezingKnownMutableFunctions.ts
validateExhaustiveDependencies (after reactivity inference) Validation/ValidateExhaustiveDependencies.ts
validateStaticComponents (lint mode) Validation/ValidateStaticComponents.ts
validatePreservedManualMemoization (later phase) Validation/ValidatePreservedManualMemoization.ts

The biggest of these — ValidateNoRefAccessInRender — has dozens of subtle cases for what counts as a ref access during render.

Phase 7: reactivity inference

Pass File What it does
inferReactivePlaces Inference/InferReactivePlaces.ts Marks each Place as reactive (depends on props/state/context) or not.
rewriteInstructionKindsBasedOnReassignment SSA/RewriteInstructionKindsBasedOnReassignment.ts Converts Reassign instructions back to Assign where the SSA dance allows.
inferReactiveScopeVariables ReactiveScopes/InferReactiveScopeVariables.ts Computes the reactive scopes — the spans of HIR that will be wrapped in memo cache lookups.

If enableMemoization is off, no scopes are created and the rest of the pipeline mostly no-ops. This is how lint mode bypasses memoization.

Phase 8: reactive-tree transformations

After buildReactiveFunction, the IR shifts from HIRFunction to ReactiveFunction — a tree shaped more like the input source code, with reactive scopes annotated. The remaining passes operate on this representation:

Pass File What it does
buildReactiveFunction ReactiveScopes/BuildReactiveFunction.ts The CFG → tree conversion.
pruneUnusedLabels ReactiveScopes/PruneUnusedLabels.ts Discards block labels nothing branches to.
pruneNonEscapingScopes ReactiveScopes/PruneNonEscapingScopes.ts Drops scopes whose values never escape — there is nothing to memoize.
pruneAlwaysInvalidatingScopes ReactiveScopes/PruneAlwaysInvalidatingScopes.ts Drops scopes that would invalidate every render anyway.
pruneNonReactiveDependencies ReactiveScopes/PruneNonReactiveDependencies.ts Removes deps that aren't reactive.
mergeReactiveScopesThatInvalidateTogether ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.ts Coalesces sibling scopes with the same dep set.
flattenReactiveLoopsHIR ReactiveScopes/FlattenReactiveLoopsHIR.ts Loops can't host scopes; this lifts them.
flattenScopesWithHooksOrUseHIR ReactiveScopes/FlattenScopesWithHooksOrUseHIR.ts Hooks and use(...) can't be inside a memoization block; this lifts them.
propagateEarlyReturns ReactiveScopes/PropagateEarlyReturns.ts Ensures early-return paths through scopes are sane.
propagateScopeDependenciesHIR HIR/PropagateScopeDependenciesHIR.ts Final dep set computation per scope.
alignReactiveScopesToBlockScopesHIR ReactiveScopes/AlignReactiveScopesToBlockScopesHIR.ts Aligns reactive scopes with JS block boundaries so codegen is clean.
alignMethodCallScopes ReactiveScopes/AlignMethodCallScopes.ts Special handling for obj.method(...) calls.
alignObjectMethodScopes ReactiveScopes/AlignObjectMethodScopes.ts Object literal methods.
extractScopeDeclarationsFromDestructuring ReactiveScopes/ExtractScopeDeclarationsFromDestructuring.ts Pulls declarations out of destructuring patterns.
pruneHoistedContexts ReactiveScopes/PruneHoistedContexts.ts Cleans up after hoisting transformations.
pruneUnusedScopes ReactiveScopes/PruneUnusedScopes.ts Final scope cleanup.
pruneUnusedLValues ReactiveScopes/PruneUnusedLValues.ts Drops dead lvalues.
renameVariables ReactiveScopes/RenameVariables.ts Resolves SSA name conflicts before codegen.
promoteUsedTemporaries ReactiveScopes/PromoteUsedTemporaries.ts Promotes temporaries that need stable names.
outlineFunctions Optimization/OutlineFunctions.ts Hoists nested functions into top-level helpers where it improves dedup.
outlineJSX Optimization/OutlineJsx.ts Same for JSX subtrees that don't depend on local state.
nameAnonymousFunctions Transform/NameAnonymousFunctions.ts Names anonymous arrow functions for better dev tools / stack traces.
memoizeFbtAndMacroOperandsInSameScope ReactiveScopes/MemoizeFbtAndMacroOperandsInSameScope.ts Special-cases for the fbt translation macro and a handful of others.
stabilizeBlockIds ReactiveScopes/StabilizeBlockIds.ts Renumbers block ids deterministically so codegen output is stable.
assertScopeInstructionsWithinScopes invariant pass Sanity check.
assertWellFormedBreakTargets invariant pass Sanity check.

Phase 9: codegen

Pass File What it does
codegenFunction ReactiveScopes/CodegenReactiveFunction.ts Walks the ReactiveFunction and emits Babel AST: const $ = c(N);, the per-scope if ($[i] !== dep) ... blocks, and the original logic interleaved.

The output is a CodegenFunction ASTish object. The Babel plugin then replaces the original function in the source AST with this output.

Per-pass invariants

Several passes assert structural invariants:

  • assertConsistentIdentifiers — every Identifier is reached only once (post-SSA).
  • assertTerminalSuccessorsExist — block terminals point at real blocks.
  • assertTerminalPredsExist — every block claims the right predecessors.
  • assertValidBlockNesting — block parents form a valid tree.
  • assertValidMutableRanges — mutable ranges respect the SSA dominance frontier.

These are turned on in dev / test builds and silently become no-ops in production builds. They are the first thing to check when a fixture suddenly fails after an unrelated pass change.

Where to read source

For the canonical ordering, see compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts. For each individual pass, the file paths above are the source of truth — most are between 100 and 1000 lines and largely self-contained.

For testing a new pass, see compiler/CLAUDE.md's description of the snap test runner, the error.todo-*.js / error.bug-*.js fixture conventions, and the "compile arbitrary file" workflow (yarn snap compile <path>).

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Compiler passes – React wiki | Factory