[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() {