[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}> = [];