outlineJSX
File
src/Optimization/OutlineJsx.ts
Purpose
This pass outlines nested JSX elements into separate component functions. When a callback function contains JSX, this pass can extract that JSX into a new component, which enables:
- Better code splitting - Outlined components can be lazily loaded
- Memoization at component boundaries - React's reconciliation can skip unchanged subtrees
- Reduced closure captures - Outlined components receive props explicitly
The pass specifically targets JSX within callbacks (like .map() callbacks) rather than top-level component returns.
Input Invariants
- The
enableJsxOutliningfeature flag must be enabled - The function must be a React component or hook
- JSX must appear within a nested function expression (callback)
Output Guarantees
- Nested functions containing only JSX returns are extracted as separate components
- The original callback is replaced with a call to the outlined component
- Captured variables become explicit props to the outlined component
- The outlined component is registered with the environment for emission
Algorithm
Phase 1: Identify Outlinable JSX
function outlineJsxImpl(fn: HIRFunction, outlinedFns: Array<HIRFunction>): void {
for (const [, block] of fn.body.blocks) {
for (const instr of block.instructions) {
if (instr.value.kind === 'FunctionExpression') {
const innerFn = instr.value.loweredFunc.func;
// Check if function only returns JSX
if (canOutline(innerFn)) {
const outlined = createOutlinedComponent(innerFn);
outlinedFns.push(outlined);
replaceWithComponentCall(instr, outlined);
}
}
}
}
}
Phase 2: Check Outlinability
function canOutline(fn: HIRFunction): boolean {
// Must have exactly one block with only JSX-related instructions
// Must end with returning JSX
// Must not have complex control flow
return (
fn.body.blocks.size === 1 &&
returnsJSX(fn) &&
!hasComplexControlFlow(fn)
);
}
Phase 3: Create Outlined Component
function createOutlinedComponent(fn: HIRFunction): HIRFunction {
// Convert captured context to props
const props = fn.context.map(capture => ({
name: capture.identifier.name,
type: capture.identifier.type,
}));
// Create new component function
return {
...fn,
params: [{kind: 'Identifier', name: 'props', ...}],
context: [], // No captures - all via props
};
}
Phase 4: Replace Original Callback
function replaceWithComponentCall(instr: Instruction, outlined: HIRFunction): void {
// Original: items.map(item => <Stringify item={item} />)
// Becomes: items.map(item => <OutlinedComponent item={item} />)
instr.value = {
kind: 'JSX',
tag: {kind: 'LoadGlobal', name: outlined.id},
props: capturedVariablesToProps(instr.value.context),
};
}
Phase 5: Register Outlined Functions
export function outlineJSX(fn: HIRFunction): void {
const outlinedFns: Array<HIRFunction> = [];
outlineJsxImpl(fn, outlinedFns);
for (const outlinedFn of outlinedFns) {
fn.env.outlineFunction(outlinedFn, 'Component');
}
}
Edge Cases
Context Captures
Variables captured by the callback become props: ```javascript // Before: items.map(item => )
// After (outlined): function OutlinedCard(props) { return ; } items.map(item => ) ```
Complex Control Flow
Callbacks with conditionals or loops are not outlined:
javascript
// Not outlined - has conditional
items.map(item => item.show ? <Card item={item} /> : null)
Multiple JSX Returns
Only single-JSX-return callbacks are outlined:
javascript
// Not outlined - multiple potential returns
items.map(item => {
if (item.type === 'a') return <TypeA item={item} />;
return <TypeB item={item} />;
})
Top-Level JSX
Only JSX in nested callbacks is outlined, not component return values:
javascript
function Component() {
return <div />; // Not outlined - this is the component's return
}
Recursive Outlining
The pass recursively processes outlined components to outline their nested JSX.
TODOs
None in the source file.
Example
Fixture: outlined-helper.js
Input:
javascript
// @enableFunctionOutlining
function Component(props) {
return (
<div>
{props.items.map(item => (
<Stringify key={item.id} item={item.name} />
))}
</div>
);
}
After OutlineJSX: ``` // Outlined component: function _outlined_Component$1(props) { return ; }
// Original component modified: function Component(props) { return (
Generated Code: ```javascript function _outlined_Component$1(props) { const $ = _c(2); const item = props.item; let t0; if ($[0] !== item.id || $[1] !== item.name) { t0 = ; $[0] = item.id; $[1] = item.name; } else { t0 = $[1]; } return t0; }
function Component(props) { const $ = _c(2); let t0; if ($[0] !== props.items) { t0 = props.items.map((item) => <_outlined_Component$1 item={item} />); $[0] = props.items; $[1] = t0; } else { t0 = $[1]; } return
Key observations:
- The map callback JSX is extracted into _outlined_Component$1
- The item variable becomes a prop instead of a closure capture
- The outlined component gets its own memoization cache
- This enables React to skip re-rendering unchanged list items