Code Generation & Pruning
The React Compiler optimizes your components by automatically memoizing values. However, to ensure the generated code is efficient and doesn't incur unnecessary overhead, the compiler performs a final stage of Escape Analysis and Scope Pruning. This process removes memoization for values that do not affect the output of the component.
Understanding Reactive Scopes
The compiler groups related instructions into Reactive Scopes. A scope is a block of code that should only re-execute if its dependencies change. During the code generation phase, the compiler decides which of these scopes are strictly necessary and which can be "pruned" (collapsed back into standard, non-memoized JavaScript).
Pruning Non-Escaping Scopes
Not every variable in a component needs to be memoized. To minimize code size and JIT overhead, the compiler identifies "non-escaping" values.
A value is considered to escape if:
- It is directly returned by the component or hook.
- It is transitively aliased by a return value (e.g., it is an element inside an array that is returned).
- It is passed as an argument to a hook (e.g., a closure passed to
useEffect).
If a value does not escape and is not used by any other escaping value, the compiler prunes its reactive scope.
Dependency Chain Preservation
Pruning is not always possible for non-escaping values. If a non-escaping value is a dependency for an escaping value, the compiler must continue to memoize it. Failing to do so would cause the downstream escaping value to invalidate on every render, breaking the memoization chain.
function Component(props) {
// 'a' does not escape, but it is a dependency of 'b'.
// To ensure 'b' stays memoized, 'a' must also be memoized.
const a = [props.a];
// 'b' is returned and therefore escapes.
const b = { data: a };
return b;
}
In the example above, even though a is never used outside this component, its scope is preserved to maintain the stability of b.
Code Generation Rules
The compiler follows specific rules during generation to ensure the output remains valid React and optimized for modern engines.
Capitalized Call Validation
React components must be invoked using JSX, not via direct function calls. During the code generation and validation phase, the compiler enforces a strict rule: Capitalized functions are reserved for components.
If you attempt to call a capitalized function directly, the compiler will throw an error or skip compilation for that function:
// ❌ This will trigger a validation error in the compiler
function Component() {
const x = SomeComponent();
return <div>{x}</div>;
}
// ✅ Correct usage
function Component() {
return <SomeComponent />;
}
If you have a utility function that is capitalized but is not a component, you can configure the compiler via validateNoCapitalizedCalls in your environment config to allowlist specific names or patterns.
Primitive Inlining
To keep the generated output clean, the compiler may inline simple expressions and primitives into their consuming scopes. If a value is non-reactive and non-escaping, the compiler avoids creating a temporary variable or a memoization block entirely, reducing the execution cost.
// Input
function Component() {
const className = "btn-" + "primary";
return <div className={className} />;
}
// Optimized Output (Simplified)
function Component() {
return <div className="btn-primary" />;
}
Configuration
You can influence how the compiler generates and prunes code through your Babel configuration:
| Option | Description |
| :--- | :--- |
| compilationMode | Set to "infer" to let the compiler decide what to optimize based on usage. |
| validateNoCapitalizedCalls | An array of strings to allowlist capitalized function calls that are not components. |
| hookPattern | A regex string to identify custom hooks (which are allowed to be called directly). |
By pruning unnecessary scopes and enforcing valid React patterns, the compiler ensures that the resulting JavaScript is as performant as hand-optimized code without the manual maintenance of useMemo and useCallback.