@samitouri / QOS-React-2 / commits / c91b3b090a

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.

Sathya Gunasekaran committed Oct 17, 2024 at 18:15 UTC c91b3b090ad406fcd103483de0abb6adf44b6f48
17 files changed +1641 -21
compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts
+5
@@ -103,6 +103,7 @@ import {lowerContextAccess} from '../Optimization/LowerContextAccess';
103 import {validateNoSetStateInPassiveEffects} from '../Validation/ValidateNoSetStateInPassiveEffects';
104 import {validateNoJSXInTryStatement} from '../Validation/ValidateNoJSXInTryStatement';
105 import {propagateScopeDependenciesHIR} from '../HIR/PropagateScopeDependenciesHIR';
106 +import {outlineJSX} from '../Optimization/OutlineJsx';
107
108 export type CompilerPipelineValue =
109 | {kind: 'ast'; name: string; value: CodegenFunction}
@@ -278,6 +279,10 @@ function* runWithEnvironment(
279 value: hir,
280 });
281
282 + if (env.config.enableJsxOutlining) {
283 + outlineJSX(hir);
284 + }
285 +
286 if (env.config.enableFunctionOutlining) {
287 outlineFunctions(hir, fbtOperands);
288 yield log({kind: 'hir', name: 'OutlineFunctions', value: hir});
compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts
+5 -12
@@ -199,7 +199,7 @@ function insertNewOutlinedFunctionNode(
199 program: NodePath<t.Program>,
200 originalFn: BabelFn,
201 compiledFn: CodegenFunction,
202 -): NodePath<t.Function> {
202 +): BabelFn {
203 switch (originalFn.type) {
204 case 'FunctionDeclaration': {
205 return originalFn.insertAfter(
@@ -491,18 +491,11 @@ export function compileProgram(
491 fn.skip();
492 ALREADY_COMPILED.add(fn.node);
493 if (outlined.type !== null) {
494 - CompilerError.throwTodo({
495 - reason: `Implement support for outlining React functions (components/hooks)`,
496 - loc: outlined.fn.loc,
494 + queue.push({
495 + kind: 'outlined',
496 + fn,
497 + fnType: outlined.type,
498 });
498 - /*
499 - * Above should be as simple as the following, but needs testing:
500 - * queue.push({
501 - * kind: "outlined",
502 - * fn,
503 - * fnType: outlined.type,
504 - * });
505 - */
499 }
500 }
501 compiledFns.push({
compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts
+48
@@ -354,6 +354,54 @@ const EnvironmentConfigSchema = z.object({
354 */
355 enableFunctionOutlining: z.boolean().default(true),
356
357 + /**
358 + * If enabled, this will outline nested JSX into a separate component.
359 + *
360 + * This will enable the compiler to memoize the separate component, giving us
361 + * the same behavior as compiling _within_ the callback.
362 + *
363 + * ```
364 + * function Component(countries, onDelete) {
365 + * const name = useFoo();
366 + * return countries.map(() => {
367 + * return (
368 + * <Foo>
369 + * <Bar>{name}</Bar>
370 + * <Button onclick={onDelete}>delete</Button>
371 + * </Foo>
372 + * );
373 + * });
374 + * }
375 + * ```
376 + *
377 + * will be transpiled to:
378 + *
379 + * ```
380 + * function Component(countries, onDelete) {
381 + * const name = useFoo();
382 + * return countries.map(() => {
383 + * return (
384 + * <Temp name={name} onDelete={onDelete} />
385 + * );
386 + * });
387 + * }
388 + *
389 + * function Temp({name, onDelete}) {
390 + * return (
391 + * <Foo>
392 + * <Bar>{name}</Bar>
393 + * <Button onclick={onDelete}>delete</Button>
394 + * </Foo>
395 + * );
396 + * }
397 + *
398 + * Both, `Component` and `Temp` will then be memoized by the compiler.
399 + *
400 + * With this change, when `countries` is updated by adding one single value,
401 + * only the newly added value is re-rendered and not the entire list.
402 + */
403 + enableJsxOutlining: z.boolean().default(false),
404 +
405 /*
406 * Enables instrumentation codegen. This emits a dev-mode only call to an
407 * instrumentation function, for components and hooks that Forget compiles.
compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts
+11 -9
@@ -920,15 +920,7 @@ export type InstructionValue =
920 type: Type;
921 loc: SourceLocation;
922 }
923 - | {
924 - kind: 'JsxExpression';
925 - tag: Place | BuiltinTag;
926 - props: Array<JsxAttribute>;
927 - children: Array<Place> | null; // null === no children
928 - loc: SourceLocation;
929 - openingLoc: SourceLocation;
930 - closingLoc: SourceLocation;
931 - }
923 + | JsxExpression
924 | {
925 kind: 'ObjectExpression';
926 properties: Array<ObjectProperty | SpreadPattern>;
@@ -1074,6 +1066,16 @@ export type InstructionValue =
1066 loc: SourceLocation;
1067 };
1068
1069 +export type JsxExpression = {
1070 + kind: 'JsxExpression';
1071 + tag: Place | BuiltinTag;
1072 + props: Array<JsxAttribute>;
1073 + children: Array<Place> | null; // null === no children
1074 + loc: SourceLocation;
1075 + openingLoc: SourceLocation;
1076 + closingLoc: SourceLocation;
1077 +};
1078 +
1079 export type JsxAttribute =
1080 | {kind: 'JsxSpreadAttribute'; argument: Place}
1081 | {kind: 'JsxAttribute'; name: string; place: Place};
compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineJsx.ts new
+466
@@ -0,0 +1,466 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +
8 +import invariant from 'invariant';
9 +import {Environment} from '../HIR';
10 +import {
11 + BasicBlock,
12 + GeneratedSource,
13 + HIRFunction,
14 + IdentifierId,
15 + Instruction,
16 + InstructionId,
17 + InstructionKind,
18 + JsxAttribute,
19 + JsxExpression,
20 + LoadGlobal,
21 + makeBlockId,
22 + makeIdentifierName,
23 + makeInstructionId,
24 + makeType,
25 + ObjectProperty,
26 + Place,
27 + promoteTemporary,
28 + promoteTemporaryJsxTag,
29 +} from '../HIR/HIR';
30 +import {createTemporaryPlace} from '../HIR/HIRBuilder';
31 +import {printIdentifier} from '../HIR/PrintHIR';
32 +import {deadCodeElimination} from './DeadCodeElimination';
33 +import {assertExhaustive} from '../Utils/utils';
34 +
35 +export function outlineJSX(fn: HIRFunction): void {
36 + const outlinedFns: Array<HIRFunction> = [];
37 + outlineJsxImpl(fn, outlinedFns);
38 +
39 + for (const outlinedFn of outlinedFns) {
40 + fn.env.outlineFunction(outlinedFn, 'Component');
41 + }
42 +}
43 +
44 +type JsxInstruction = Instruction & {value: JsxExpression};
45 +type LoadGlobalInstruction = Instruction & {value: LoadGlobal};
46 +type LoadGlobalMap = Map<IdentifierId, LoadGlobalInstruction>;
47 +
48 +type State = {
49 + jsx: Array<JsxInstruction>;
50 + children: Set<IdentifierId>;
51 +};
52 +
53 +function outlineJsxImpl(
54 + fn: HIRFunction,
55 + outlinedFns: Array<HIRFunction>,
56 +): void {
57 + const globals: LoadGlobalMap = new Map();
58 +
59 + function processAndOutlineJSX(
60 + state: State,
61 + rewriteInstr: Map<InstructionId, Array<Instruction>>,
62 + ): void {
63 + if (state.jsx.length <= 1) {
64 + return;
65 + }
66 + const result = process(
67 + fn,
68 + [...state.jsx].sort((a, b) => a.id - b.id),
69 + globals,
70 + );
71 + if (result) {
72 + outlinedFns.push(result.fn);
73 + rewriteInstr.set(state.jsx.at(0)!.id, result.instrs);
74 + }
75 + }
76 +
77 + for (const [, block] of fn.body.blocks) {
78 + const rewriteInstr = new Map();
79 + let state: State = {
80 + jsx: [],
81 + children: new Set(),
82 + };
83 +
84 + for (let i = block.instructions.length - 1; i >= 0; i--) {
85 + const instr = block.instructions[i];
86 + const {value, lvalue} = instr;
87 + switch (value.kind) {
88 + case 'LoadGlobal': {
89 + globals.set(lvalue.identifier.id, instr as LoadGlobalInstruction);
90 + break;
91 + }
92 + case 'FunctionExpression': {
93 + outlineJsxImpl(value.loweredFunc.func, outlinedFns);
94 + break;
95 + }
96 +
97 + case 'JsxExpression': {
98 + if (!state.children.has(lvalue.identifier.id)) {
99 + processAndOutlineJSX(state, rewriteInstr);
100 +
101 + state = {
102 + jsx: [],
103 + children: new Set(),
104 + };
105 + }
106 + state.jsx.push(instr as JsxInstruction);
107 + if (value.children) {
108 + for (const child of value.children) {
109 + state.children.add(child.identifier.id);
110 + }
111 + }
112 + break;
113 + }
114 + case 'ArrayExpression':
115 + case 'Await':
116 + case 'BinaryExpression':
117 + case 'CallExpression':
118 + case 'ComputedDelete':
119 + case 'ComputedLoad':
120 + case 'ComputedStore':
121 + case 'Debugger':
122 + case 'DeclareContext':
123 + case 'DeclareLocal':
124 + case 'Destructure':
125 + case 'FinishMemoize':
126 + case 'GetIterator':
127 + case 'IteratorNext':
128 + case 'JSXText':
129 + case 'JsxFragment':
130 + case 'LoadContext':
131 + case 'LoadLocal':
132 + case 'MetaProperty':
133 + case 'MethodCall':
134 + case 'NewExpression':
135 + case 'NextPropertyOf':
136 + case 'ObjectExpression':
137 + case 'ObjectMethod':
138 + case 'PostfixUpdate':
139 + case 'PrefixUpdate':
140 + case 'Primitive':
141 + case 'PropertyDelete':
142 + case 'PropertyLoad':
143 + case 'PropertyStore':
144 + case 'RegExpLiteral':
145 + case 'StartMemoize':
146 + case 'StoreContext':
147 + case 'StoreGlobal':
148 + case 'StoreLocal':
149 + case 'TaggedTemplateExpression':
150 + case 'TemplateLiteral':
151 + case 'TypeCastExpression':
152 + case 'UnsupportedNode':
153 + case 'UnaryExpression': {
154 + break;
155 + }
156 + default: {
157 + assertExhaustive(value, `Unexpected instruction: ${value}`);
158 + }
159 + }
160 + }
161 + processAndOutlineJSX(state, rewriteInstr);
162 +
163 + if (rewriteInstr.size > 0) {
164 + const newInstrs = [];
165 + for (let i = 0; i < block.instructions.length; i++) {
166 + // InstructionId's are one-indexed, so add one to account for them.
167 + const id = i + 1;
168 + if (rewriteInstr.has(id)) {
169 + const instrs = rewriteInstr.get(id);
170 + newInstrs.push(...instrs);
171 + } else {
172 + newInstrs.push(block.instructions[i]);
173 + }
174 + }
175 + block.instructions = newInstrs;
176 + }
177 + deadCodeElimination(fn);
178 + }
179 +}
180 +
181 +type OutlinedResult = {
182 + instrs: Array<Instruction>;
183 + fn: HIRFunction;
184 +};
185 +
186 +function process(
187 + fn: HIRFunction,
188 + jsx: Array<JsxInstruction>,
189 + globals: LoadGlobalMap,
190 +): OutlinedResult | null {
191 + /**
192 + * In the future, add a check for backedge to outline jsx inside loops in a
193 + * top level component. For now, only outline jsx in callbacks.
194 + */
195 + if (fn.fnType === 'Component') {
196 + return null;
197 + }
198 +
199 + const props = collectProps(jsx);
200 + if (!props) return null;
201 +
202 + const outlinedTag = fn.env.generateGloballyUniqueIdentifierName(null).value;
203 + const newInstrs = emitOutlinedJsx(fn.env, jsx, props, outlinedTag);
204 + if (!newInstrs) return null;
205 +
206 + const outlinedFn = emitOutlinedFn(fn.env, jsx, props, globals);
207 + if (!outlinedFn) return null;
208 + outlinedFn.id = outlinedTag;
209 +
210 + return {instrs: newInstrs, fn: outlinedFn};
211 +}
212 +
213 +function collectProps(
214 + instructions: Array<JsxInstruction>,
215 +): Array<JsxAttribute> | null {
216 + const attributes: Array<JsxAttribute> = [];
217 + const jsxIds = new Set(instructions.map(i => i.lvalue.identifier.id));
218 + const seen: Set<string> = new Set();
219 + for (const instr of instructions) {
220 + const {value} = instr;
221 +
222 + for (const at of value.props) {
223 + if (at.kind === 'JsxSpreadAttribute') {
224 + return null;
225 + }
226 +
227 + /*
228 + * TODO(gsn): Handle attributes that have same value across
229 + * the outlined jsx instructions.
230 + */
231 + if (seen.has(at.name)) {
232 + return null;
233 + }
234 +
235 + if (at.kind === 'JsxAttribute') {
236 + seen.add(at.name);
237 + attributes.push(at);
238 + }
239 + }
240 +
241 + // TODO(gsn): Add support for children that are not jsx expressions
242 + if (
243 + value.children &&
244 + value.children.some(child => !jsxIds.has(child.identifier.id))
245 + ) {
246 + return null;
247 + }
248 + }
249 + return attributes;
250 +}
251 +
252 +function emitOutlinedJsx(
253 + env: Environment,
254 + instructions: Array<Instruction>,
255 + props: Array<JsxAttribute>,
256 + outlinedTag: string,
257 +): Array<Instruction> {
258 + const loadJsx: Instruction = {
259 + id: makeInstructionId(0),
260 + loc: GeneratedSource,
261 + lvalue: createTemporaryPlace(env, GeneratedSource),
262 + value: {
263 + kind: 'LoadGlobal',
264 + binding: {
265 + kind: 'ModuleLocal',
266 + name: outlinedTag,
267 + },
268 + loc: GeneratedSource,
269 + },
270 + };
271 + promoteTemporaryJsxTag(loadJsx.lvalue.identifier);
272 + const jsxExpr: Instruction = {
273 + id: makeInstructionId(0),
274 + loc: GeneratedSource,
275 + lvalue: instructions.at(-1)!.lvalue,
276 + value: {
277 + kind: 'JsxExpression',
278 + tag: {...loadJsx.lvalue},
279 + props,
280 + children: null,
281 + loc: GeneratedSource,
282 + openingLoc: GeneratedSource,
283 + closingLoc: GeneratedSource,
284 + },
285 + };
286 +
287 + return [loadJsx, jsxExpr];
288 +}
289 +
290 +function emitOutlinedFn(
291 + env: Environment,
292 + jsx: Array<JsxInstruction>,
293 + oldProps: Array<JsxAttribute>,
294 + globals: LoadGlobalMap,
295 +): HIRFunction | null {
296 + const instructions: Array<Instruction> = [];
297 + const oldToNewProps = createOldToNewPropsMapping(env, oldProps);
298 +
299 + const propsObj: Place = createTemporaryPlace(env, GeneratedSource);
300 + promoteTemporary(propsObj.identifier);
301 +
302 + const destructurePropsInstr = emitDestructureProps(env, propsObj, [
303 + ...oldToNewProps.values(),
304 + ]);
305 + instructions.push(destructurePropsInstr);
306 +
307 + const updatedJsxInstructions = emitUpdatedJsx(jsx, oldToNewProps);
308 + const loadGlobalInstrs = emitLoadGlobals(jsx, globals);
309 + if (!loadGlobalInstrs) {
310 + return null;
311 + }
312 + instructions.push(...loadGlobalInstrs);
313 + instructions.push(...updatedJsxInstructions);
314 +
315 + const block: BasicBlock = {
316 + kind: 'block',
317 + id: makeBlockId(0),
318 + instructions,
319 + terminal: {
320 + id: makeInstructionId(0),
321 + kind: 'return',
322 + loc: GeneratedSource,
323 + value: instructions.at(-1)!.lvalue,
324 + },
325 + preds: new Set(),
326 + phis: new Set(),
327 + };
328 +
329 + const fn: HIRFunction = {
330 + loc: GeneratedSource,
331 + id: null,
332 + fnType: 'Other',
333 + env,
334 + params: [propsObj],
335 + returnTypeAnnotation: null,
336 + returnType: makeType(),
337 + context: [],
338 + effects: null,
339 + body: {
340 + entry: block.id,
341 + blocks: new Map([[block.id, block]]),
342 + },
343 + generator: false,
344 + async: false,
345 + directives: [],
346 + };
347 + return fn;
348 +}
349 +
350 +function emitLoadGlobals(
351 + jsx: Array<JsxInstruction>,
352 + globals: LoadGlobalMap,
353 +): Array<Instruction> | null {
354 + const instructions: Array<Instruction> = [];
355 + for (const {value} of jsx) {
356 + // Add load globals instructions for jsx tags
357 + if (value.tag.kind === 'Identifier') {
358 + const loadGlobalInstr = globals.get(value.tag.identifier.id);
359 + if (!loadGlobalInstr) {
360 + return null;
361 + }
362 + instructions.push(loadGlobalInstr);
363 + }
364 + }
365 +
366 + return instructions;
367 +}
368 +
369 +function emitUpdatedJsx(
370 + jsx: Array<JsxInstruction>,
371 + oldToNewProps: Map<IdentifierId, ObjectProperty>,
372 +): Array<JsxInstruction> {
373 + const newInstrs: Array<JsxInstruction> = [];
374 +
375 + for (const instr of jsx) {
376 + const {value} = instr;
377 + const newProps: Array<JsxAttribute> = [];
378 + // Update old props references to use the newly destructured props param
379 + for (const prop of value.props) {
380 + invariant(
381 + prop.kind === 'JsxAttribute',
382 + `Expected only attributes but found ${prop.kind}`,
383 + );
384 + if (prop.name === 'key') {
385 + continue;
386 + }
387 + const newProp = oldToNewProps.get(prop.place.identifier.id);
388 + invariant(
389 + newProp !== undefined,
390 + `Expected a new property for ${printIdentifier(prop.place.identifier)}`,
391 + );
392 + newProps.push({
393 + ...prop,
394 + place: newProp.place,
395 + });
396 + }
397 +
398 + newInstrs.push({
399 + ...instr,
400 + value: {
401 + ...value,
402 + props: newProps,
403 + },
404 + });
405 + }
406 +
407 + return newInstrs;
408 +}
409 +
410 +function createOldToNewPropsMapping(
411 + env: Environment,
412 + oldProps: Array<JsxAttribute>,
413 +): Map<IdentifierId, ObjectProperty> {
414 + const oldToNewProps = new Map();
415 +
416 + for (const oldProp of oldProps) {
417 + invariant(
418 + oldProp.kind === 'JsxAttribute',
419 + `Expected only attributes but found ${oldProp.kind}`,
420 + );
421 +
422 + // Do not read key prop in the outlined component
423 + if (oldProp.name === 'key') {
424 + continue;
425 + }
426 +
427 + const newProp: ObjectProperty = {
428 + kind: 'ObjectProperty',
429 + key: {
430 + kind: 'string',
431 + name: oldProp.name,
432 + },
433 + type: 'property',
434 + place: createTemporaryPlace(env, GeneratedSource),
435 + };
436 + newProp.place.identifier.name = makeIdentifierName(oldProp.name);
437 + oldToNewProps.set(oldProp.place.identifier.id, newProp);
438 + }
439 +
440 + return oldToNewProps;
441 +}
442 +
443 +function emitDestructureProps(
444 + env: Environment,
445 + propsObj: Place,
446 + properties: Array<ObjectProperty>,
447 +): Instruction {
448 + const destructurePropsInstr: Instruction = {
449 + id: makeInstructionId(0),
450 + lvalue: createTemporaryPlace(env, GeneratedSource),
451 + loc: GeneratedSource,
452 + value: {
453 + kind: 'Destructure',
454 + lvalue: {
455 + pattern: {
456 + kind: 'ObjectPattern',
457 + properties,
458 + },
459 + kind: InstructionKind.Let,
460 + },
461 + loc: GeneratedSource,
462 + value: propsObj,
463 + },
464 + };
465 + return destructurePropsInstr;
466 +}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md new
+143
@@ -0,0 +1,143 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enableJsxOutlining
6 +function Component(arr) {
7 + const x = useX();
8 + return arr.map(i => {
9 + <>
10 + {arr.map((i, id) => {
11 + let child = (
12 + <Bar x={x}>
13 + <Baz i={i}></Baz>
14 + </Bar>
15 + );
16 +
17 + let jsx = <div>{child}</div>;
18 + return jsx;
19 + })}
20 + </>;
21 + });
22 +}
23 +
24 +function Bar({x, children}) {
25 + return (
26 + <>
27 + {x}
28 + {children}
29 + </>
30 + );
31 +}
32 +
33 +function Baz({i}) {
34 + return <>{i}</>;
35 +}
36 +
37 +function useX() {
38 + return 'x';
39 +}
40 +
41 +export const FIXTURE_ENTRYPOINT = {
42 + fn: Component,
43 + params: [{arr: ['foo', 'bar']}],
44 +};
45 +
46 +```
47 +
48 +## Code
49 +
50 +```javascript
51 +import { c as _c } from "react/compiler-runtime"; // @enableJsxOutlining
52 +function Component(arr) {
53 + const $ = _c(3);
54 + const x = useX();
55 + let t0;
56 + if ($[0] !== arr || $[1] !== x) {
57 + t0 = arr.map((i) => {
58 + arr.map((i_0, id) => {
59 + const T0 = _temp;
60 + const child = <T0 i={i_0} x={x} />;
61 +
62 + const jsx = <div>{child}</div>;
63 + return jsx;
64 + });
65 + });
66 + $[0] = arr;
67 + $[1] = x;
68 + $[2] = t0;
69 + } else {
70 + t0 = $[2];
71 + }
72 + return t0;
73 +}
74 +function _temp(t0) {
75 + const $ = _c(5);
76 + const { i: i, x: x } = t0;
77 + let t1;
78 + if ($[0] !== i) {
79 + t1 = <Baz i={i} />;
80 + $[0] = i;
81 + $[1] = t1;
82 + } else {
83 + t1 = $[1];
84 + }
85 + let t2;
86 + if ($[2] !== x || $[3] !== t1) {
87 + t2 = <Bar x={x}>{t1}</Bar>;
88 + $[2] = x;
89 + $[3] = t1;
90 + $[4] = t2;
91 + } else {
92 + t2 = $[4];
93 + }
94 + return t2;
95 +}
96 +
97 +function Bar(t0) {
98 + const $ = _c(3);
99 + const { x, children } = t0;
100 + let t1;
101 + if ($[0] !== x || $[1] !== children) {
102 + t1 = (
103 + <>
104 + {x}
105 + {children}
106 + </>
107 + );
108 + $[0] = x;
109 + $[1] = children;
110 + $[2] = t1;
111 + } else {
112 + t1 = $[2];
113 + }
114 + return t1;
115 +}
116 +
117 +function Baz(t0) {
118 + const $ = _c(2);
119 + const { i } = t0;
120 + let t1;
121 + if ($[0] !== i) {
122 + t1 = <>{i}</>;
123 + $[0] = i;
124 + $[1] = t1;
125 + } else {
126 + t1 = $[1];
127 + }
128 + return t1;
129 +}
130 +
131 +function useX() {
132 + return "x";
133 +}
134 +
135 +export const FIXTURE_ENTRYPOINT = {
136 + fn: Component,
137 + params: [{ arr: ["foo", "bar"] }],
138 +};
139 +
140 +```
141 +
142 +### Eval output
143 +(kind: exception) arr.map is not a function
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.js new
+40
@@ -0,0 +1,40 @@
1 +// @enableJsxOutlining
2 +function Component(arr) {
3 + const x = useX();
4 + return arr.map(i => {
5 + <>
6 + {arr.map((i, id) => {
7 + let child = (
8 + <Bar x={x}>
9 + <Baz i={i}></Baz>
10 + </Bar>
11 + );
12 +
13 + let jsx = <div>{child}</div>;
14 + return jsx;
15 + })}
16 + </>;
17 + });
18 +}
19 +
20 +function Bar({x, children}) {
21 + return (
22 + <>
23 + {x}
24 + {children}
25 + </>
26 + );
27 +}
28 +
29 +function Baz({i}) {
30 + return <>{i}</>;
31 +}
32 +
33 +function useX() {
34 + return 'x';
35 +}
36 +
37 +export const FIXTURE_ENTRYPOINT = {
38 + fn: Component,
39 + params: [{arr: ['foo', 'bar']}],
40 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-jsx-stored-in-id.expect.md new
+145
@@ -0,0 +1,145 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enableJsxOutlining
6 +function Component({arr}) {
7 + const x = useX();
8 + return (
9 + <>
10 + {arr.map((i, id) => {
11 + let jsx = (
12 + <Bar key={id} x={x}>
13 + <Baz i={i}></Baz>
14 + </Bar>
15 + );
16 + return jsx;
17 + })}
18 + </>
19 + );
20 +}
21 +
22 +function Bar({x, children}) {
23 + return (
24 + <>
25 + {x}
26 + {children}
27 + </>
28 + );
29 +}
30 +
31 +function Baz({i}) {
32 + return i;
33 +}
34 +
35 +function useX() {
36 + return 'x';
37 +}
38 +
39 +export const FIXTURE_ENTRYPOINT = {
40 + fn: Component,
41 + params: [{arr: ['foo', 'bar']}],
42 +};
43 +
44 +```
45 +
46 +## Code
47 +
48 +```javascript
49 +import { c as _c } from "react/compiler-runtime"; // @enableJsxOutlining
50 +function Component(t0) {
51 + const $ = _c(7);
52 + const { arr } = t0;
53 + const x = useX();
54 + let t1;
55 + if ($[0] !== x || $[1] !== arr) {
56 + let t2;
57 + if ($[3] !== x) {
58 + t2 = (i, id) => {
59 + const T0 = _temp;
60 + const jsx = <T0 i={i} key={id} x={x} />;
61 + return jsx;
62 + };
63 + $[3] = x;
64 + $[4] = t2;
65 + } else {
66 + t2 = $[4];
67 + }
68 + t1 = arr.map(t2);
69 + $[0] = x;
70 + $[1] = arr;
71 + $[2] = t1;
72 + } else {
73 + t1 = $[2];
74 + }
75 + let t2;
76 + if ($[5] !== t1) {
77 + t2 = <>{t1}</>;
78 + $[5] = t1;
79 + $[6] = t2;
80 + } else {
81 + t2 = $[6];
82 + }
83 + return t2;
84 +}
85 +function _temp(t0) {
86 + const $ = _c(5);
87 + const { i: i, x: x } = t0;
88 + let t1;
89 + if ($[0] !== i) {
90 + t1 = <Baz i={i} />;
91 + $[0] = i;
92 + $[1] = t1;
93 + } else {
94 + t1 = $[1];
95 + }
96 + let t2;
97 + if ($[2] !== x || $[3] !== t1) {
98 + t2 = <Bar x={x}>{t1}</Bar>;
99 + $[2] = x;
100 + $[3] = t1;
101 + $[4] = t2;
102 + } else {
103 + t2 = $[4];
104 + }
105 + return t2;
106 +}
107 +
108 +function Bar(t0) {
109 + const $ = _c(3);
110 + const { x, children } = t0;
111 + let t1;
112 + if ($[0] !== x || $[1] !== children) {
113 + t1 = (
114 + <>
115 + {x}
116 + {children}
117 + </>
118 + );
119 + $[0] = x;
120 + $[1] = children;
121 + $[2] = t1;
122 + } else {
123 + t1 = $[2];
124 + }
125 + return t1;
126 +}
127 +
128 +function Baz(t0) {
129 + const { i } = t0;
130 + return i;
131 +}
132 +
133 +function useX() {
134 + return "x";
135 +}
136 +
137 +export const FIXTURE_ENTRYPOINT = {
138 + fn: Component,
139 + params: [{ arr: ["foo", "bar"] }],
140 +};
141 +
142 +```
143 +
144 +### Eval output
145 +(kind: ok) xfooxbar
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-jsx-stored-in-id.js new
+38
@@ -0,0 +1,38 @@
1 +// @enableJsxOutlining
2 +function Component({arr}) {
3 + const x = useX();
4 + return (
5 + <>
6 + {arr.map((i, id) => {
7 + let jsx = (
8 + <Bar key={id} x={x}>
9 + <Baz i={i}></Baz>
10 + </Bar>
11 + );
12 + return jsx;
13 + })}
14 + </>
15 + );
16 +}
17 +
18 +function Bar({x, children}) {
19 + return (
20 + <>
21 + {x}
22 + {children}
23 + </>
24 + );
25 +}
26 +
27 +function Baz({i}) {
28 + return i;
29 +}
30 +
31 +function useX() {
32 + return 'x';
33 +}
34 +
35 +export const FIXTURE_ENTRYPOINT = {
36 + fn: Component,
37 + params: [{arr: ['foo', 'bar']}],
38 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-separate-nested.expect.md new
+186
@@ -0,0 +1,186 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enableJsxOutlining
6 +function Component({arr}) {
7 + const x = useX();
8 + return (
9 + <>
10 + {arr.map((i, id) => {
11 + return (
12 + <Bar key={id} x={x}>
13 + <Baz i={i}></Baz>
14 + <Joe j={i}></Joe>
15 + <Foo k={i}></Foo>
16 + </Bar>
17 + );
18 + })}
19 + </>
20 + );
21 +}
22 +function Bar({x, children}) {
23 + return (
24 + <>
25 + {x}
26 + {children}
27 + </>
28 + );
29 +}
30 +
31 +function Baz({i}) {
32 + return i;
33 +}
34 +
35 +function Joe({j}) {
36 + return j;
37 +}
38 +
39 +function Foo({k}) {
40 + return k;
41 +}
42 +
43 +function useX() {
44 + return 'x';
45 +}
46 +
47 +export const FIXTURE_ENTRYPOINT = {
48 + fn: Component,
49 + params: [{arr: ['foo', 'bar']}],
50 +};
51 +
52 +```
53 +
54 +## Code
55 +
56 +```javascript
57 +import { c as _c } from "react/compiler-runtime"; // @enableJsxOutlining
58 +function Component(t0) {
59 + const $ = _c(7);
60 + const { arr } = t0;
61 + const x = useX();
62 + let t1;
63 + if ($[0] !== x || $[1] !== arr) {
64 + let t2;
65 + if ($[3] !== x) {
66 + t2 = (i, id) => {
67 + const T0 = _temp;
68 + return <T0 i={i} j={i} k={i} key={id} x={x} />;
69 + };
70 + $[3] = x;
71 + $[4] = t2;
72 + } else {
73 + t2 = $[4];
74 + }
75 + t1 = arr.map(t2);
76 + $[0] = x;
77 + $[1] = arr;
78 + $[2] = t1;
79 + } else {
80 + t1 = $[2];
81 + }
82 + let t2;
83 + if ($[5] !== t1) {
84 + t2 = <>{t1}</>;
85 + $[5] = t1;
86 + $[6] = t2;
87 + } else {
88 + t2 = $[6];
89 + }
90 + return t2;
91 +}
92 +function _temp(t0) {
93 + const $ = _c(11);
94 + const { i: i, j: j, k: k, x: x } = t0;
95 + let t1;
96 + if ($[0] !== i) {
97 + t1 = <Baz i={i} />;
98 + $[0] = i;
99 + $[1] = t1;
100 + } else {
101 + t1 = $[1];
102 + }
103 + let t2;
104 + if ($[2] !== j) {
105 + t2 = <Joe j={j} />;
106 + $[2] = j;
107 + $[3] = t2;
108 + } else {
109 + t2 = $[3];
110 + }
111 + let t3;
112 + if ($[4] !== k) {
113 + t3 = <Foo k={k} />;
114 + $[4] = k;
115 + $[5] = t3;
116 + } else {
117 + t3 = $[5];
118 + }
119 + let t4;
120 + if ($[6] !== x || $[7] !== t1 || $[8] !== t2 || $[9] !== t3) {
121 + t4 = (
122 + <Bar x={x}>
123 + {t1}
124 + {t2}
125 + {t3}
126 + </Bar>
127 + );
128 + $[6] = x;
129 + $[7] = t1;
130 + $[8] = t2;
131 + $[9] = t3;
132 + $[10] = t4;
133 + } else {
134 + t4 = $[10];
135 + }
136 + return t4;
137 +}
138 +
139 +function Bar(t0) {
140 + const $ = _c(3);
141 + const { x, children } = t0;
142 + let t1;
143 + if ($[0] !== x || $[1] !== children) {
144 + t1 = (
145 + <>
146 + {x}
147 + {children}
148 + </>
149 + );
150 + $[0] = x;
151 + $[1] = children;
152 + $[2] = t1;
153 + } else {
154 + t1 = $[2];
155 + }
156 + return t1;
157 +}
158 +
159 +function Baz(t0) {
160 + const { i } = t0;
161 + return i;
162 +}
163 +
164 +function Joe(t0) {
165 + const { j } = t0;
166 + return j;
167 +}
168 +
169 +function Foo(t0) {
170 + const { k } = t0;
171 + return k;
172 +}
173 +
174 +function useX() {
175 + return "x";
176 +}
177 +
178 +export const FIXTURE_ENTRYPOINT = {
179 + fn: Component,
180 + params: [{ arr: ["foo", "bar"] }],
181 +};
182 +
183 +```
184 +
185 +### Eval output
186 +(kind: ok) xfoofoofooxbarbarbar
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-separate-nested.js new
+46
@@ -0,0 +1,46 @@
1 +// @enableJsxOutlining
2 +function Component({arr}) {
3 + const x = useX();
4 + return (
5 + <>
6 + {arr.map((i, id) => {
7 + return (
8 + <Bar key={id} x={x}>
9 + <Baz i={i}></Baz>
10 + <Joe j={i}></Joe>
11 + <Foo k={i}></Foo>
12 + </Bar>
13 + );
14 + })}
15 + </>
16 + );
17 +}
18 +function Bar({x, children}) {
19 + return (
20 + <>
21 + {x}
22 + {children}
23 + </>
24 + );
25 +}
26 +
27 +function Baz({i}) {
28 + return i;
29 +}
30 +
31 +function Joe({j}) {
32 + return j;
33 +}
34 +
35 +function Foo({k}) {
36 + return k;
37 +}
38 +
39 +function useX() {
40 + return 'x';
41 +}
42 +
43 +export const FIXTURE_ENTRYPOINT = {
44 + fn: Component,
45 + params: [{arr: ['foo', 'bar']}],
46 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-simple.expect.md new
+142
@@ -0,0 +1,142 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enableJsxOutlining
6 +function Component({arr}) {
7 + const x = useX();
8 + return (
9 + <>
10 + {arr.map((i, id) => {
11 + return (
12 + <Bar key={id} x={x}>
13 + <Baz i={i}></Baz>
14 + </Bar>
15 + );
16 + })}
17 + </>
18 + );
19 +}
20 +function Bar({x, children}) {
21 + return (
22 + <>
23 + {x}
24 + {children}
25 + </>
26 + );
27 +}
28 +
29 +function Baz({i}) {
30 + return i;
31 +}
32 +
33 +function useX() {
34 + return 'x';
35 +}
36 +
37 +export const FIXTURE_ENTRYPOINT = {
38 + fn: Component,
39 + params: [{arr: ['foo', 'bar']}],
40 +};
41 +
42 +```
43 +
44 +## Code
45 +
46 +```javascript
47 +import { c as _c } from "react/compiler-runtime"; // @enableJsxOutlining
48 +function Component(t0) {
49 + const $ = _c(7);
50 + const { arr } = t0;
51 + const x = useX();
52 + let t1;
53 + if ($[0] !== x || $[1] !== arr) {
54 + let t2;
55 + if ($[3] !== x) {
56 + t2 = (i, id) => {
57 + const T0 = _temp;
58 + return <T0 i={i} key={id} x={x} />;
59 + };
60 + $[3] = x;
61 + $[4] = t2;
62 + } else {
63 + t2 = $[4];
64 + }
65 + t1 = arr.map(t2);
66 + $[0] = x;
67 + $[1] = arr;
68 + $[2] = t1;
69 + } else {
70 + t1 = $[2];
71 + }
72 + let t2;
73 + if ($[5] !== t1) {
74 + t2 = <>{t1}</>;
75 + $[5] = t1;
76 + $[6] = t2;
77 + } else {
78 + t2 = $[6];
79 + }
80 + return t2;
81 +}
82 +function _temp(t0) {
83 + const $ = _c(5);
84 + const { i: i, x: x } = t0;
85 + let t1;
86 + if ($[0] !== i) {
87 + t1 = <Baz i={i} />;
88 + $[0] = i;
89 + $[1] = t1;
90 + } else {
91 + t1 = $[1];
92 + }
93 + let t2;
94 + if ($[2] !== x || $[3] !== t1) {
95 + t2 = <Bar x={x}>{t1}</Bar>;
96 + $[2] = x;
97 + $[3] = t1;
98 + $[4] = t2;
99 + } else {
100 + t2 = $[4];
101 + }
102 + return t2;
103 +}
104 +
105 +function Bar(t0) {
106 + const $ = _c(3);
107 + const { x, children } = t0;
108 + let t1;
109 + if ($[0] !== x || $[1] !== children) {
110 + t1 = (
111 + <>
112 + {x}
113 + {children}
114 + </>
115 + );
116 + $[0] = x;
117 + $[1] = children;
118 + $[2] = t1;
119 + } else {
120 + t1 = $[2];
121 + }
122 + return t1;
123 +}
124 +
125 +function Baz(t0) {
126 + const { i } = t0;
127 + return i;
128 +}
129 +
130 +function useX() {
131 + return "x";
132 +}
133 +
134 +export const FIXTURE_ENTRYPOINT = {
135 + fn: Component,
136 + params: [{ arr: ["foo", "bar"] }],
137 +};
138 +
139 +```
140 +
141 +### Eval output
142 +(kind: ok) xfooxbar
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-simple.js new
+36
@@ -0,0 +1,36 @@
1 +// @enableJsxOutlining
2 +function Component({arr}) {
3 + const x = useX();
4 + return (
5 + <>
6 + {arr.map((i, id) => {
7 + return (
8 + <Bar key={id} x={x}>
9 + <Baz i={i}></Baz>
10 + </Bar>
11 + );
12 + })}
13 + </>
14 + );
15 +}
16 +function Bar({x, children}) {
17 + return (
18 + <>
19 + {x}
20 + {children}
21 + </>
22 + );
23 +}
24 +
25 +function Baz({i}) {
26 + return i;
27 +}
28 +
29 +function useX() {
30 + return 'x';
31 +}
32 +
33 +export const FIXTURE_ENTRYPOINT = {
34 + fn: Component,
35 + params: [{arr: ['foo', 'bar']}],
36 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-children.expect.md new
+121
@@ -0,0 +1,121 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enableJsxOutlining
6 +function Component({arr}) {
7 + const x = useX();
8 + return (
9 + <>
10 + {arr.map((i, id) => {
11 + return (
12 + <Bar key={id} x={x}>
13 + <Baz i={i}>Test</Baz>
14 + </Bar>
15 + );
16 + })}
17 + </>
18 + );
19 +}
20 +function Bar({x, children}) {
21 + return (
22 + <>
23 + {x}
24 + {children}
25 + </>
26 + );
27 +}
28 +
29 +function Baz({i}) {
30 + return i;
31 +}
32 +
33 +function useX() {
34 + return 'x';
35 +}
36 +
37 +export const FIXTURE_ENTRYPOINT = {
38 + fn: Component,
39 + params: [{arr: ['foo', 'bar']}],
40 +};
41 +
42 +```
43 +
44 +## Code
45 +
46 +```javascript
47 +import { c as _c } from "react/compiler-runtime"; // @enableJsxOutlining
48 +function Component(t0) {
49 + const $ = _c(7);
50 + const { arr } = t0;
51 + const x = useX();
52 + let t1;
53 + if ($[0] !== x || $[1] !== arr) {
54 + let t2;
55 + if ($[3] !== x) {
56 + t2 = (i, id) => (
57 + <Bar key={id} x={x}>
58 + <Baz i={i}>Test</Baz>
59 + </Bar>
60 + );
61 + $[3] = x;
62 + $[4] = t2;
63 + } else {
64 + t2 = $[4];
65 + }
66 + t1 = arr.map(t2);
67 + $[0] = x;
68 + $[1] = arr;
69 + $[2] = t1;
70 + } else {
71 + t1 = $[2];
72 + }
73 + let t2;
74 + if ($[5] !== t1) {
75 + t2 = <>{t1}</>;
76 + $[5] = t1;
77 + $[6] = t2;
78 + } else {
79 + t2 = $[6];
80 + }
81 + return t2;
82 +}
83 +
84 +function Bar(t0) {
85 + const $ = _c(3);
86 + const { x, children } = t0;
87 + let t1;
88 + if ($[0] !== x || $[1] !== children) {
89 + t1 = (
90 + <>
91 + {x}
92 + {children}
93 + </>
94 + );
95 + $[0] = x;
96 + $[1] = children;
97 + $[2] = t1;
98 + } else {
99 + t1 = $[2];
100 + }
101 + return t1;
102 +}
103 +
104 +function Baz(t0) {
105 + const { i } = t0;
106 + return i;
107 +}
108 +
109 +function useX() {
110 + return "x";
111 +}
112 +
113 +export const FIXTURE_ENTRYPOINT = {
114 + fn: Component,
115 + params: [{ arr: ["foo", "bar"] }],
116 +};
117 +
118 +```
119 +
120 +### Eval output
121 +(kind: ok) xfooxbar
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-children.js new
+36
@@ -0,0 +1,36 @@
1 +// @enableJsxOutlining
2 +function Component({arr}) {
3 + const x = useX();
4 + return (
5 + <>
6 + {arr.map((i, id) => {
7 + return (
8 + <Bar key={id} x={x}>
9 + <Baz i={i}>Test</Baz>
10 + </Bar>
11 + );
12 + })}
13 + </>
14 + );
15 +}
16 +function Bar({x, children}) {
17 + return (
18 + <>
19 + {x}
20 + {children}
21 + </>
22 + );
23 +}
24 +
25 +function Baz({i}) {
26 + return i;
27 +}
28 +
29 +function useX() {
30 + return 'x';
31 +}
32 +
33 +export const FIXTURE_ENTRYPOINT = {
34 + fn: Component,
35 + params: [{arr: ['foo', 'bar']}],
36 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-duplicate-prop.expect.md new
+132
@@ -0,0 +1,132 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enableJsxOutlining
6 +function Component({arr}) {
7 + const x = useX();
8 + return (
9 + <>
10 + {arr.map((i, id) => {
11 + return (
12 + <Bar key={id} x={x}>
13 + <Baz i={i}></Baz>
14 + <Foo i={i}></Foo>
15 + </Bar>
16 + );
17 + })}
18 + </>
19 + );
20 +}
21 +function Bar({x, children}) {
22 + return (
23 + <>
24 + {x}
25 + {children}
26 + </>
27 + );
28 +}
29 +
30 +function Baz({i}) {
31 + return i;
32 +}
33 +
34 +function Foo({k}) {
35 + return k;
36 +}
37 +
38 +function useX() {
39 + return 'x';
40 +}
41 +
42 +export const FIXTURE_ENTRYPOINT = {
43 + fn: Component,
44 + params: [{arr: ['foo', 'bar']}],
45 +};
46 +
47 +```
48 +
49 +## Code
50 +
51 +```javascript
52 +import { c as _c } from "react/compiler-runtime"; // @enableJsxOutlining
53 +function Component(t0) {
54 + const $ = _c(7);
55 + const { arr } = t0;
56 + const x = useX();
57 + let t1;
58 + if ($[0] !== x || $[1] !== arr) {
59 + let t2;
60 + if ($[3] !== x) {
61 + t2 = (i, id) => (
62 + <Bar key={id} x={x}>
63 + <Baz i={i} />
64 + <Foo i={i} />
65 + </Bar>
66 + );
67 + $[3] = x;
68 + $[4] = t2;
69 + } else {
70 + t2 = $[4];
71 + }
72 + t1 = arr.map(t2);
73 + $[0] = x;
74 + $[1] = arr;
75 + $[2] = t1;
76 + } else {
77 + t1 = $[2];
78 + }
79 + let t2;
80 + if ($[5] !== t1) {
81 + t2 = <>{t1}</>;
82 + $[5] = t1;
83 + $[6] = t2;
84 + } else {
85 + t2 = $[6];
86 + }
87 + return t2;
88 +}
89 +
90 +function Bar(t0) {
91 + const $ = _c(3);
92 + const { x, children } = t0;
93 + let t1;
94 + if ($[0] !== x || $[1] !== children) {
95 + t1 = (
96 + <>
97 + {x}
98 + {children}
99 + </>
100 + );
101 + $[0] = x;
102 + $[1] = children;
103 + $[2] = t1;
104 + } else {
105 + t1 = $[2];
106 + }
107 + return t1;
108 +}
109 +
110 +function Baz(t0) {
111 + const { i } = t0;
112 + return i;
113 +}
114 +
115 +function Foo(t0) {
116 + const { k } = t0;
117 + return k;
118 +}
119 +
120 +function useX() {
121 + return "x";
122 +}
123 +
124 +export const FIXTURE_ENTRYPOINT = {
125 + fn: Component,
126 + params: [{ arr: ["foo", "bar"] }],
127 +};
128 +
129 +```
130 +
131 +### Eval output
132 +(kind: ok) xfooxbar
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.jsx-outlining-duplicate-prop.js new
+41
@@ -0,0 +1,41 @@
1 +// @enableJsxOutlining
2 +function Component({arr}) {
3 + const x = useX();
4 + return (
5 + <>
6 + {arr.map((i, id) => {
7 + return (
8 + <Bar key={id} x={x}>
9 + <Baz i={i}></Baz>
10 + <Foo i={i}></Foo>
11 + </Bar>
12 + );
13 + })}
14 + </>
15 + );
16 +}
17 +function Bar({x, children}) {
18 + return (
19 + <>
20 + {x}
21 + {children}
22 + </>
23 + );
24 +}
25 +
26 +function Baz({i}) {
27 + return i;
28 +}
29 +
30 +function Foo({k}) {
31 + return k;
32 +}
33 +
34 +function useX() {
35 + return 'x';
36 +}
37 +
38 +export const FIXTURE_ENTRYPOINT = {
39 + fn: Component,
40 + params: [{arr: ['foo', 'bar']}],
41 +};