@samitouri / QOS-React / commits / a88b9e5f68

[compiler] Outline JSX with non-jsx children (#31442)

Previously, we bailed out on outlining jsx that had children that were not part of the outlined jsx. Now, we add support for children by treating as attributes.

Sathya Gunasekaran committed Nov 6, 2024 at 17:54 UTC a88b9e5f6882a121417b2e8434d4c7ec30a60c52
3 files changed +137 -24
compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineJsx.ts
+46 -12
@@ -219,10 +219,20 @@ type OutlinedJsxAttribute = {
219 function collectProps(
220 instructions: Array<JsxInstruction>,
221 ): Array<OutlinedJsxAttribute> | null {
222 + let id = 1;
223 +
224 + function generateName(oldName: string): string {
225 + let newName = oldName;
226 + while (seen.has(newName)) {
227 + newName = `${oldName}${id++}`;
228 + }
229 + seen.add(newName);
230 + return newName;
231 + }
232 +
233 const attributes: Array<OutlinedJsxAttribute> = [];
234 const jsxIds = new Set(instructions.map(i => i.lvalue.identifier.id));
235 const seen: Set<string> = new Set();
225 - let id = 1;
236
237 for (const instr of instructions) {
238 const {value} = instr;
@@ -233,25 +243,29 @@ function collectProps(
243 }
244
245 if (at.kind === 'JsxAttribute') {
236 - let newName = at.name;
237 - while (seen.has(newName)) {
238 - newName = `${at.name}${id++}`;
239 - }
246 + const newName = generateName(at.name);
247 attributes.push({
248 originalName: at.name,
249 newName,
250 place: at.place,
251 });
245 - seen.add(newName);
252 }
253 }
254
249 - // TODO(gsn): Add support for children that are not jsx expressions
250 - if (
251 - value.children &&
252 - value.children.some(child => !jsxIds.has(child.identifier.id))
253 - ) {
254 - return null;
255 + if (value.children) {
256 + for (const child of value.children) {
257 + if (jsxIds.has(child.identifier.id)) {
258 + continue;
259 + }
260 +
261 + promoteTemporary(child.identifier);
262 + const newName = generateName('t');
263 + attributes.push({
264 + originalName: child.identifier.name!.value,
265 + newName: newName,
266 + place: child,
267 + });
268 + }
269 }
270 }
271 return attributes;
@@ -387,6 +401,7 @@ function emitUpdatedJsx(
401 oldToNewProps: Map<IdentifierId, OutlinedJsxAttribute>,
402 ): Array<JsxInstruction> {
403 const newInstrs: Array<JsxInstruction> = [];
404 + const jsxIds = new Set(jsx.map(i => i.lvalue.identifier.id));
405
406 for (const instr of jsx) {
407 const {value} = instr;
@@ -412,11 +427,30 @@ function emitUpdatedJsx(
427 });
428 }
429
430 + let newChildren: Array<Place> | null = null;
431 + if (value.children) {
432 + newChildren = [];
433 + for (const child of value.children) {
434 + if (jsxIds.has(child.identifier.id)) {
435 + newChildren.push({...child});
436 + continue;
437 + }
438 +
439 + const newChild = oldToNewProps.get(child.identifier.id);
440 + invariant(
441 + newChild !== undefined,
442 + `Expected a new prop for ${printIdentifier(child.identifier)}`,
443 + );
444 + newChildren.push({...newChild.place});
445 + }
446 + }
447 +
448 newInstrs.push({
449 ...instr,
450 value: {
451 ...value,
452 props: newProps,
453 + children: newChildren,
454 },
455 });
456 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-with-non-jsx-children.expect.md renamed
+78 -10
@@ -11,12 +11,14 @@ function Component({arr}) {
11 return (
12 <Bar key={id} x={x}>
13 <Baz i={i}>Test</Baz>
14 + <Foo k={i} />
15 </Bar>
16 );
17 })}
18 </>
19 );
20 }
21 +
22 function Bar({x, children}) {
23 return (
24 <>
@@ -26,8 +28,17 @@ function Bar({x, children}) {
28 );
29 }
30
29 -function Baz({i}) {
30 - return i;
31 +function Baz({i, children}) {
32 + return (
33 + <>
34 + {i}
35 + {children}
36 + </>
37 + );
38 +}
39 +
40 +function Foo({k}) {
41 + return k;
42 }
43
44 function useX() {
@@ -53,11 +64,11 @@ function Component(t0) {
64 if ($[0] !== arr || $[1] !== x) {
65 let t2;
66 if ($[3] !== x) {
56 - t2 = (i, id) => (
57 - <Bar key={id} x={x}>
58 - <Baz i={i}>Test</Baz>
59 - </Bar>
60 - );
67 + t2 = (i, id) => {
68 + const t3 = "Test";
69 + const T0 = _temp;
70 + return <T0 i={i} t={t3} k={i} key={id} x={x} />;
71 + };
72 $[3] = x;
73 $[4] = t2;
74 } else {
@@ -80,6 +91,43 @@ function Component(t0) {
91 }
92 return t2;
93 }
94 +function _temp(t0) {
95 + const $ = _c(9);
96 + const { i: i, t: t, k: k, x: x } = t0;
97 + let t1;
98 + if ($[0] !== i || $[1] !== t) {
99 + t1 = <Baz i={i}>{t}</Baz>;
100 + $[0] = i;
101 + $[1] = t;
102 + $[2] = t1;
103 + } else {
104 + t1 = $[2];
105 + }
106 + let t2;
107 + if ($[3] !== k) {
108 + t2 = <Foo k={k} />;
109 + $[3] = k;
110 + $[4] = t2;
111 + } else {
112 + t2 = $[4];
113 + }
114 + let t3;
115 + if ($[5] !== t1 || $[6] !== t2 || $[7] !== x) {
116 + t3 = (
117 + <Bar x={x}>
118 + {t1}
119 + {t2}
120 + </Bar>
121 + );
122 + $[5] = t1;
123 + $[6] = t2;
124 + $[7] = x;
125 + $[8] = t3;
126 + } else {
127 + t3 = $[8];
128 + }
129 + return t3;
130 +}
131
132 function Bar(t0) {
133 const $ = _c(3);
@@ -102,8 +150,28 @@ function Bar(t0) {
150 }
151
152 function Baz(t0) {
105 - const { i } = t0;
106 - return i;
153 + const $ = _c(3);
154 + const { i, children } = t0;
155 + let t1;
156 + if ($[0] !== children || $[1] !== i) {
157 + t1 = (
158 + <>
159 + {i}
160 + {children}
161 + </>
162 + );
163 + $[0] = children;
164 + $[1] = i;
165 + $[2] = t1;
166 + } else {
167 + t1 = $[2];
168 + }
169 + return t1;
170 +}
171 +
172 +function Foo(t0) {
173 + const { k } = t0;
174 + return k;
175 }
176
177 function useX() {
@@ -118,4 +186,4 @@ export const FIXTURE_ENTRYPOINT = {
186 ```
187
188 ### Eval output
121 -(kind: ok) xfooxbar
\ No newline at end of file
189 +(kind: ok) xfooTestfooxbarTestbar
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-with-non-jsx-children.js renamed
+13 -2
@@ -7,12 +7,14 @@ function Component({arr}) {
7 return (
8 <Bar key={id} x={x}>
9 <Baz i={i}>Test</Baz>
10 + <Foo k={i} />
11 </Bar>
12 );
13 })}
14 </>
15 );
16 }
17 +
18 function Bar({x, children}) {
19 return (
20 <>
@@ -22,8 +24,17 @@ function Bar({x, children}) {
24 );
25 }
26
25 -function Baz({i}) {
26 - return i;
27 +function Baz({i, children}) {
28 + return (
29 + <>
30 + {i}
31 + {children}
32 + </>
33 + );
34 +}
35 +
36 +function Foo({k}) {
37 + return k;
38 }
39
40 function useX() {