[compiler] Add JSX inlining optimization (#30867)
This adds an `InlineJsxTransform` optimization pass, toggled by the `enableInlineJsxTransform` flag. When enabled, JSX will be transformed into React Element object literals, preventing runtime overhead during element creation. TODO: - [ ] Add conditionals to make transform PROD-only - [ ] Make the React element symbol configurable so this works with runtimes that support `react.element` or `react.transitional.element` - [ ] Look into additional optimization to pass props spread through directly if none of the properties are mutated
Jack Pope committed
Sep 18, 2024 at 11:51 UTC
5dcb009760160c085496e943f76090d98528f971
8 files changed
+711
-14
compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts
+10
@@ -41,6 +41,7 @@ import {
41
constantPropagation,
42
deadCodeElimination,
43
pruneMaybeThrows,
44
+ inlineJsxTransform,
45
} from '../Optimization';
46
import {instructionReordering} from '../Optimization/InstructionReordering';
47
import {
@@ -351,6 +352,15 @@ function* runWithEnvironment(
352
});
353
}
354
355
+ if (env.config.enableInlineJsxTransform) {
356
+ inlineJsxTransform(hir);
357
+ yield log({
358
+ kind: 'hir',
359
+ name: 'inlineJsxTransform',
360
+ value: hir,
361
+ });
362
+ }
363
+
364
const reactiveFunction = buildReactiveFunction(hir);
365
yield log({
366
kind: 'reactive',
compiler/packages/babel-plugin-react-compiler/src/HIR/BuildReactiveScopeTerminalsHIR.ts
+2
-14
@@ -14,6 +14,7 @@ import {
14
ScopeId,
15
} from './HIR';
16
import {
17
+ fixScopeAndIdentifierRanges,
18
markInstructionIds,
19
markPredecessors,
20
reversePostorderBlocks,
@@ -176,20 +177,7 @@ export function buildReactiveScopeTerminalsHIR(fn: HIRFunction): void {
177
* Step 5:
178
* Fix scope and identifier ranges to account for renumbered instructions
179
*/
179
- for (const [, block] of fn.body.blocks) {
180
- const terminal = block.terminal;
181
- if (terminal.kind === 'scope' || terminal.kind === 'pruned-scope') {
182
- /*
183
- * Scope ranges should always align to start at the 'scope' terminal
184
- * and end at the first instruction of the fallthrough block
185
- */
186
- const fallthroughBlock = fn.body.blocks.get(terminal.fallthrough)!;
187
- const firstId =
188
- fallthroughBlock.instructions[0]?.id ?? fallthroughBlock.terminal.id;
189
- terminal.scope.range.start = terminal.id;
190
- terminal.scope.range.end = firstId;
191
- }
192
- }
180
+ fixScopeAndIdentifierRanges(fn.body);
181
}
182
183
type TerminalRewriteInfo =
compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts
+7
@@ -233,6 +233,13 @@ const EnvironmentConfigSchema = z.object({
233
*/
234
enableOptionalDependencies: z.boolean().default(true),
235
236
+ /**
237
+ * Enables inlining ReactElement object literals in place of JSX
238
+ * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime
239
+ * Currently a prod-only optimization, requiring Fast JSX dependencies
240
+ */
241
+ enableInlineJsxTransform: z.boolean().default(false),
242
+
243
/*
244
* Enable validation of hooks to partially check that the component honors the rules of hooks.
245
* When disabled, the component is assumed to follow the rules (though the Babel plugin looks
compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts
+20
@@ -912,3 +912,23 @@ export function clonePlaceToTemporary(env: Environment, place: Place): Place {
912
temp.reactive = place.reactive;
913
return temp;
914
}
915
+
916
+/**
917
+ * Fix scope and identifier ranges to account for renumbered instructions
918
+ */
919
+export function fixScopeAndIdentifierRanges(func: HIR): void {
920
+ for (const [, block] of func.blocks) {
921
+ const terminal = block.terminal;
922
+ if (terminal.kind === 'scope' || terminal.kind === 'pruned-scope') {
923
+ /*
924
+ * Scope ranges should always align to start at the 'scope' terminal
925
+ * and end at the first instruction of the fallthrough block
926
+ */
927
+ const fallthroughBlock = func.blocks.get(terminal.fallthrough)!;
928
+ const firstId =
929
+ fallthroughBlock.instructions[0]?.id ?? fallthroughBlock.terminal.id;
930
+ terminal.scope.range.start = terminal.id;
931
+ terminal.scope.range.end = firstId;
932
+ }
933
+ }
934
+}
compiler/packages/babel-plugin-react-compiler/src/Optimization/InlineJsxTransform.ts
new
+402
@@ -0,0 +1,402 @@
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 {
9
+ BuiltinTag,
10
+ Effect,
11
+ HIRFunction,
12
+ Instruction,
13
+ JsxAttribute,
14
+ makeInstructionId,
15
+ ObjectProperty,
16
+ Place,
17
+ SpreadPattern,
18
+} from '../HIR';
19
+import {
20
+ createTemporaryPlace,
21
+ fixScopeAndIdentifierRanges,
22
+ markInstructionIds,
23
+ markPredecessors,
24
+ reversePostorderBlocks,
25
+} from '../HIR/HIRBuilder';
26
+
27
+function createSymbolProperty(
28
+ fn: HIRFunction,
29
+ instr: Instruction,
30
+ nextInstructions: Array<Instruction>,
31
+ propertyName: string,
32
+ symbolName: string,
33
+): ObjectProperty {
34
+ const symbolPlace = createTemporaryPlace(fn.env, instr.value.loc);
35
+ const symbolInstruction: Instruction = {
36
+ id: makeInstructionId(0),
37
+ lvalue: {...symbolPlace, effect: Effect.Mutate},
38
+ value: {
39
+ kind: 'LoadGlobal',
40
+ binding: {kind: 'Global', name: 'Symbol'},
41
+ loc: instr.value.loc,
42
+ },
43
+ loc: instr.loc,
44
+ };
45
+ nextInstructions.push(symbolInstruction);
46
+
47
+ const symbolForPlace = createTemporaryPlace(fn.env, instr.value.loc);
48
+ const symbolForInstruction: Instruction = {
49
+ id: makeInstructionId(0),
50
+ lvalue: {...symbolForPlace, effect: Effect.Read},
51
+ value: {
52
+ kind: 'PropertyLoad',
53
+ object: {...symbolInstruction.lvalue},
54
+ property: 'for',
55
+ loc: instr.value.loc,
56
+ },
57
+ loc: instr.loc,
58
+ };
59
+ nextInstructions.push(symbolForInstruction);
60
+
61
+ const symbolValuePlace = createTemporaryPlace(fn.env, instr.value.loc);
62
+ const symbolValueInstruction: Instruction = {
63
+ id: makeInstructionId(0),
64
+ lvalue: {...symbolValuePlace, effect: Effect.Mutate},
65
+ value: {
66
+ kind: 'Primitive',
67
+ value: symbolName,
68
+ loc: instr.value.loc,
69
+ },
70
+ loc: instr.loc,
71
+ };
72
+ nextInstructions.push(symbolValueInstruction);
73
+
74
+ const $$typeofPlace = createTemporaryPlace(fn.env, instr.value.loc);
75
+ const $$typeofInstruction: Instruction = {
76
+ id: makeInstructionId(0),
77
+ lvalue: {...$$typeofPlace, effect: Effect.Mutate},
78
+ value: {
79
+ kind: 'MethodCall',
80
+ receiver: symbolInstruction.lvalue,
81
+ property: symbolForInstruction.lvalue,
82
+ args: [symbolValueInstruction.lvalue],
83
+ loc: instr.value.loc,
84
+ },
85
+ loc: instr.loc,
86
+ };
87
+ const $$typeofProperty: ObjectProperty = {
88
+ kind: 'ObjectProperty',
89
+ key: {name: propertyName, kind: 'string'},
90
+ type: 'property',
91
+ place: {...$$typeofPlace, effect: Effect.Capture},
92
+ };
93
+ nextInstructions.push($$typeofInstruction);
94
+ return $$typeofProperty;
95
+}
96
+
97
+function createTagProperty(
98
+ fn: HIRFunction,
99
+ instr: Instruction,
100
+ nextInstructions: Array<Instruction>,
101
+ componentTag: BuiltinTag | Place,
102
+): ObjectProperty {
103
+ let tagProperty: ObjectProperty;
104
+ switch (componentTag.kind) {
105
+ case 'BuiltinTag': {
106
+ const tagPropertyPlace = createTemporaryPlace(fn.env, instr.value.loc);
107
+ const tagInstruction: Instruction = {
108
+ id: makeInstructionId(0),
109
+ lvalue: {...tagPropertyPlace, effect: Effect.Mutate},
110
+ value: {
111
+ kind: 'Primitive',
112
+ value: componentTag.name,
113
+ loc: instr.value.loc,
114
+ },
115
+ loc: instr.loc,
116
+ };
117
+ tagProperty = {
118
+ kind: 'ObjectProperty',
119
+ key: {name: 'type', kind: 'string'},
120
+ type: 'property',
121
+ place: {...tagPropertyPlace, effect: Effect.Capture},
122
+ };
123
+ nextInstructions.push(tagInstruction);
124
+ break;
125
+ }
126
+ case 'Identifier': {
127
+ tagProperty = {
128
+ kind: 'ObjectProperty',
129
+ key: {name: 'type', kind: 'string'},
130
+ type: 'property',
131
+ place: {...componentTag, effect: Effect.Capture},
132
+ };
133
+ break;
134
+ }
135
+ }
136
+
137
+ return tagProperty;
138
+}
139
+
140
+function createPropsProperties(
141
+ fn: HIRFunction,
142
+ instr: Instruction,
143
+ nextInstructions: Array<Instruction>,
144
+ propAttributes: Array<JsxAttribute>,
145
+ children: Array<Place> | null,
146
+): {
147
+ refProperty: ObjectProperty;
148
+ keyProperty: ObjectProperty;
149
+ propsProperty: ObjectProperty;
150
+} {
151
+ let refProperty: ObjectProperty | undefined;
152
+ let keyProperty: ObjectProperty | undefined;
153
+ const props: Array<ObjectProperty | SpreadPattern> = [];
154
+ propAttributes.forEach(prop => {
155
+ switch (prop.kind) {
156
+ case 'JsxAttribute': {
157
+ if (prop.name === 'ref') {
158
+ refProperty = {
159
+ kind: 'ObjectProperty',
160
+ key: {name: 'ref', kind: 'string'},
161
+ type: 'property',
162
+ place: {...prop.place},
163
+ };
164
+ } else if (prop.name === 'key') {
165
+ keyProperty = {
166
+ kind: 'ObjectProperty',
167
+ key: {name: 'key', kind: 'string'},
168
+ type: 'property',
169
+ place: {...prop.place},
170
+ };
171
+ } else {
172
+ const attributeProperty: ObjectProperty = {
173
+ kind: 'ObjectProperty',
174
+ key: {name: prop.name, kind: 'string'},
175
+ type: 'property',
176
+ place: {...prop.place},
177
+ };
178
+ props.push(attributeProperty);
179
+ }
180
+ break;
181
+ }
182
+ case 'JsxSpreadAttribute': {
183
+ // TODO: Optimize spreads to pass object directly if none of its properties are mutated
184
+ props.push({
185
+ kind: 'Spread',
186
+ place: {...prop.argument},
187
+ });
188
+ break;
189
+ }
190
+ }
191
+ });
192
+ const propsPropertyPlace = createTemporaryPlace(fn.env, instr.value.loc);
193
+ if (children) {
194
+ let childrenPropProperty: ObjectProperty;
195
+ if (children.length === 1) {
196
+ childrenPropProperty = {
197
+ kind: 'ObjectProperty',
198
+ key: {name: 'children', kind: 'string'},
199
+ type: 'property',
200
+ place: {...children[0], effect: Effect.Capture},
201
+ };
202
+ } else {
203
+ const childrenPropPropertyPlace = createTemporaryPlace(
204
+ fn.env,
205
+ instr.value.loc,
206
+ );
207
+
208
+ const childrenPropInstruction: Instruction = {
209
+ id: makeInstructionId(0),
210
+ lvalue: {...childrenPropPropertyPlace, effect: Effect.Mutate},
211
+ value: {
212
+ kind: 'ArrayExpression',
213
+ elements: [...children],
214
+ loc: instr.value.loc,
215
+ },
216
+ loc: instr.loc,
217
+ };
218
+ nextInstructions.push(childrenPropInstruction);
219
+ childrenPropProperty = {
220
+ kind: 'ObjectProperty',
221
+ key: {name: 'children', kind: 'string'},
222
+ type: 'property',
223
+ place: {...childrenPropPropertyPlace, effect: Effect.Capture},
224
+ };
225
+ }
226
+ props.push(childrenPropProperty);
227
+ }
228
+
229
+ if (refProperty == null) {
230
+ const refPropertyPlace = createTemporaryPlace(fn.env, instr.value.loc);
231
+ const refInstruction: Instruction = {
232
+ id: makeInstructionId(0),
233
+ lvalue: {...refPropertyPlace, effect: Effect.Mutate},
234
+ value: {
235
+ kind: 'Primitive',
236
+ value: null,
237
+ loc: instr.value.loc,
238
+ },
239
+ loc: instr.loc,
240
+ };
241
+ refProperty = {
242
+ kind: 'ObjectProperty',
243
+ key: {name: 'ref', kind: 'string'},
244
+ type: 'property',
245
+ place: {...refPropertyPlace, effect: Effect.Capture},
246
+ };
247
+ nextInstructions.push(refInstruction);
248
+ }
249
+
250
+ if (keyProperty == null) {
251
+ const keyPropertyPlace = createTemporaryPlace(fn.env, instr.value.loc);
252
+ const keyInstruction: Instruction = {
253
+ id: makeInstructionId(0),
254
+ lvalue: {...keyPropertyPlace, effect: Effect.Mutate},
255
+ value: {
256
+ kind: 'Primitive',
257
+ value: null,
258
+ loc: instr.value.loc,
259
+ },
260
+ loc: instr.loc,
261
+ };
262
+ keyProperty = {
263
+ kind: 'ObjectProperty',
264
+ key: {name: 'key', kind: 'string'},
265
+ type: 'property',
266
+ place: {...keyPropertyPlace, effect: Effect.Capture},
267
+ };
268
+ nextInstructions.push(keyInstruction);
269
+ }
270
+
271
+ const propsInstruction: Instruction = {
272
+ id: makeInstructionId(0),
273
+ lvalue: {...propsPropertyPlace, effect: Effect.Mutate},
274
+ value: {
275
+ kind: 'ObjectExpression',
276
+ properties: props,
277
+ loc: instr.value.loc,
278
+ },
279
+ loc: instr.loc,
280
+ };
281
+ const propsProperty: ObjectProperty = {
282
+ kind: 'ObjectProperty',
283
+ key: {name: 'props', kind: 'string'},
284
+ type: 'property',
285
+ place: {...propsPropertyPlace, effect: Effect.Capture},
286
+ };
287
+ nextInstructions.push(propsInstruction);
288
+ return {refProperty, keyProperty, propsProperty};
289
+}
290
+
291
+// TODO: Make PROD only with conditional statements
292
+export function inlineJsxTransform(fn: HIRFunction): void {
293
+ for (const [, block] of fn.body.blocks) {
294
+ let nextInstructions: Array<Instruction> | null = null;
295
+ for (let i = 0; i < block.instructions.length; i++) {
296
+ const instr = block.instructions[i]!;
297
+ switch (instr.value.kind) {
298
+ case 'JsxExpression': {
299
+ nextInstructions ??= block.instructions.slice(0, i);
300
+
301
+ const {refProperty, keyProperty, propsProperty} =
302
+ createPropsProperties(
303
+ fn,
304
+ instr,
305
+ nextInstructions,
306
+ instr.value.props,
307
+ instr.value.children,
308
+ );
309
+ const reactElementInstruction: Instruction = {
310
+ id: makeInstructionId(0),
311
+ lvalue: {...instr.lvalue, effect: Effect.Store},
312
+ value: {
313
+ kind: 'ObjectExpression',
314
+ properties: [
315
+ createSymbolProperty(
316
+ fn,
317
+ instr,
318
+ nextInstructions,
319
+ '$$typeof',
320
+ /**
321
+ * TODO: Add this to config so we can switch between
322
+ * react.element / react.transitional.element
323
+ */
324
+ 'react.transitional.element',
325
+ ),
326
+ createTagProperty(fn, instr, nextInstructions, instr.value.tag),
327
+ refProperty,
328
+ keyProperty,
329
+ propsProperty,
330
+ ],
331
+ loc: instr.value.loc,
332
+ },
333
+ loc: instr.loc,
334
+ };
335
+ nextInstructions.push(reactElementInstruction);
336
+
337
+ break;
338
+ }
339
+ case 'JsxFragment': {
340
+ nextInstructions ??= block.instructions.slice(0, i);
341
+ const {refProperty, keyProperty, propsProperty} =
342
+ createPropsProperties(
343
+ fn,
344
+ instr,
345
+ nextInstructions,
346
+ [],
347
+ instr.value.children,
348
+ );
349
+ const reactElementInstruction: Instruction = {
350
+ id: makeInstructionId(0),
351
+ lvalue: {...instr.lvalue, effect: Effect.Store},
352
+ value: {
353
+ kind: 'ObjectExpression',
354
+ properties: [
355
+ createSymbolProperty(
356
+ fn,
357
+ instr,
358
+ nextInstructions,
359
+ '$$typeof',
360
+ /**
361
+ * TODO: Add this to config so we can switch between
362
+ * react.element / react.transitional.element
363
+ */
364
+ 'react.transitional.element',
365
+ ),
366
+ createSymbolProperty(
367
+ fn,
368
+ instr,
369
+ nextInstructions,
370
+ 'type',
371
+ 'react.fragment',
372
+ ),
373
+ refProperty,
374
+ keyProperty,
375
+ propsProperty,
376
+ ],
377
+ loc: instr.value.loc,
378
+ },
379
+ loc: instr.loc,
380
+ };
381
+ nextInstructions.push(reactElementInstruction);
382
+ break;
383
+ }
384
+ default: {
385
+ if (nextInstructions !== null) {
386
+ nextInstructions.push(instr);
387
+ }
388
+ }
389
+ }
390
+ }
391
+ if (nextInstructions !== null) {
392
+ block.instructions = nextInstructions;
393
+ }
394
+ }
395
+
396
+ // Fixup the HIR to restore RPO, ensure correct predecessors, and renumber instructions.
397
+ reversePostorderBlocks(fn.body);
398
+ markPredecessors(fn.body);
399
+ markInstructionIds(fn.body);
400
+ // The renumbering instructions invalidates scope and identifier ranges
401
+ fixScopeAndIdentifierRanges(fn.body);
402
+}
compiler/packages/babel-plugin-react-compiler/src/Optimization/index.ts
+1
@@ -8,3 +8,4 @@
8
export {constantPropagation} from './ConstantPropagation';
9
export {deadCodeElimination} from './DeadCodeElimination';
10
export {pruneMaybeThrows} from './PruneMaybeThrows';
11
+export {inlineJsxTransform} from './InlineJsxTransform';
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.expect.md
new
+226
@@ -0,0 +1,226 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// @enableInlineJsxTransform
6
+
7
+function Parent({children, a: _a, b: _b, c: _c, ref}) {
8
+ return <div ref={ref}>{children}</div>;
9
+}
10
+
11
+function Child({children}) {
12
+ return <>{children}</>;
13
+}
14
+
15
+function GrandChild({className}) {
16
+ return (
17
+ <span className={className}>
18
+ <React.Fragment key="fragmentKey">Hello world</React.Fragment>
19
+ </span>
20
+ );
21
+}
22
+
23
+function ParentAndRefAndKey(props) {
24
+ const testRef = useRef();
25
+ return <Parent a="a" b={{b: 'b'}} c={C} key="testKey" ref={testRef} />;
26
+}
27
+
28
+function ParentAndChildren(props) {
29
+ return (
30
+ <Parent>
31
+ <Child key="a" />
32
+ <Child key="b">
33
+ <GrandChild className={props.foo} />
34
+ </Child>
35
+ </Parent>
36
+ );
37
+}
38
+
39
+const propsToSpread = {a: 'a', b: 'b', c: 'c'};
40
+function PropsSpread() {
41
+ return <Test {...propsToSpread} />;
42
+}
43
+
44
+export const FIXTURE_ENTRYPOINT = {
45
+ fn: ParentAndChildren,
46
+ params: [{foo: 'abc'}],
47
+};
48
+
49
+```
50
+
51
+## Code
52
+
53
+```javascript
54
+import { c as _c2 } from "react/compiler-runtime"; // @enableInlineJsxTransform
55
+
56
+function Parent(t0) {
57
+ const $ = _c2(2);
58
+ const { children, ref } = t0;
59
+ let t1;
60
+ if ($[0] !== children) {
61
+ t1 = {
62
+ $$typeof: Symbol.for("react.transitional.element"),
63
+ type: "div",
64
+ ref: ref,
65
+ key: null,
66
+ props: { children: children },
67
+ };
68
+ $[0] = children;
69
+ $[1] = t1;
70
+ } else {
71
+ t1 = $[1];
72
+ }
73
+ return t1;
74
+}
75
+
76
+function Child(t0) {
77
+ const $ = _c2(2);
78
+ const { children } = t0;
79
+ let t1;
80
+ if ($[0] !== children) {
81
+ t1 = {
82
+ $$typeof: Symbol.for("react.transitional.element"),
83
+ type: Symbol.for("react.fragment"),
84
+ ref: null,
85
+ key: null,
86
+ props: { children: children },
87
+ };
88
+ $[0] = children;
89
+ $[1] = t1;
90
+ } else {
91
+ t1 = $[1];
92
+ }
93
+ return t1;
94
+}
95
+
96
+function GrandChild(t0) {
97
+ const $ = _c2(3);
98
+ const { className } = t0;
99
+ let t1;
100
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
101
+ t1 = {
102
+ $$typeof: Symbol.for("react.transitional.element"),
103
+ type: React.Fragment,
104
+ ref: null,
105
+ key: "fragmentKey",
106
+ props: { children: "Hello world" },
107
+ };
108
+ $[0] = t1;
109
+ } else {
110
+ t1 = $[0];
111
+ }
112
+ let t2;
113
+ if ($[1] !== className) {
114
+ t2 = {
115
+ $$typeof: Symbol.for("react.transitional.element"),
116
+ type: "span",
117
+ ref: null,
118
+ key: null,
119
+ props: { className: className, children: t1 },
120
+ };
121
+ $[1] = className;
122
+ $[2] = t2;
123
+ } else {
124
+ t2 = $[2];
125
+ }
126
+ return t2;
127
+}
128
+
129
+function ParentAndRefAndKey(props) {
130
+ const $ = _c2(1);
131
+ const testRef = useRef();
132
+ let t0;
133
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
134
+ t0 = {
135
+ $$typeof: Symbol.for("react.transitional.element"),
136
+ type: Parent,
137
+ ref: testRef,
138
+ key: "testKey",
139
+ props: { a: "a", b: { b: "b" }, c: C },
140
+ };
141
+ $[0] = t0;
142
+ } else {
143
+ t0 = $[0];
144
+ }
145
+ return t0;
146
+}
147
+
148
+function ParentAndChildren(props) {
149
+ const $ = _c2(3);
150
+ let t0;
151
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
152
+ t0 = {
153
+ $$typeof: Symbol.for("react.transitional.element"),
154
+ type: Child,
155
+ ref: null,
156
+ key: "a",
157
+ props: {},
158
+ };
159
+ $[0] = t0;
160
+ } else {
161
+ t0 = $[0];
162
+ }
163
+ let t1;
164
+ if ($[1] !== props.foo) {
165
+ t1 = {
166
+ $$typeof: Symbol.for("react.transitional.element"),
167
+ type: Parent,
168
+ ref: null,
169
+ key: null,
170
+ props: {
171
+ children: [
172
+ t0,
173
+ {
174
+ $$typeof: Symbol.for("react.transitional.element"),
175
+ type: Child,
176
+ ref: null,
177
+ key: "b",
178
+ props: {
179
+ children: {
180
+ $$typeof: Symbol.for("react.transitional.element"),
181
+ type: GrandChild,
182
+ ref: null,
183
+ key: null,
184
+ props: { className: props.foo },
185
+ },
186
+ },
187
+ },
188
+ ],
189
+ },
190
+ };
191
+ $[1] = props.foo;
192
+ $[2] = t1;
193
+ } else {
194
+ t1 = $[2];
195
+ }
196
+ return t1;
197
+}
198
+
199
+const propsToSpread = { a: "a", b: "b", c: "c" };
200
+function PropsSpread() {
201
+ const $ = _c2(1);
202
+ let t0;
203
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
204
+ t0 = {
205
+ $$typeof: Symbol.for("react.transitional.element"),
206
+ type: Test,
207
+ ref: null,
208
+ key: null,
209
+ props: { ...propsToSpread },
210
+ };
211
+ $[0] = t0;
212
+ } else {
213
+ t0 = $[0];
214
+ }
215
+ return t0;
216
+}
217
+
218
+export const FIXTURE_ENTRYPOINT = {
219
+ fn: ParentAndChildren,
220
+ params: [{ foo: "abc" }],
221
+};
222
+
223
+```
224
+
225
+### Eval output
226
+(kind: ok) <div><span class="abc">Hello world</span></div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.js
new
+43
@@ -0,0 +1,43 @@
1
+// @enableInlineJsxTransform
2
+
3
+function Parent({children, a: _a, b: _b, c: _c, ref}) {
4
+ return <div ref={ref}>{children}</div>;
5
+}
6
+
7
+function Child({children}) {
8
+ return <>{children}</>;
9
+}
10
+
11
+function GrandChild({className}) {
12
+ return (
13
+ <span className={className}>
14
+ <React.Fragment key="fragmentKey">Hello world</React.Fragment>
15
+ </span>
16
+ );
17
+}
18
+
19
+function ParentAndRefAndKey(props) {
20
+ const testRef = useRef();
21
+ return <Parent a="a" b={{b: 'b'}} c={C} key="testKey" ref={testRef} />;
22
+}
23
+
24
+function ParentAndChildren(props) {
25
+ return (
26
+ <Parent>
27
+ <Child key="a" />
28
+ <Child key="b">
29
+ <GrandChild className={props.foo} />
30
+ </Child>
31
+ </Parent>
32
+ );
33
+}
34
+
35
+const propsToSpread = {a: 'a', b: 'b', c: 'c'};
36
+function PropsSpread() {
37
+ return <Test {...propsToSpread} />;
38
+}
39
+
40
+export const FIXTURE_ENTRYPOINT = {
41
+ fn: ParentAndChildren,
42
+ params: [{foo: 'abc'}],
43
+};