@samitouri / QOS-React-1 / commits / 543eb09321

[compiler] Wrap inline jsx transform codegen in conditional (#31267)

JSX inlining is a prod-only optimization. We want to enforce this while maintaining the same compiler output in DEV and PROD. Here we add a conditional to the transform that only replaces JSX with object literals outside of DEV. Then a later build step can handle DCE based on the value of `__DEV__`

Jack Pope committed Nov 4, 2024 at 13:19 UTC 543eb0932155fcf8481c457ed98200006ad57cf5
6 files changed +694 -229
compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts
+1
@@ -55,6 +55,7 @@ export const ReactElementSymbolSchema = z.object({
55 z.literal('react.element'),
56 z.literal('react.transitional.element'),
57 ]),
58 + globalDevVar: z.string(),
59 });
60
61 export const ExternalFunctionSchema = z.object({
compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts
+11
@@ -1243,6 +1243,17 @@ export function makeTemporaryIdentifier(
1243 };
1244 }
1245
1246 +export function forkTemporaryIdentifier(
1247 + id: IdentifierId,
1248 + source: Identifier,
1249 +): Identifier {
1250 + return {
1251 + ...source,
1252 + mutableRange: {start: makeInstructionId(0), end: makeInstructionId(0)},
1253 + id,
1254 + };
1255 +}
1256 +
1257 /**
1258 * Creates a valid identifier name. This should *not* be used for synthesizing
1259 * identifier names: only call this method for identifier names that appear in the
compiler/packages/babel-plugin-react-compiler/src/Optimization/InlineJsxTransform.ts
+400 -115
@@ -6,14 +6,25 @@
6 */
7
8 import {
9 + BasicBlock,
10 + BlockId,
11 BuiltinTag,
12 + DeclarationId,
13 Effect,
14 + forkTemporaryIdentifier,
15 + GotoTerminal,
16 + GotoVariant,
17 HIRFunction,
18 + Identifier,
19 + IfTerminal,
20 Instruction,
21 + InstructionKind,
22 JsxAttribute,
23 makeInstructionId,
24 ObjectProperty,
25 + Phi,
26 Place,
27 + promoteTemporary,
28 SpreadPattern,
29 } from '../HIR';
30 import {
@@ -24,6 +35,365 @@ import {
35 reversePostorderBlocks,
36 } from '../HIR/HIRBuilder';
37 import {CompilerError, EnvironmentConfig} from '..';
38 +import {
39 + mapInstructionLValues,
40 + mapInstructionOperands,
41 + mapInstructionValueOperands,
42 + mapTerminalOperands,
43 +} from '../HIR/visitors';
44 +
45 +type InlinedJsxDeclarationMap = Map<
46 + DeclarationId,
47 + {identifier: Identifier; blockIdsToIgnore: Set<BlockId>}
48 +>;
49 +
50 +/**
51 + * A prod-only, RN optimization to replace JSX with inlined ReactElement object literals
52 + *
53 + * Example:
54 + * <>foo</>
55 + * _______________
56 + * let t1;
57 + * if (__DEV__) {
58 + * t1 = <>foo</>
59 + * } else {
60 + * t1 = {...}
61 + * }
62 + *
63 + */
64 +export function inlineJsxTransform(
65 + fn: HIRFunction,
66 + inlineJsxTransformConfig: NonNullable<
67 + EnvironmentConfig['inlineJsxTransform']
68 + >,
69 +): void {
70 + const inlinedJsxDeclarations: InlinedJsxDeclarationMap = new Map();
71 + /**
72 + * Step 1: Codegen the conditional and ReactElement object literal
73 + */
74 + for (const [_, currentBlock] of [...fn.body.blocks]) {
75 + let fallthroughBlockInstructions: Array<Instruction> | null = null;
76 + const instructionCount = currentBlock.instructions.length;
77 + for (let i = 0; i < instructionCount; i++) {
78 + const instr = currentBlock.instructions[i]!;
79 + // TODO: Support value blocks
80 + if (currentBlock.kind === 'value') {
81 + fn.env.logger?.logEvent(fn.env.filename, {
82 + kind: 'CompileDiagnostic',
83 + fnLoc: null,
84 + detail: {
85 + reason: 'JSX Inlining is not supported on value blocks',
86 + loc: instr.loc,
87 + },
88 + });
89 + continue;
90 + }
91 + switch (instr.value.kind) {
92 + case 'JsxExpression':
93 + case 'JsxFragment': {
94 + /**
95 + * Split into blocks for new IfTerminal:
96 + * current, then, else, fallthrough
97 + */
98 + const currentBlockInstructions = currentBlock.instructions.slice(
99 + 0,
100 + i,
101 + );
102 + const thenBlockInstructions = currentBlock.instructions.slice(
103 + i,
104 + i + 1,
105 + );
106 + const elseBlockInstructions: Array<Instruction> = [];
107 + fallthroughBlockInstructions ??= currentBlock.instructions.slice(
108 + i + 1,
109 + );
110 +
111 + const fallthroughBlockId = fn.env.nextBlockId;
112 + const fallthroughBlock: BasicBlock = {
113 + kind: currentBlock.kind,
114 + id: fallthroughBlockId,
115 + instructions: fallthroughBlockInstructions,
116 + terminal: currentBlock.terminal,
117 + preds: new Set(),
118 + phis: new Set(),
119 + };
120 +
121 + /**
122 + * Complete current block
123 + * - Add instruction for variable declaration
124 + * - Add instruction for LoadGlobal used by conditional
125 + * - End block with a new IfTerminal
126 + */
127 + const varPlace = createTemporaryPlace(fn.env, instr.value.loc);
128 + promoteTemporary(varPlace.identifier);
129 + const varLValuePlace = createTemporaryPlace(fn.env, instr.value.loc);
130 + const thenVarPlace = {
131 + ...varPlace,
132 + identifier: forkTemporaryIdentifier(
133 + fn.env.nextIdentifierId,
134 + varPlace.identifier,
135 + ),
136 + };
137 + const elseVarPlace = {
138 + ...varPlace,
139 + identifier: forkTemporaryIdentifier(
140 + fn.env.nextIdentifierId,
141 + varPlace.identifier,
142 + ),
143 + };
144 + const varInstruction: Instruction = {
145 + id: makeInstructionId(0),
146 + lvalue: {...varLValuePlace},
147 + value: {
148 + kind: 'DeclareLocal',
149 + lvalue: {place: {...varPlace}, kind: InstructionKind.Let},
150 + type: null,
151 + loc: instr.value.loc,
152 + },
153 + loc: instr.loc,
154 + };
155 + currentBlockInstructions.push(varInstruction);
156 +
157 + const devGlobalPlace = createTemporaryPlace(fn.env, instr.value.loc);
158 + const devGlobalInstruction: Instruction = {
159 + id: makeInstructionId(0),
160 + lvalue: {...devGlobalPlace, effect: Effect.Mutate},
161 + value: {
162 + kind: 'LoadGlobal',
163 + binding: {
164 + kind: 'Global',
165 + name: inlineJsxTransformConfig.globalDevVar,
166 + },
167 + loc: instr.value.loc,
168 + },
169 + loc: instr.loc,
170 + };
171 + currentBlockInstructions.push(devGlobalInstruction);
172 + const thenBlockId = fn.env.nextBlockId;
173 + const elseBlockId = fn.env.nextBlockId;
174 + const ifTerminal: IfTerminal = {
175 + kind: 'if',
176 + test: {...devGlobalPlace, effect: Effect.Read},
177 + consequent: thenBlockId,
178 + alternate: elseBlockId,
179 + fallthrough: fallthroughBlockId,
180 + loc: instr.loc,
181 + id: makeInstructionId(0),
182 + };
183 + currentBlock.instructions = currentBlockInstructions;
184 + currentBlock.terminal = ifTerminal;
185 +
186 + /**
187 + * Set up then block where we put the original JSX return
188 + */
189 + const thenBlock: BasicBlock = {
190 + id: thenBlockId,
191 + instructions: thenBlockInstructions,
192 + kind: 'block',
193 + phis: new Set(),
194 + preds: new Set(),
195 + terminal: {
196 + kind: 'goto',
197 + block: fallthroughBlockId,
198 + variant: GotoVariant.Break,
199 + id: makeInstructionId(0),
200 + loc: instr.loc,
201 + },
202 + };
203 + fn.body.blocks.set(thenBlockId, thenBlock);
204 +
205 + const resassignElsePlace = createTemporaryPlace(
206 + fn.env,
207 + instr.value.loc,
208 + );
209 + const reassignElseInstruction: Instruction = {
210 + id: makeInstructionId(0),
211 + lvalue: {...resassignElsePlace},
212 + value: {
213 + kind: 'StoreLocal',
214 + lvalue: {
215 + place: elseVarPlace,
216 + kind: InstructionKind.Reassign,
217 + },
218 + value: {...instr.lvalue},
219 + type: null,
220 + loc: instr.value.loc,
221 + },
222 + loc: instr.loc,
223 + };
224 + thenBlockInstructions.push(reassignElseInstruction);
225 +
226 + /**
227 + * Set up else block where we add new codegen
228 + */
229 + const elseBlockTerminal: GotoTerminal = {
230 + kind: 'goto',
231 + block: fallthroughBlockId,
232 + variant: GotoVariant.Break,
233 + id: makeInstructionId(0),
234 + loc: instr.loc,
235 + };
236 + const elseBlock: BasicBlock = {
237 + id: elseBlockId,
238 + instructions: elseBlockInstructions,
239 + kind: 'block',
240 + phis: new Set(),
241 + preds: new Set(),
242 + terminal: elseBlockTerminal,
243 + };
244 + fn.body.blocks.set(elseBlockId, elseBlock);
245 +
246 + /**
247 + * ReactElement object literal codegen
248 + */
249 + const {refProperty, keyProperty, propsProperty} =
250 + createPropsProperties(
251 + fn,
252 + instr,
253 + elseBlockInstructions,
254 + instr.value.kind === 'JsxExpression' ? instr.value.props : [],
255 + instr.value.children,
256 + );
257 + const reactElementInstructionPlace = createTemporaryPlace(
258 + fn.env,
259 + instr.value.loc,
260 + );
261 + const reactElementInstruction: Instruction = {
262 + id: makeInstructionId(0),
263 + lvalue: {...reactElementInstructionPlace, effect: Effect.Store},
264 + value: {
265 + kind: 'ObjectExpression',
266 + properties: [
267 + createSymbolProperty(
268 + fn,
269 + instr,
270 + elseBlockInstructions,
271 + '$$typeof',
272 + inlineJsxTransformConfig.elementSymbol,
273 + ),
274 + instr.value.kind === 'JsxExpression'
275 + ? createTagProperty(
276 + fn,
277 + instr,
278 + elseBlockInstructions,
279 + instr.value.tag,
280 + )
281 + : createSymbolProperty(
282 + fn,
283 + instr,
284 + elseBlockInstructions,
285 + 'type',
286 + 'react.fragment',
287 + ),
288 + refProperty,
289 + keyProperty,
290 + propsProperty,
291 + ],
292 + loc: instr.value.loc,
293 + },
294 + loc: instr.loc,
295 + };
296 + elseBlockInstructions.push(reactElementInstruction);
297 +
298 + const reassignConditionalInstruction: Instruction = {
299 + id: makeInstructionId(0),
300 + lvalue: {...createTemporaryPlace(fn.env, instr.value.loc)},
301 + value: {
302 + kind: 'StoreLocal',
303 + lvalue: {
304 + place: {...elseVarPlace},
305 + kind: InstructionKind.Reassign,
306 + },
307 + value: {...reactElementInstruction.lvalue},
308 + type: null,
309 + loc: instr.value.loc,
310 + },
311 + loc: instr.loc,
312 + };
313 + elseBlockInstructions.push(reassignConditionalInstruction);
314 +
315 + /**
316 + * Create phis to reassign the var
317 + */
318 + const operands: Map<BlockId, Place> = new Map();
319 + operands.set(thenBlockId, {
320 + ...elseVarPlace,
321 + });
322 + operands.set(elseBlockId, {
323 + ...thenVarPlace,
324 + });
325 +
326 + const phiIdentifier = forkTemporaryIdentifier(
327 + fn.env.nextIdentifierId,
328 + varPlace.identifier,
329 + );
330 + const phiPlace = {
331 + ...createTemporaryPlace(fn.env, instr.value.loc),
332 + identifier: phiIdentifier,
333 + };
334 + const phis: Set<Phi> = new Set([
335 + {
336 + kind: 'Phi',
337 + operands,
338 + place: phiPlace,
339 + },
340 + ]);
341 + fallthroughBlock.phis = phis;
342 + fn.body.blocks.set(fallthroughBlockId, fallthroughBlock);
343 +
344 + /**
345 + * Track this JSX instruction so we can replace references in step 2
346 + */
347 + inlinedJsxDeclarations.set(instr.lvalue.identifier.declarationId, {
348 + identifier: phiIdentifier,
349 + blockIdsToIgnore: new Set([thenBlockId, elseBlockId]),
350 + });
351 + break;
352 + }
353 + case 'FunctionExpression':
354 + case 'ObjectMethod': {
355 + inlineJsxTransform(
356 + instr.value.loweredFunc.func,
357 + inlineJsxTransformConfig,
358 + );
359 + break;
360 + }
361 + }
362 + }
363 + }
364 +
365 + /**
366 + * Step 2: Replace declarations with new phi values
367 + */
368 + for (const [blockId, block] of fn.body.blocks) {
369 + for (const instr of block.instructions) {
370 + mapInstructionOperands(instr, place =>
371 + handlePlace(place, blockId, inlinedJsxDeclarations),
372 + );
373 +
374 + mapInstructionLValues(instr, lvalue =>
375 + handlelValue(lvalue, blockId, inlinedJsxDeclarations),
376 + );
377 +
378 + mapInstructionValueOperands(instr.value, place =>
379 + handlePlace(place, blockId, inlinedJsxDeclarations),
380 + );
381 + }
382 +
383 + mapTerminalOperands(block.terminal, place =>
384 + handlePlace(place, blockId, inlinedJsxDeclarations),
385 + );
386 + }
387 +
388 + /**
389 + * Step 3: Fixup the HIR
390 + * Restore RPO, ensure correct predecessors, renumber instructions, fix scope and ranges.
391 + */
392 + reversePostorderBlocks(fn.body);
393 + markPredecessors(fn.body);
394 + markInstructionIds(fn.body);
395 + fixScopeAndIdentifierRanges(fn.body);
396 +}
397
398 function createSymbolProperty(
399 fn: HIRFunction,
@@ -315,123 +685,38 @@ function createPropsProperties(
685 return {refProperty, keyProperty, propsProperty};
686 }
687
318 -// TODO: Make PROD only with conditional statements
319 -export function inlineJsxTransform(
320 - fn: HIRFunction,
321 - inlineJsxTransformConfig: NonNullable<
322 - EnvironmentConfig['inlineJsxTransform']
323 - >,
324 -): void {
325 - for (const [, block] of fn.body.blocks) {
326 - let nextInstructions: Array<Instruction> | null = null;
327 - for (let i = 0; i < block.instructions.length; i++) {
328 - const instr = block.instructions[i]!;
329 - switch (instr.value.kind) {
330 - case 'JsxExpression': {
331 - nextInstructions ??= block.instructions.slice(0, i);
688 +function handlePlace(
689 + place: Place,
690 + blockId: BlockId,
691 + inlinedJsxDeclarations: InlinedJsxDeclarationMap,
692 +): Place {
693 + const inlinedJsxDeclaration = inlinedJsxDeclarations.get(
694 + place.identifier.declarationId,
695 + );
696 + if (
697 + inlinedJsxDeclaration == null ||
698 + inlinedJsxDeclaration.blockIdsToIgnore.has(blockId)
699 + ) {
700 + return {...place};
701 + }
702
333 - const {refProperty, keyProperty, propsProperty} =
334 - createPropsProperties(
335 - fn,
336 - instr,
337 - nextInstructions,
338 - instr.value.props,
339 - instr.value.children,
340 - );
341 - const reactElementInstruction: Instruction = {
342 - id: makeInstructionId(0),
343 - lvalue: {...instr.lvalue, effect: Effect.Store},
344 - value: {
345 - kind: 'ObjectExpression',
346 - properties: [
347 - createSymbolProperty(
348 - fn,
349 - instr,
350 - nextInstructions,
351 - '$$typeof',
352 - inlineJsxTransformConfig.elementSymbol,
353 - ),
354 - createTagProperty(fn, instr, nextInstructions, instr.value.tag),
355 - refProperty,
356 - keyProperty,
357 - propsProperty,
358 - ],
359 - loc: instr.value.loc,
360 - },
361 - loc: instr.loc,
362 - };
363 - nextInstructions.push(reactElementInstruction);
703 + return {...place, identifier: {...inlinedJsxDeclaration.identifier}};
704 +}
705
365 - break;
366 - }
367 - case 'JsxFragment': {
368 - nextInstructions ??= block.instructions.slice(0, i);
369 - const {refProperty, keyProperty, propsProperty} =
370 - createPropsProperties(
371 - fn,
372 - instr,
373 - nextInstructions,
374 - [],
375 - instr.value.children,
376 - );
377 - const reactElementInstruction: Instruction = {
378 - id: makeInstructionId(0),
379 - lvalue: {...instr.lvalue, effect: Effect.Store},
380 - value: {
381 - kind: 'ObjectExpression',
382 - properties: [
383 - createSymbolProperty(
384 - fn,
385 - instr,
386 - nextInstructions,
387 - '$$typeof',
388 - inlineJsxTransformConfig.elementSymbol,
389 - ),
390 - createSymbolProperty(
391 - fn,
392 - instr,
393 - nextInstructions,
394 - 'type',
395 - 'react.fragment',
396 - ),
397 - refProperty,
398 - keyProperty,
399 - propsProperty,
400 - ],
401 - loc: instr.value.loc,
402 - },
403 - loc: instr.loc,
404 - };
405 - nextInstructions.push(reactElementInstruction);
406 - break;
407 - }
408 - case 'FunctionExpression':
409 - case 'ObjectMethod': {
410 - inlineJsxTransform(
411 - instr.value.loweredFunc.func,
412 - inlineJsxTransformConfig,
413 - );
414 - if (nextInstructions !== null) {
415 - nextInstructions.push(instr);
416 - }
417 - break;
418 - }
419 - default: {
420 - if (nextInstructions !== null) {
421 - nextInstructions.push(instr);
422 - }
423 - }
424 - }
425 - }
426 - if (nextInstructions !== null) {
427 - block.instructions = nextInstructions;
428 - }
706 +function handlelValue(
707 + lvalue: Place,
708 + blockId: BlockId,
709 + inlinedJsxDeclarations: InlinedJsxDeclarationMap,
710 +): Place {
711 + const inlinedJsxDeclaration = inlinedJsxDeclarations.get(
712 + lvalue.identifier.declarationId,
713 + );
714 + if (
715 + inlinedJsxDeclaration == null ||
716 + inlinedJsxDeclaration.blockIdsToIgnore.has(blockId)
717 + ) {
718 + return {...lvalue};
719 }
720
431 - // Fixup the HIR to restore RPO, ensure correct predecessors, and renumber instructions.
432 - reversePostorderBlocks(fn.body);
433 - markPredecessors(fn.body);
434 - markInstructionIds(fn.body);
435 - // The renumbering instructions invalidates scope and identifier ranges
436 - fixScopeAndIdentifierRanges(fn.body);
721 + return {...lvalue, identifier: {...inlinedJsxDeclaration.identifier}};
722 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.expect.md
+262 -113
@@ -50,6 +50,22 @@ function PropsSpread() {
50 );
51 }
52
53 +function ConditionalJsx({shouldWrap}) {
54 + let content = <div>Hello</div>;
55 +
56 + if (shouldWrap) {
57 + content = <Parent>{content}</Parent>;
58 + }
59 +
60 + return content;
61 +}
62 +
63 +// TODO: Support value blocks
64 +function TernaryJsx({cond}) {
65 + return cond ? <div /> : null;
66 +}
67 +
68 +global.DEV = true;
69 export const FIXTURE_ENTRYPOINT = {
70 fn: ParentAndChildren,
71 params: [{foo: 'abc'}],
@@ -67,13 +83,17 @@ function Parent(t0) {
83 const { children, ref } = t0;
84 let t1;
85 if ($[0] !== children) {
70 - t1 = {
71 - $$typeof: Symbol.for("react.transitional.element"),
72 - type: "div",
73 - ref: ref,
74 - key: null,
75 - props: { children: children },
76 - };
86 + if (DEV) {
87 + t1 = <div ref={ref}>{children}</div>;
88 + } else {
89 + t1 = {
90 + $$typeof: Symbol.for("react.transitional.element"),
91 + type: "div",
92 + ref: ref,
93 + key: null,
94 + props: { children: children },
95 + };
96 + }
97 $[0] = children;
98 $[1] = t1;
99 } else {
@@ -87,13 +107,17 @@ function Child(t0) {
107 const { children } = t0;
108 let t1;
109 if ($[0] !== children) {
90 - t1 = {
91 - $$typeof: Symbol.for("react.transitional.element"),
92 - type: Symbol.for("react.fragment"),
93 - ref: null,
94 - key: null,
95 - props: { children: children },
96 - };
110 + if (DEV) {
111 + t1 = <>{children}</>;
112 + } else {
113 + t1 = {
114 + $$typeof: Symbol.for("react.transitional.element"),
115 + type: Symbol.for("react.fragment"),
116 + ref: null,
117 + key: null,
118 + props: { children: children },
119 + };
120 + }
121 $[0] = children;
122 $[1] = t1;
123 } else {
@@ -107,26 +131,34 @@ function GrandChild(t0) {
131 const { className } = t0;
132 let t1;
133 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
110 - t1 = {
111 - $$typeof: Symbol.for("react.transitional.element"),
112 - type: React.Fragment,
113 - ref: null,
114 - key: "fragmentKey",
115 - props: { children: "Hello world" },
116 - };
134 + if (DEV) {
135 + t1 = <React.Fragment key="fragmentKey">Hello world</React.Fragment>;
136 + } else {
137 + t1 = {
138 + $$typeof: Symbol.for("react.transitional.element"),
139 + type: React.Fragment,
140 + ref: null,
141 + key: "fragmentKey",
142 + props: { children: "Hello world" },
143 + };
144 + }
145 $[0] = t1;
146 } else {
147 t1 = $[0];
148 }
149 let t2;
150 if ($[1] !== className) {
123 - t2 = {
124 - $$typeof: Symbol.for("react.transitional.element"),
125 - type: "span",
126 - ref: null,
127 - key: null,
128 - props: { className: className, children: t1 },
129 - };
151 + if (DEV) {
152 + t2 = <span className={className}>{t1}</span>;
153 + } else {
154 + t2 = {
155 + $$typeof: Symbol.for("react.transitional.element"),
156 + type: "span",
157 + ref: null,
158 + key: null,
159 + props: { className: className, children: t1 },
160 + };
161 + }
162 $[1] = className;
163 $[2] = t2;
164 } else {
@@ -140,13 +172,17 @@ function ParentAndRefAndKey(props) {
172 const testRef = useRef();
173 let t0;
174 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
143 - t0 = {
144 - $$typeof: Symbol.for("react.transitional.element"),
145 - type: Parent,
146 - ref: testRef,
147 - key: "testKey",
148 - props: { a: "a", b: { b: "b" }, c: C },
149 - };
175 + if (DEV) {
176 + t0 = <Parent a="a" b={{ b: "b" }} c={C} key="testKey" ref={testRef} />;
177 + } else {
178 + t0 = {
179 + $$typeof: Symbol.for("react.transitional.element"),
180 + type: Parent,
181 + ref: testRef,
182 + key: "testKey",
183 + props: { a: "a", b: { b: "b" }, c: C },
184 + };
185 + }
186 $[0] = t0;
187 } else {
188 t0 = $[0];
@@ -158,13 +194,21 @@ function ParentAndChildren(props) {
194 const $ = _c2(14);
195 let t0;
196 if ($[0] !== props.foo) {
161 - t0 = () => ({
162 - $$typeof: Symbol.for("react.transitional.element"),
163 - type: "div",
164 - ref: null,
165 - key: "d",
166 - props: { children: props.foo },
167 - });
197 + t0 = () => {
198 + let t1;
199 + if (DEV) {
200 + t1 = <div key="d">{props.foo}</div>;
201 + } else {
202 + t1 = {
203 + $$typeof: Symbol.for("react.transitional.element"),
204 + type: "div",
205 + ref: null,
206 + key: "d",
207 + props: { children: props.foo },
208 + };
209 + }
210 + return t1;
211 + };
212 $[0] = props.foo;
213 $[1] = t0;
214 } else {
@@ -173,71 +217,99 @@ function ParentAndChildren(props) {
217 const render = t0;
218 let t1;
219 if ($[2] !== props) {
176 - t1 = {
177 - $$typeof: Symbol.for("react.transitional.element"),
178 - type: Child,
179 - ref: null,
180 - key: "a",
181 - props: props,
182 - };
220 + if (DEV) {
221 + t1 = <Child key="a" {...props} />;
222 + } else {
223 + t1 = {
224 + $$typeof: Symbol.for("react.transitional.element"),
225 + type: Child,
226 + ref: null,
227 + key: "a",
228 + props: props,
229 + };
230 + }
231 $[2] = props;
232 $[3] = t1;
233 } else {
234 t1 = $[3];
235 }
188 - let t2;
236 +
237 + const t2 = props.foo;
238 + let t3;
239 if ($[4] !== props) {
190 - t2 = {
191 - $$typeof: Symbol.for("react.transitional.element"),
192 - type: GrandChild,
193 - ref: null,
194 - key: "c",
195 - props: { className: props.foo, ...props },
196 - };
240 + if (DEV) {
241 + t3 = <GrandChild key="c" className={t2} {...props} />;
242 + } else {
243 + t3 = {
244 + $$typeof: Symbol.for("react.transitional.element"),
245 + type: GrandChild,
246 + ref: null,
247 + key: "c",
248 + props: { className: t2, ...props },
249 + };
250 + }
251 $[4] = props;
198 - $[5] = t2;
252 + $[5] = t3;
253 } else {
200 - t2 = $[5];
254 + t3 = $[5];
255 }
202 - let t3;
256 + let t4;
257 if ($[6] !== render) {
204 - t3 = render();
258 + t4 = render();
259 $[6] = render;
206 - $[7] = t3;
260 + $[7] = t4;
261 } else {
208 - t3 = $[7];
262 + t4 = $[7];
263 }
210 - let t4;
211 - if ($[8] !== t2 || $[9] !== t3) {
212 - t4 = {
213 - $$typeof: Symbol.for("react.transitional.element"),
214 - type: Child,
215 - ref: null,
216 - key: "b",
217 - props: { children: [t2, t3] },
218 - };
219 - $[8] = t2;
220 - $[9] = t3;
221 - $[10] = t4;
264 + let t5;
265 + if ($[8] !== t3 || $[9] !== t4) {
266 + if (DEV) {
267 + t5 = (
268 + <Child key="b">
269 + {t3}
270 + {t4}
271 + </Child>
272 + );
273 + } else {
274 + t5 = {
275 + $$typeof: Symbol.for("react.transitional.element"),
276 + type: Child,
277 + ref: null,
278 + key: "b",
279 + props: { children: [t3, t4] },
280 + };
281 + }
282 + $[8] = t3;
283 + $[9] = t4;
284 + $[10] = t5;
285 } else {
223 - t4 = $[10];
286 + t5 = $[10];
287 }
225 - let t5;
226 - if ($[11] !== t1 || $[12] !== t4) {
227 - t5 = {
228 - $$typeof: Symbol.for("react.transitional.element"),
229 - type: Parent,
230 - ref: null,
231 - key: null,
232 - props: { children: [t1, t4] },
233 - };
288 + let t6;
289 + if ($[11] !== t1 || $[12] !== t5) {
290 + if (DEV) {
291 + t6 = (
292 + <Parent>
293 + {t1}
294 + {t5}
295 + </Parent>
296 + );
297 + } else {
298 + t6 = {
299 + $$typeof: Symbol.for("react.transitional.element"),
300 + type: Parent,
301 + ref: null,
302 + key: null,
303 + props: { children: [t1, t5] },
304 + };
305 + }
306 $[11] = t1;
235 - $[12] = t4;
236 - $[13] = t5;
307 + $[12] = t5;
308 + $[13] = t6;
309 } else {
238 - t5 = $[13];
310 + t6 = $[13];
311 }
240 - return t5;
312 + return t6;
313 }
314
315 const propsToSpread = { a: "a", b: "b", c: "c" };
@@ -245,30 +317,46 @@ function PropsSpread() {
317 const $ = _c2(1);
318 let t0;
319 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
248 - t0 = {
249 - $$typeof: Symbol.for("react.transitional.element"),
250 - type: Symbol.for("react.fragment"),
251 - ref: null,
252 - key: null,
253 - props: {
254 - children: [
255 - {
256 - $$typeof: Symbol.for("react.transitional.element"),
257 - type: Test,
258 - ref: null,
259 - key: "a",
260 - props: propsToSpread,
261 - },
262 - {
263 - $$typeof: Symbol.for("react.transitional.element"),
264 - type: Test,
265 - ref: null,
266 - key: "b",
267 - props: { ...propsToSpread, a: "z" },
268 - },
269 - ],
270 - },
271 - };
320 + let t1;
321 + if (DEV) {
322 + t1 = <Test key="a" {...propsToSpread} />;
323 + } else {
324 + t1 = {
325 + $$typeof: Symbol.for("react.transitional.element"),
326 + type: Test,
327 + ref: null,
328 + key: "a",
329 + props: propsToSpread,
330 + };
331 + }
332 + let t2;
333 + if (DEV) {
334 + t2 = <Test key="b" {...propsToSpread} a="z" />;
335 + } else {
336 + t2 = {
337 + $$typeof: Symbol.for("react.transitional.element"),
338 + type: Test,
339 + ref: null,
340 + key: "b",
341 + props: { ...propsToSpread, a: "z" },
342 + };
343 + }
344 + if (DEV) {
345 + t0 = (
346 + <>
347 + {t1}
348 + {t2}
349 + </>
350 + );
351 + } else {
352 + t0 = {
353 + $$typeof: Symbol.for("react.transitional.element"),
354 + type: Symbol.for("react.fragment"),
355 + ref: null,
356 + key: null,
357 + props: { children: [t1, t2] },
358 + };
359 + }
360 $[0] = t0;
361 } else {
362 t0 = $[0];
@@ -276,6 +364,67 @@ function PropsSpread() {
364 return t0;
365 }
366
367 +function ConditionalJsx(t0) {
368 + const $ = _c2(2);
369 + const { shouldWrap } = t0;
370 + let t1;
371 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
372 + if (DEV) {
373 + t1 = <div>Hello</div>;
374 + } else {
375 + t1 = {
376 + $$typeof: Symbol.for("react.transitional.element"),
377 + type: "div",
378 + ref: null,
379 + key: null,
380 + props: { children: "Hello" },
381 + };
382 + }
383 + $[0] = t1;
384 + } else {
385 + t1 = $[0];
386 + }
387 + let content = t1;
388 + if (shouldWrap) {
389 + const t2 = content;
390 + let t3;
391 + if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
392 + if (DEV) {
393 + t3 = <Parent>{t2}</Parent>;
394 + } else {
395 + t3 = {
396 + $$typeof: Symbol.for("react.transitional.element"),
397 + type: Parent,
398 + ref: null,
399 + key: null,
400 + props: { children: t2 },
401 + };
402 + }
403 + $[1] = t3;
404 + } else {
405 + t3 = $[1];
406 + }
407 + content = t3;
408 + }
409 + return content;
410 +}
411 +
412 +// TODO: Support value blocks
413 +function TernaryJsx(t0) {
414 + const $ = _c2(2);
415 + const { cond } = t0;
416 + let t1;
417 + if ($[0] !== cond) {
418 + t1 = cond ? <div /> : null;
419 + $[0] = cond;
420 + $[1] = t1;
421 + } else {
422 + t1 = $[1];
423 + }
424 + return t1;
425 +}
426 +
427 +global.DEV = true;
428 export const FIXTURE_ENTRYPOINT = {
429 fn: ParentAndChildren,
430 params: [{ foo: "abc" }],
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inline-jsx-transform.js
+16
@@ -46,6 +46,22 @@ function PropsSpread() {
46 );
47 }
48
49 +function ConditionalJsx({shouldWrap}) {
50 + let content = <div>Hello</div>;
51 +
52 + if (shouldWrap) {
53 + content = <Parent>{content}</Parent>;
54 + }
55 +
56 + return content;
57 +}
58 +
59 +// TODO: Support value blocks
60 +function TernaryJsx({cond}) {
61 + return cond ? <div /> : null;
62 +}
63 +
64 +global.DEV = true;
65 export const FIXTURE_ENTRYPOINT = {
66 fn: ParentAndChildren,
67 params: [{foo: 'abc'}],
compiler/packages/snap/src/compiler.ts
+4 -1
@@ -207,7 +207,10 @@ function makePluginOptions(
207
208 let inlineJsxTransform: EnvironmentConfig['inlineJsxTransform'] = null;
209 if (firstLine.includes('@enableInlineJsxTransform')) {
210 - inlineJsxTransform = {elementSymbol: 'react.transitional.element'};
210 + inlineJsxTransform = {
211 + elementSymbol: 'react.transitional.element',
212 + globalDevVar: 'DEV',
213 + };
214 }
215
216 let logs: Array<{filename: string | null; event: LoggerEvent}> = [];