@samitouri / QOS-React-1 / commits / 5dcb009760

[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 +};