JSX Outlining (#30956)

Currently, the react compiler can not compile within callbacks which can
potentially cause over rendering. Consider this example:
```jsx
function Component(countries, onDelete) {
  const name = useFoo();
  return countries.map(() => {
    return (
      <Foo>
        <Bar name={name}/>
        <Baz onclick={onDelete} />
      </Foo>
    );
  });
}
```

In this case, there's no memoization of the nested jsx elements. But
instead if we were to manually refactor the nested jsx into separate
component like this:
```jsx
function Component(countries, onDelete) {
  const name = useFoo();
  return countries.map(() => {
    return <Temp name={name} onDelete={onDelete} />;
  });
}

function Temp({ name, onDelete }) {
  return (
    <Foo>
      <Bar name={name} />
      <Baz onclick={onDelete} />
    </Foo>
  );
}

```

The compiler can now optimise both these components:
```jsx
function Component(countries, onDelete) {
  const $ = _c(4);
  const name = useFoo();
  let t0;
  if ($[0] !== name || $[1] !== onDelete || $[2] !== countries) {
    t0 = countries.map(() => <Temp name={name} onDelete={onDelete} />);
    $[0] = name;
    $[1] = onDelete;
    $[2] = countries;
    $[3] = t0;
  } else {
    t0 = $[3];
  }
  return t0;
}

function Temp(t0) {
  const $ = _c(7);
  const { name, onDelete } = t0;
  let t1;
  if ($[0] !== name) {
    t1 = <Bar name={name} />;
    $[0] = name;
    $[1] = t1;
  } else {
    t1 = $[1];
  }
  let t2;
  if ($[2] !== onDelete) {
    t2 = <Baz onclick={onDelete} />;
    $[2] = onDelete;
    $[3] = t2;
  } else {
    t2 = $[3];
  }
  let t3;
  if ($[4] !== t1 || $[5] !== t2) {
    t3 = (
      <Foo>
        {t1}
        {t2}
      </Foo>
    );
    $[4] = t1;
    $[5] = t2;
    $[6] = t3;
  } else {
    t3 = $[6];
  }
  return t3;
}
```

Now, when `countries` is updated by adding one single value, only the
newly added value is re-rendered and not the entire list. Rather than
having to do this manually, this PR teaches the react compiler to do
this transformation.

This PR adds a new pass (`OutlineJsx`) to capture nested jsx statements
and outline them in a separate component. This newly outlined component
can then by memoized by the compiler, giving us more fine grained
rendering.
This commit is contained in:
Sathya Gunasekaran
2024-10-17 18:15:32 +01:00
committed by GitHub
parent 93077b5c6f
commit 97a5049c44
17 changed files with 1641 additions and 21 deletions
@@ -103,6 +103,7 @@ import {lowerContextAccess} from '../Optimization/LowerContextAccess';
import {validateNoSetStateInPassiveEffects} from '../Validation/ValidateNoSetStateInPassiveEffects';
import {validateNoJSXInTryStatement} from '../Validation/ValidateNoJSXInTryStatement';
import {propagateScopeDependenciesHIR} from '../HIR/PropagateScopeDependenciesHIR';
import {outlineJSX} from '../Optimization/OutlineJsx';
export type CompilerPipelineValue =
| {kind: 'ast'; name: string; value: CodegenFunction}
@@ -278,6 +279,10 @@ function* runWithEnvironment(
value: hir,
});
if (env.config.enableJsxOutlining) {
outlineJSX(hir);
}
if (env.config.enableFunctionOutlining) {
outlineFunctions(hir, fbtOperands);
yield log({kind: 'hir', name: 'OutlineFunctions', value: hir});
@@ -199,7 +199,7 @@ function insertNewOutlinedFunctionNode(
program: NodePath<t.Program>,
originalFn: BabelFn,
compiledFn: CodegenFunction,
): NodePath<t.Function> {
): BabelFn {
switch (originalFn.type) {
case 'FunctionDeclaration': {
return originalFn.insertAfter(
@@ -491,18 +491,11 @@ export function compileProgram(
fn.skip();
ALREADY_COMPILED.add(fn.node);
if (outlined.type !== null) {
CompilerError.throwTodo({
reason: `Implement support for outlining React functions (components/hooks)`,
loc: outlined.fn.loc,
queue.push({
kind: 'outlined',
fn,
fnType: outlined.type,
});
/*
* Above should be as simple as the following, but needs testing:
* queue.push({
* kind: "outlined",
* fn,
* fnType: outlined.type,
* });
*/
}
}
compiledFns.push({
@@ -354,6 +354,54 @@ const EnvironmentConfigSchema = z.object({
*/
enableFunctionOutlining: z.boolean().default(true),
/**
* If enabled, this will outline nested JSX into a separate component.
*
* This will enable the compiler to memoize the separate component, giving us
* the same behavior as compiling _within_ the callback.
*
* ```
* function Component(countries, onDelete) {
* const name = useFoo();
* return countries.map(() => {
* return (
* <Foo>
* <Bar>{name}</Bar>
* <Button onclick={onDelete}>delete</Button>
* </Foo>
* );
* });
* }
* ```
*
* will be transpiled to:
*
* ```
* function Component(countries, onDelete) {
* const name = useFoo();
* return countries.map(() => {
* return (
* <Temp name={name} onDelete={onDelete} />
* );
* });
* }
*
* function Temp({name, onDelete}) {
* return (
* <Foo>
* <Bar>{name}</Bar>
* <Button onclick={onDelete}>delete</Button>
* </Foo>
* );
* }
*
* Both, `Component` and `Temp` will then be memoized by the compiler.
*
* With this change, when `countries` is updated by adding one single value,
* only the newly added value is re-rendered and not the entire list.
*/
enableJsxOutlining: z.boolean().default(false),
/*
* Enables instrumentation codegen. This emits a dev-mode only call to an
* instrumentation function, for components and hooks that Forget compiles.
@@ -920,15 +920,7 @@ export type InstructionValue =
type: Type;
loc: SourceLocation;
}
| {
kind: 'JsxExpression';
tag: Place | BuiltinTag;
props: Array<JsxAttribute>;
children: Array<Place> | null; // null === no children
loc: SourceLocation;
openingLoc: SourceLocation;
closingLoc: SourceLocation;
}
| JsxExpression
| {
kind: 'ObjectExpression';
properties: Array<ObjectProperty | SpreadPattern>;
@@ -1074,6 +1066,16 @@ export type InstructionValue =
loc: SourceLocation;
};
export type JsxExpression = {
kind: 'JsxExpression';
tag: Place | BuiltinTag;
props: Array<JsxAttribute>;
children: Array<Place> | null; // null === no children
loc: SourceLocation;
openingLoc: SourceLocation;
closingLoc: SourceLocation;
};
export type JsxAttribute =
| {kind: 'JsxSpreadAttribute'; argument: Place}
| {kind: 'JsxAttribute'; name: string; place: Place};
@@ -0,0 +1,466 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import invariant from 'invariant';
import {Environment} from '../HIR';
import {
BasicBlock,
GeneratedSource,
HIRFunction,
IdentifierId,
Instruction,
InstructionId,
InstructionKind,
JsxAttribute,
JsxExpression,
LoadGlobal,
makeBlockId,
makeIdentifierName,
makeInstructionId,
makeType,
ObjectProperty,
Place,
promoteTemporary,
promoteTemporaryJsxTag,
} from '../HIR/HIR';
import {createTemporaryPlace} from '../HIR/HIRBuilder';
import {printIdentifier} from '../HIR/PrintHIR';
import {deadCodeElimination} from './DeadCodeElimination';
import {assertExhaustive} from '../Utils/utils';
export function outlineJSX(fn: HIRFunction): void {
const outlinedFns: Array<HIRFunction> = [];
outlineJsxImpl(fn, outlinedFns);
for (const outlinedFn of outlinedFns) {
fn.env.outlineFunction(outlinedFn, 'Component');
}
}
type JsxInstruction = Instruction & {value: JsxExpression};
type LoadGlobalInstruction = Instruction & {value: LoadGlobal};
type LoadGlobalMap = Map<IdentifierId, LoadGlobalInstruction>;
type State = {
jsx: Array<JsxInstruction>;
children: Set<IdentifierId>;
};
function outlineJsxImpl(
fn: HIRFunction,
outlinedFns: Array<HIRFunction>,
): void {
const globals: LoadGlobalMap = new Map();
function processAndOutlineJSX(
state: State,
rewriteInstr: Map<InstructionId, Array<Instruction>>,
): void {
if (state.jsx.length <= 1) {
return;
}
const result = process(
fn,
[...state.jsx].sort((a, b) => a.id - b.id),
globals,
);
if (result) {
outlinedFns.push(result.fn);
rewriteInstr.set(state.jsx.at(0)!.id, result.instrs);
}
}
for (const [, block] of fn.body.blocks) {
const rewriteInstr = new Map();
let state: State = {
jsx: [],
children: new Set(),
};
for (let i = block.instructions.length - 1; i >= 0; i--) {
const instr = block.instructions[i];
const {value, lvalue} = instr;
switch (value.kind) {
case 'LoadGlobal': {
globals.set(lvalue.identifier.id, instr as LoadGlobalInstruction);
break;
}
case 'FunctionExpression': {
outlineJsxImpl(value.loweredFunc.func, outlinedFns);
break;
}
case 'JsxExpression': {
if (!state.children.has(lvalue.identifier.id)) {
processAndOutlineJSX(state, rewriteInstr);
state = {
jsx: [],
children: new Set(),
};
}
state.jsx.push(instr as JsxInstruction);
if (value.children) {
for (const child of value.children) {
state.children.add(child.identifier.id);
}
}
break;
}
case 'ArrayExpression':
case 'Await':
case 'BinaryExpression':
case 'CallExpression':
case 'ComputedDelete':
case 'ComputedLoad':
case 'ComputedStore':
case 'Debugger':
case 'DeclareContext':
case 'DeclareLocal':
case 'Destructure':
case 'FinishMemoize':
case 'GetIterator':
case 'IteratorNext':
case 'JSXText':
case 'JsxFragment':
case 'LoadContext':
case 'LoadLocal':
case 'MetaProperty':
case 'MethodCall':
case 'NewExpression':
case 'NextPropertyOf':
case 'ObjectExpression':
case 'ObjectMethod':
case 'PostfixUpdate':
case 'PrefixUpdate':
case 'Primitive':
case 'PropertyDelete':
case 'PropertyLoad':
case 'PropertyStore':
case 'RegExpLiteral':
case 'StartMemoize':
case 'StoreContext':
case 'StoreGlobal':
case 'StoreLocal':
case 'TaggedTemplateExpression':
case 'TemplateLiteral':
case 'TypeCastExpression':
case 'UnsupportedNode':
case 'UnaryExpression': {
break;
}
default: {
assertExhaustive(value, `Unexpected instruction: ${value}`);
}
}
}
processAndOutlineJSX(state, rewriteInstr);
if (rewriteInstr.size > 0) {
const newInstrs = [];
for (let i = 0; i < block.instructions.length; i++) {
// InstructionId's are one-indexed, so add one to account for them.
const id = i + 1;
if (rewriteInstr.has(id)) {
const instrs = rewriteInstr.get(id);
newInstrs.push(...instrs);
} else {
newInstrs.push(block.instructions[i]);
}
}
block.instructions = newInstrs;
}
deadCodeElimination(fn);
}
}
type OutlinedResult = {
instrs: Array<Instruction>;
fn: HIRFunction;
};
function process(
fn: HIRFunction,
jsx: Array<JsxInstruction>,
globals: LoadGlobalMap,
): OutlinedResult | null {
/**
* In the future, add a check for backedge to outline jsx inside loops in a
* top level component. For now, only outline jsx in callbacks.
*/
if (fn.fnType === 'Component') {
return null;
}
const props = collectProps(jsx);
if (!props) return null;
const outlinedTag = fn.env.generateGloballyUniqueIdentifierName(null).value;
const newInstrs = emitOutlinedJsx(fn.env, jsx, props, outlinedTag);
if (!newInstrs) return null;
const outlinedFn = emitOutlinedFn(fn.env, jsx, props, globals);
if (!outlinedFn) return null;
outlinedFn.id = outlinedTag;
return {instrs: newInstrs, fn: outlinedFn};
}
function collectProps(
instructions: Array<JsxInstruction>,
): Array<JsxAttribute> | null {
const attributes: Array<JsxAttribute> = [];
const jsxIds = new Set(instructions.map(i => i.lvalue.identifier.id));
const seen: Set<string> = new Set();
for (const instr of instructions) {
const {value} = instr;
for (const at of value.props) {
if (at.kind === 'JsxSpreadAttribute') {
return null;
}
/*
* TODO(gsn): Handle attributes that have same value across
* the outlined jsx instructions.
*/
if (seen.has(at.name)) {
return null;
}
if (at.kind === 'JsxAttribute') {
seen.add(at.name);
attributes.push(at);
}
}
// TODO(gsn): Add support for children that are not jsx expressions
if (
value.children &&
value.children.some(child => !jsxIds.has(child.identifier.id))
) {
return null;
}
}
return attributes;
}
function emitOutlinedJsx(
env: Environment,
instructions: Array<Instruction>,
props: Array<JsxAttribute>,
outlinedTag: string,
): Array<Instruction> {
const loadJsx: Instruction = {
id: makeInstructionId(0),
loc: GeneratedSource,
lvalue: createTemporaryPlace(env, GeneratedSource),
value: {
kind: 'LoadGlobal',
binding: {
kind: 'ModuleLocal',
name: outlinedTag,
},
loc: GeneratedSource,
},
};
promoteTemporaryJsxTag(loadJsx.lvalue.identifier);
const jsxExpr: Instruction = {
id: makeInstructionId(0),
loc: GeneratedSource,
lvalue: instructions.at(-1)!.lvalue,
value: {
kind: 'JsxExpression',
tag: {...loadJsx.lvalue},
props,
children: null,
loc: GeneratedSource,
openingLoc: GeneratedSource,
closingLoc: GeneratedSource,
},
};
return [loadJsx, jsxExpr];
}
function emitOutlinedFn(
env: Environment,
jsx: Array<JsxInstruction>,
oldProps: Array<JsxAttribute>,
globals: LoadGlobalMap,
): HIRFunction | null {
const instructions: Array<Instruction> = [];
const oldToNewProps = createOldToNewPropsMapping(env, oldProps);
const propsObj: Place = createTemporaryPlace(env, GeneratedSource);
promoteTemporary(propsObj.identifier);
const destructurePropsInstr = emitDestructureProps(env, propsObj, [
...oldToNewProps.values(),
]);
instructions.push(destructurePropsInstr);
const updatedJsxInstructions = emitUpdatedJsx(jsx, oldToNewProps);
const loadGlobalInstrs = emitLoadGlobals(jsx, globals);
if (!loadGlobalInstrs) {
return null;
}
instructions.push(...loadGlobalInstrs);
instructions.push(...updatedJsxInstructions);
const block: BasicBlock = {
kind: 'block',
id: makeBlockId(0),
instructions,
terminal: {
id: makeInstructionId(0),
kind: 'return',
loc: GeneratedSource,
value: instructions.at(-1)!.lvalue,
},
preds: new Set(),
phis: new Set(),
};
const fn: HIRFunction = {
loc: GeneratedSource,
id: null,
fnType: 'Other',
env,
params: [propsObj],
returnTypeAnnotation: null,
returnType: makeType(),
context: [],
effects: null,
body: {
entry: block.id,
blocks: new Map([[block.id, block]]),
},
generator: false,
async: false,
directives: [],
};
return fn;
}
function emitLoadGlobals(
jsx: Array<JsxInstruction>,
globals: LoadGlobalMap,
): Array<Instruction> | null {
const instructions: Array<Instruction> = [];
for (const {value} of jsx) {
// Add load globals instructions for jsx tags
if (value.tag.kind === 'Identifier') {
const loadGlobalInstr = globals.get(value.tag.identifier.id);
if (!loadGlobalInstr) {
return null;
}
instructions.push(loadGlobalInstr);
}
}
return instructions;
}
function emitUpdatedJsx(
jsx: Array<JsxInstruction>,
oldToNewProps: Map<IdentifierId, ObjectProperty>,
): Array<JsxInstruction> {
const newInstrs: Array<JsxInstruction> = [];
for (const instr of jsx) {
const {value} = instr;
const newProps: Array<JsxAttribute> = [];
// Update old props references to use the newly destructured props param
for (const prop of value.props) {
invariant(
prop.kind === 'JsxAttribute',
`Expected only attributes but found ${prop.kind}`,
);
if (prop.name === 'key') {
continue;
}
const newProp = oldToNewProps.get(prop.place.identifier.id);
invariant(
newProp !== undefined,
`Expected a new property for ${printIdentifier(prop.place.identifier)}`,
);
newProps.push({
...prop,
place: newProp.place,
});
}
newInstrs.push({
...instr,
value: {
...value,
props: newProps,
},
});
}
return newInstrs;
}
function createOldToNewPropsMapping(
env: Environment,
oldProps: Array<JsxAttribute>,
): Map<IdentifierId, ObjectProperty> {
const oldToNewProps = new Map();
for (const oldProp of oldProps) {
invariant(
oldProp.kind === 'JsxAttribute',
`Expected only attributes but found ${oldProp.kind}`,
);
// Do not read key prop in the outlined component
if (oldProp.name === 'key') {
continue;
}
const newProp: ObjectProperty = {
kind: 'ObjectProperty',
key: {
kind: 'string',
name: oldProp.name,
},
type: 'property',
place: createTemporaryPlace(env, GeneratedSource),
};
newProp.place.identifier.name = makeIdentifierName(oldProp.name);
oldToNewProps.set(oldProp.place.identifier.id, newProp);
}
return oldToNewProps;
}
function emitDestructureProps(
env: Environment,
propsObj: Place,
properties: Array<ObjectProperty>,
): Instruction {
const destructurePropsInstr: Instruction = {
id: makeInstructionId(0),
lvalue: createTemporaryPlace(env, GeneratedSource),
loc: GeneratedSource,
value: {
kind: 'Destructure',
lvalue: {
pattern: {
kind: 'ObjectPattern',
properties,
},
kind: InstructionKind.Let,
},
loc: GeneratedSource,
value: propsObj,
},
};
return destructurePropsInstr;
}
@@ -0,0 +1,143 @@
## Input
```javascript
// @enableJsxOutlining
function Component(arr) {
const x = useX();
return arr.map(i => {
<>
{arr.map((i, id) => {
let child = (
<Bar x={x}>
<Baz i={i}></Baz>
</Bar>
);
let jsx = <div>{child}</div>;
return jsx;
})}
</>;
});
}
function Bar({x, children}) {
return (
<>
{x}
{children}
</>
);
}
function Baz({i}) {
return <>{i}</>;
}
function useX() {
return 'x';
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{arr: ['foo', 'bar']}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableJsxOutlining
function Component(arr) {
const $ = _c(3);
const x = useX();
let t0;
if ($[0] !== arr || $[1] !== x) {
t0 = arr.map((i) => {
arr.map((i_0, id) => {
const T0 = _temp;
const child = <T0 i={i_0} x={x} />;
const jsx = <div>{child}</div>;
return jsx;
});
});
$[0] = arr;
$[1] = x;
$[2] = t0;
} else {
t0 = $[2];
}
return t0;
}
function _temp(t0) {
const $ = _c(5);
const { i: i, x: x } = t0;
let t1;
if ($[0] !== i) {
t1 = <Baz i={i} />;
$[0] = i;
$[1] = t1;
} else {
t1 = $[1];
}
let t2;
if ($[2] !== x || $[3] !== t1) {
t2 = <Bar x={x}>{t1}</Bar>;
$[2] = x;
$[3] = t1;
$[4] = t2;
} else {
t2 = $[4];
}
return t2;
}
function Bar(t0) {
const $ = _c(3);
const { x, children } = t0;
let t1;
if ($[0] !== x || $[1] !== children) {
t1 = (
<>
{x}
{children}
</>
);
$[0] = x;
$[1] = children;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
}
function Baz(t0) {
const $ = _c(2);
const { i } = t0;
let t1;
if ($[0] !== i) {
t1 = <>{i}</>;
$[0] = i;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
function useX() {
return "x";
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ arr: ["foo", "bar"] }],
};
```
### Eval output
(kind: exception) arr.map is not a function
@@ -0,0 +1,40 @@
// @enableJsxOutlining
function Component(arr) {
const x = useX();
return arr.map(i => {
<>
{arr.map((i, id) => {
let child = (
<Bar x={x}>
<Baz i={i}></Baz>
</Bar>
);
let jsx = <div>{child}</div>;
return jsx;
})}
</>;
});
}
function Bar({x, children}) {
return (
<>
{x}
{children}
</>
);
}
function Baz({i}) {
return <>{i}</>;
}
function useX() {
return 'x';
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{arr: ['foo', 'bar']}],
};
@@ -0,0 +1,145 @@
## Input
```javascript
// @enableJsxOutlining
function Component({arr}) {
const x = useX();
return (
<>
{arr.map((i, id) => {
let jsx = (
<Bar key={id} x={x}>
<Baz i={i}></Baz>
</Bar>
);
return jsx;
})}
</>
);
}
function Bar({x, children}) {
return (
<>
{x}
{children}
</>
);
}
function Baz({i}) {
return i;
}
function useX() {
return 'x';
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{arr: ['foo', 'bar']}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableJsxOutlining
function Component(t0) {
const $ = _c(7);
const { arr } = t0;
const x = useX();
let t1;
if ($[0] !== x || $[1] !== arr) {
let t2;
if ($[3] !== x) {
t2 = (i, id) => {
const T0 = _temp;
const jsx = <T0 i={i} key={id} x={x} />;
return jsx;
};
$[3] = x;
$[4] = t2;
} else {
t2 = $[4];
}
t1 = arr.map(t2);
$[0] = x;
$[1] = arr;
$[2] = t1;
} else {
t1 = $[2];
}
let t2;
if ($[5] !== t1) {
t2 = <>{t1}</>;
$[5] = t1;
$[6] = t2;
} else {
t2 = $[6];
}
return t2;
}
function _temp(t0) {
const $ = _c(5);
const { i: i, x: x } = t0;
let t1;
if ($[0] !== i) {
t1 = <Baz i={i} />;
$[0] = i;
$[1] = t1;
} else {
t1 = $[1];
}
let t2;
if ($[2] !== x || $[3] !== t1) {
t2 = <Bar x={x}>{t1}</Bar>;
$[2] = x;
$[3] = t1;
$[4] = t2;
} else {
t2 = $[4];
}
return t2;
}
function Bar(t0) {
const $ = _c(3);
const { x, children } = t0;
let t1;
if ($[0] !== x || $[1] !== children) {
t1 = (
<>
{x}
{children}
</>
);
$[0] = x;
$[1] = children;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
}
function Baz(t0) {
const { i } = t0;
return i;
}
function useX() {
return "x";
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ arr: ["foo", "bar"] }],
};
```
### Eval output
(kind: ok) xfooxbar
@@ -0,0 +1,38 @@
// @enableJsxOutlining
function Component({arr}) {
const x = useX();
return (
<>
{arr.map((i, id) => {
let jsx = (
<Bar key={id} x={x}>
<Baz i={i}></Baz>
</Bar>
);
return jsx;
})}
</>
);
}
function Bar({x, children}) {
return (
<>
{x}
{children}
</>
);
}
function Baz({i}) {
return i;
}
function useX() {
return 'x';
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{arr: ['foo', 'bar']}],
};
@@ -0,0 +1,186 @@
## Input
```javascript
// @enableJsxOutlining
function Component({arr}) {
const x = useX();
return (
<>
{arr.map((i, id) => {
return (
<Bar key={id} x={x}>
<Baz i={i}></Baz>
<Joe j={i}></Joe>
<Foo k={i}></Foo>
</Bar>
);
})}
</>
);
}
function Bar({x, children}) {
return (
<>
{x}
{children}
</>
);
}
function Baz({i}) {
return i;
}
function Joe({j}) {
return j;
}
function Foo({k}) {
return k;
}
function useX() {
return 'x';
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{arr: ['foo', 'bar']}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableJsxOutlining
function Component(t0) {
const $ = _c(7);
const { arr } = t0;
const x = useX();
let t1;
if ($[0] !== x || $[1] !== arr) {
let t2;
if ($[3] !== x) {
t2 = (i, id) => {
const T0 = _temp;
return <T0 i={i} j={i} k={i} key={id} x={x} />;
};
$[3] = x;
$[4] = t2;
} else {
t2 = $[4];
}
t1 = arr.map(t2);
$[0] = x;
$[1] = arr;
$[2] = t1;
} else {
t1 = $[2];
}
let t2;
if ($[5] !== t1) {
t2 = <>{t1}</>;
$[5] = t1;
$[6] = t2;
} else {
t2 = $[6];
}
return t2;
}
function _temp(t0) {
const $ = _c(11);
const { i: i, j: j, k: k, x: x } = t0;
let t1;
if ($[0] !== i) {
t1 = <Baz i={i} />;
$[0] = i;
$[1] = t1;
} else {
t1 = $[1];
}
let t2;
if ($[2] !== j) {
t2 = <Joe j={j} />;
$[2] = j;
$[3] = t2;
} else {
t2 = $[3];
}
let t3;
if ($[4] !== k) {
t3 = <Foo k={k} />;
$[4] = k;
$[5] = t3;
} else {
t3 = $[5];
}
let t4;
if ($[6] !== x || $[7] !== t1 || $[8] !== t2 || $[9] !== t3) {
t4 = (
<Bar x={x}>
{t1}
{t2}
{t3}
</Bar>
);
$[6] = x;
$[7] = t1;
$[8] = t2;
$[9] = t3;
$[10] = t4;
} else {
t4 = $[10];
}
return t4;
}
function Bar(t0) {
const $ = _c(3);
const { x, children } = t0;
let t1;
if ($[0] !== x || $[1] !== children) {
t1 = (
<>
{x}
{children}
</>
);
$[0] = x;
$[1] = children;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
}
function Baz(t0) {
const { i } = t0;
return i;
}
function Joe(t0) {
const { j } = t0;
return j;
}
function Foo(t0) {
const { k } = t0;
return k;
}
function useX() {
return "x";
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ arr: ["foo", "bar"] }],
};
```
### Eval output
(kind: ok) xfoofoofooxbarbarbar
@@ -0,0 +1,46 @@
// @enableJsxOutlining
function Component({arr}) {
const x = useX();
return (
<>
{arr.map((i, id) => {
return (
<Bar key={id} x={x}>
<Baz i={i}></Baz>
<Joe j={i}></Joe>
<Foo k={i}></Foo>
</Bar>
);
})}
</>
);
}
function Bar({x, children}) {
return (
<>
{x}
{children}
</>
);
}
function Baz({i}) {
return i;
}
function Joe({j}) {
return j;
}
function Foo({k}) {
return k;
}
function useX() {
return 'x';
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{arr: ['foo', 'bar']}],
};
@@ -0,0 +1,142 @@
## Input
```javascript
// @enableJsxOutlining
function Component({arr}) {
const x = useX();
return (
<>
{arr.map((i, id) => {
return (
<Bar key={id} x={x}>
<Baz i={i}></Baz>
</Bar>
);
})}
</>
);
}
function Bar({x, children}) {
return (
<>
{x}
{children}
</>
);
}
function Baz({i}) {
return i;
}
function useX() {
return 'x';
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{arr: ['foo', 'bar']}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableJsxOutlining
function Component(t0) {
const $ = _c(7);
const { arr } = t0;
const x = useX();
let t1;
if ($[0] !== x || $[1] !== arr) {
let t2;
if ($[3] !== x) {
t2 = (i, id) => {
const T0 = _temp;
return <T0 i={i} key={id} x={x} />;
};
$[3] = x;
$[4] = t2;
} else {
t2 = $[4];
}
t1 = arr.map(t2);
$[0] = x;
$[1] = arr;
$[2] = t1;
} else {
t1 = $[2];
}
let t2;
if ($[5] !== t1) {
t2 = <>{t1}</>;
$[5] = t1;
$[6] = t2;
} else {
t2 = $[6];
}
return t2;
}
function _temp(t0) {
const $ = _c(5);
const { i: i, x: x } = t0;
let t1;
if ($[0] !== i) {
t1 = <Baz i={i} />;
$[0] = i;
$[1] = t1;
} else {
t1 = $[1];
}
let t2;
if ($[2] !== x || $[3] !== t1) {
t2 = <Bar x={x}>{t1}</Bar>;
$[2] = x;
$[3] = t1;
$[4] = t2;
} else {
t2 = $[4];
}
return t2;
}
function Bar(t0) {
const $ = _c(3);
const { x, children } = t0;
let t1;
if ($[0] !== x || $[1] !== children) {
t1 = (
<>
{x}
{children}
</>
);
$[0] = x;
$[1] = children;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
}
function Baz(t0) {
const { i } = t0;
return i;
}
function useX() {
return "x";
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ arr: ["foo", "bar"] }],
};
```
### Eval output
(kind: ok) xfooxbar
@@ -0,0 +1,36 @@
// @enableJsxOutlining
function Component({arr}) {
const x = useX();
return (
<>
{arr.map((i, id) => {
return (
<Bar key={id} x={x}>
<Baz i={i}></Baz>
</Bar>
);
})}
</>
);
}
function Bar({x, children}) {
return (
<>
{x}
{children}
</>
);
}
function Baz({i}) {
return i;
}
function useX() {
return 'x';
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{arr: ['foo', 'bar']}],
};
@@ -0,0 +1,121 @@
## Input
```javascript
// @enableJsxOutlining
function Component({arr}) {
const x = useX();
return (
<>
{arr.map((i, id) => {
return (
<Bar key={id} x={x}>
<Baz i={i}>Test</Baz>
</Bar>
);
})}
</>
);
}
function Bar({x, children}) {
return (
<>
{x}
{children}
</>
);
}
function Baz({i}) {
return i;
}
function useX() {
return 'x';
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{arr: ['foo', 'bar']}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableJsxOutlining
function Component(t0) {
const $ = _c(7);
const { arr } = t0;
const x = useX();
let t1;
if ($[0] !== x || $[1] !== arr) {
let t2;
if ($[3] !== x) {
t2 = (i, id) => (
<Bar key={id} x={x}>
<Baz i={i}>Test</Baz>
</Bar>
);
$[3] = x;
$[4] = t2;
} else {
t2 = $[4];
}
t1 = arr.map(t2);
$[0] = x;
$[1] = arr;
$[2] = t1;
} else {
t1 = $[2];
}
let t2;
if ($[5] !== t1) {
t2 = <>{t1}</>;
$[5] = t1;
$[6] = t2;
} else {
t2 = $[6];
}
return t2;
}
function Bar(t0) {
const $ = _c(3);
const { x, children } = t0;
let t1;
if ($[0] !== x || $[1] !== children) {
t1 = (
<>
{x}
{children}
</>
);
$[0] = x;
$[1] = children;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
}
function Baz(t0) {
const { i } = t0;
return i;
}
function useX() {
return "x";
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ arr: ["foo", "bar"] }],
};
```
### Eval output
(kind: ok) xfooxbar
@@ -0,0 +1,36 @@
// @enableJsxOutlining
function Component({arr}) {
const x = useX();
return (
<>
{arr.map((i, id) => {
return (
<Bar key={id} x={x}>
<Baz i={i}>Test</Baz>
</Bar>
);
})}
</>
);
}
function Bar({x, children}) {
return (
<>
{x}
{children}
</>
);
}
function Baz({i}) {
return i;
}
function useX() {
return 'x';
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{arr: ['foo', 'bar']}],
};
@@ -0,0 +1,132 @@
## Input
```javascript
// @enableJsxOutlining
function Component({arr}) {
const x = useX();
return (
<>
{arr.map((i, id) => {
return (
<Bar key={id} x={x}>
<Baz i={i}></Baz>
<Foo i={i}></Foo>
</Bar>
);
})}
</>
);
}
function Bar({x, children}) {
return (
<>
{x}
{children}
</>
);
}
function Baz({i}) {
return i;
}
function Foo({k}) {
return k;
}
function useX() {
return 'x';
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{arr: ['foo', 'bar']}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableJsxOutlining
function Component(t0) {
const $ = _c(7);
const { arr } = t0;
const x = useX();
let t1;
if ($[0] !== x || $[1] !== arr) {
let t2;
if ($[3] !== x) {
t2 = (i, id) => (
<Bar key={id} x={x}>
<Baz i={i} />
<Foo i={i} />
</Bar>
);
$[3] = x;
$[4] = t2;
} else {
t2 = $[4];
}
t1 = arr.map(t2);
$[0] = x;
$[1] = arr;
$[2] = t1;
} else {
t1 = $[2];
}
let t2;
if ($[5] !== t1) {
t2 = <>{t1}</>;
$[5] = t1;
$[6] = t2;
} else {
t2 = $[6];
}
return t2;
}
function Bar(t0) {
const $ = _c(3);
const { x, children } = t0;
let t1;
if ($[0] !== x || $[1] !== children) {
t1 = (
<>
{x}
{children}
</>
);
$[0] = x;
$[1] = children;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
}
function Baz(t0) {
const { i } = t0;
return i;
}
function Foo(t0) {
const { k } = t0;
return k;
}
function useX() {
return "x";
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ arr: ["foo", "bar"] }],
};
```
### Eval output
(kind: ok) xfooxbar
@@ -0,0 +1,41 @@
// @enableJsxOutlining
function Component({arr}) {
const x = useX();
return (
<>
{arr.map((i, id) => {
return (
<Bar key={id} x={x}>
<Baz i={i}></Baz>
<Foo i={i}></Foo>
</Bar>
);
})}
</>
);
}
function Bar({x, children}) {
return (
<>
{x}
{children}
</>
);
}
function Baz({i}) {
return i;
}
function Foo({k}) {
return k;
}
function useX() {
return 'x';
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{arr: ['foo', 'bar']}],
};