Compiler Design Goals
Automatic Reactivity and Memoization
The primary design goal of the React Compiler is to automate the performance optimizations that developers previously managed manually via useMemo, useCallback, and React.memo. By transforming standard JavaScript into a "reactive" version, the compiler ensures that component re-renders only trigger downstream computations when their specific dependencies have changed.
The Reactive Scope Model
The compiler operates on a concept called Reactive Scopes. Instead of memoizing individual values, the compiler identifies blocks of instructions that produce one or more values and groups them into a scope.
A scope is invalidated if any of its inputs (dependencies) change. This architecture allows the compiler to:
- Handle Interleaved Mutations: If two variables are mutated in an interleaved fashion, they are grouped into a single reactive scope to maintain consistency.
- Minimize Overhead: By grouping related logic, the compiler reduces the number of cache lookups and comparison operations required at runtime.
Escape Analysis and Pruning
To maintain a balance between performance and code size, the compiler performs Escape Analysis. Not every value in a React component needs to be memoized. The compiler identifies "escaping" values—those that are:
- Returned directly from the component or hook.
- Passed as arguments to hooks (e.g., a dependency array or a callback to
useEffect). - Transitively aliased by a value that escapes.
If a value does not escape and is not a dependency of an escaping value, the compiler prunes its reactive scope. This prevents unnecessary code-size bloat and JIT overhead.
function Component(props) {
// 'a' does not escape and is not used in the return:
// The compiler prunes the scope for 'a'.
const a = { value: props.a };
// 'b' is returned:
// The compiler creates a reactive scope for 'b'.
const b = { value: props.b };
return b;
}
Preservation of React Semantics
The compiler is designed to enforce and preserve the "Rules of React." It includes a validation layer that prevents common anti-patterns that would break reactivity or the component lifecycle.
Capitalized Call Validation
In React, capitalized names are reserved for components. Invoking a capitalized function directly (e.g., Header()) instead of using JSX (e.g., <Header />) can lead to unexpected hook behavior and inconsistent state. The compiler validates that capitalized functions are not invoked as standard function calls.
// Compiler Error: Capitalized functions are reserved for components.
// If this is a component, render it with JSX.
function Parent() {
const x = SomeComponent(); // Validation Error
return <div />;
}
Hook Patterns
The compiler recognizes standard hook patterns and ensures they are treated as escaping points. It supports custom hook patterns via configuration, allowing developers to define what constitutes a hook call within their specific architecture.
Configuration and Opt-in
The compiler is designed for gradual adoption. Developers can control the compiler's behavior through Babel plugin configurations and file-level directives.
Key Configuration Options:
| Option | Type | Description |
| :--- | :--- | :--- |
| compilationMode | "infer" \| "all" | Determines whether the compiler should attempt to compile all components or only those it can safely infer. |
| hookPattern | string | A regex string used to identify custom hooks for escape analysis. |
| validateNoCapitalizedCalls | boolean | Enables or disables the validation of capitalized function calls. |
File-level Directives:
// @expectNothingCompiled: Used in tests and specific files to ensure the compiler skips the file.// @validatePreserveExistingMemoizationGuarantees: Forces the compiler to ensure that existinguseMemooruseCallbackblocks are not "de-optimized" during transformation.
Static Analysis and HIR
Internally, the compiler transforms source code into a High-level Intermediate Representation (HIR). This representation allows the compiler to perform complex data-flow analysis, such as:
- Inferring Mutation: Detecting if a value is mutated after its creation to determine its "reach" within a scope.
- Alias Tracking: Mapping how values are shared across different variables to ensure that if one alias escapes, the entire dependency graph is correctly memoized.