facebook/react
Compiler architecture
The React Compiler is a single-pass-per-function Babel/SWC plugin built around a custom HIR (high-level intermediate representation) and a long, linear pipeline of analysis and transformation passes. This page explains its shape; passes catalogs the individual passes.
High-level shape
graph TD Babel[Babel/SWC visit FunctionDeclaration] -->|NodePath| Plugin[index.ts: BabelPluginReactCompiler] Plugin -->|filter: is this a React fn?| Program[Entrypoint/Program.ts] Program -->|per function| Pipeline[Entrypoint/Pipeline.ts] Pipeline -->|lower AST → HIR| HIR[HIR.ts] HIR -->|N transformation + analysis passes| Reactive[ReactiveFunction] Reactive -->|codegen| AST[Codegen → t.FunctionDeclaration] AST -->|replace original| Babel
Each pass gets the HIR (or, late in the pipeline, the ReactiveFunction representation that is HIR + reactive scopes), mutates it in place or returns a new structure, and the next pass picks up where it left off. The pipeline is not staged — it is a single linear sequence in compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts.
The Environment
compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts is the per-function context object that flows through every pass. It holds:
config: EnvironmentConfig— feature flags + per-pass options.- The error accumulator (
#errorsplusrecordError,tryRecord,hasErrors,aggregateErrors— seecompiler/CLAUDE.mdfor a deep discussion). - The unique
IdentifierIdallocator. - The currently-known set of context identifiers (vars from the enclosing scope).
logger— the optional debug-IR logger.
Once an Environment is constructed in Entrypoint/Pipeline.ts:run, the original EnvironmentConfig goes out of scope — every later pass reaches flags through env.config so the source of truth is unambiguous.
HIR
Defined in compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts. The shape:
HIRFunction {
body: { blocks: Map<BlockId, BasicBlock>, entry: BlockId },
context: Place[], // captured outer-scope vars
params: Place[],
returns: Place,
aliasingEffects: AliasingEffect[],
}
BasicBlock {
id: BlockId,
kind: 'block' | 'value' | 'sequence' | 'catch' | 'loop',
phis: Set<Phi>,
instructions: Instruction[],
terminal: Terminal,
preds: Set<BlockId>,
}
Instruction {
id: InstructionId,
lvalue: Place,
value: InstructionValue, // CallExpression, FunctionExpression, LoadLocal, ...
effects?: AliasingEffect[],
}
Place { identifier: Identifier, ... }
Identifier { id: IdentifierId, ... }It's a classic SSA-friendly CFG with phi nodes at block joins. The enterSSA pass is what turns the post-lower HIR into proper SSA form.
AliasingEffects
Side-effects are first-class. Each instruction can carry a list of AliasingEffects describing what the operation does to the data flow:
Capture a -> b—ais captured (mutably) intob.Alias a -> b—baliasesa.ImmutableCapture a -> b— read-only capture.Assign a -> b— direct assignment.Mutate v/MutateTransitive v/MutateConditionally v— mutation.Render p—pis read during render (e.g. JSX prop).Freeze p—pis frozen.Create p— new value created.CreateFunction— function expression created (withcaptures: Place[]).Apply— function application with receiver, function, args, result.
Defined in compiler/packages/babel-plugin-react-compiler/src/Inference/AliasingEffects.ts. The aliasing analysis (inferMutationAliasingEffects) populates these effects; later validation passes consume them.
Hook signatures
compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts is the central registry of known hooks and what they do to their arguments. Each hook is described as:
aliasing: {
receiver: '@receiver',
params: ['@fn'], // names for positional params
rest: null,
returns: '@returns',
temporaries: [],
effects: [
{ kind: 'Freeze', value: '@fn', reason: ValueReason.HookCaptured },
{ kind: 'Assign', from: '@fn', into: '@returns' },
],
}This is how the compiler knows that useState's setter is stable, that useMemo's callback's argument is read during render, that useEffect's callback's args are not, and that useEffectEvent's callback's args are not (which avoids a class of false positives — see compiler/CLAUDE.md's discussion of UseEffectEventHook).
Pipeline shape
Pipeline.ts is ~560 lines. Roughly:
- Lowering:
lower(func, env)walks the Babel AST and produces an HIRHIRFunction. - Pre-SSA cleanup:
pruneMaybeThrows,inlineImmediatelyInvokedFunctionExpressions,mergeConsecutiveBlocks. - SSA:
enterSSA,eliminateRedundantPhi. - Constant propagation, type inference:
constantPropagation,inferTypes. - Aliasing inference:
analyseFunctions,inferMutationAliasingEffects,deadCodeElimination,inferMutationAliasingRanges. - Validation: a long list —
validateHooksUsage,validateNoCapitalizedCalls,validateNoRefAccessInRender,validateNoSetStateInRender,validateNoSetStateInEffects,validateNoDerivedComputationsInEffects,validateNoFreezingKnownMutableFunctions,validateLocalsNotReassignedAfterRender,validateNoJSXInTryStatement,validateStaticComponents,validateExhaustiveDependencies,validatePreservedManualMemoization,validateUseMemo,validateContextVariableLValues. - Reactive scope inference:
inferReactivePlaces,inferReactiveScopeVariables. This is what determines the units of memoization — the spans of code that will be wrapped inif ($[i] !== x) ...blocks. - Reactive-tree transformations:
buildReactiveFunction, then a long list of cleanups:pruneNonEscapingScopes,pruneAlwaysInvalidatingScopes,pruneNonReactiveDependencies,mergeReactiveScopesThatInvalidateTogether,flattenReactiveLoopsHIR,flattenScopesWithHooksOrUseHIR,propagateEarlyReturns,propagateScopeDependenciesHIR,alignReactiveScopesToBlockScopesHIR,alignMethodCallScopes,alignObjectMethodScopes,extractScopeDeclarationsFromDestructuring,pruneHoistedContexts,pruneUnusedScopes,pruneUnusedLValues,pruneUnusedLabels,renameVariables,promoteUsedTemporaries,outlineFunctions,outlineJSX,nameAnonymousFunctions,stabilizeBlockIds. - Codegen:
codegenFunction(reactiveFn)returns an AST that the Babel plugin substitutes for the originalFunctionDeclaration.
Each step in Pipeline.ts is preceded by an env.logger?.debugLogIRs?.({...}) call that, when yarn snap -d is on, dumps the HIR after that step. This is the primary debugging tool when working on a pass.
Fault tolerance
Validation passes are wrapped in env.tryRecord(() => pass(hir)). If they throw a CompilerError.throwTodo() (graceful bailout) or a non-invariant CompilerError, the error is recorded and pipeline execution continues. CompilerError.invariant() is reserved for "this should be impossible" and is not caught — that aborts the pipeline immediately.
Infrastructure passes (lowering, SSA construction, codegen) are not wrapped because later passes depend on their structural correctness.
At the end of the pipeline, env.hasErrors() is checked and env.aggregateErrors() produces a single CompilerError describing every recorded problem. The Babel plugin entry decides what to do with that — bail out on the function and leave it un-compiled, surface as an ESLint diagnostic, or fail the build, depending on outputMode.
Output modes
CompilerOutputMode (in Entrypoint/index.ts) selects the operating mode:
'ast'— full compilation: emit the rewritten function. The default for the Babel plugin.'lint'— only run validations; skip codegen. Used by the ESLint plugin andreact-forgive.'ssr'— variant that callsoptimizeForSSR(hir)before reactive-scope inference.
Where to make a change
- A new validation rule: add a new file under
compiler/packages/babel-plugin-react-compiler/src/Validation/, then register it in theValidation/index.tsexport list and call it conditionally from the validation block ofPipeline.ts. Add acompiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.<descriptive-name>.jsfixture that exercises it. - A new optimization or transformation: add a file under
Optimization/orReactiveScopes/, plug it intoPipeline.tsat the right spot, and add fixtures. - A new hook to teach the compiler about: edit
compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.tsto add the hook'saliasingsignature. - A new feature flag: add it in
EnvironmentConfig(HIR/Environment.ts), then gate the new code onenv.config.<flagName>.
For a step-by-step walkthrough of every pass and what it produces, see passes.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.