[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__`
This commit is contained in:
Jack Pope 2024-11-04 13:19:05 -05:00 committed by GitHub
parent 4d577fd216
commit 543eb09321
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 700 additions and 235 deletions

View File

@ -55,6 +55,7 @@ export const ReactElementSymbolSchema = z.object({
z.literal('react.element'),
z.literal('react.transitional.element'),
]),
globalDevVar: z.string(),
});
export const ExternalFunctionSchema = z.object({

View File

@ -1243,6 +1243,17 @@ export function makeTemporaryIdentifier(
};
}
export function forkTemporaryIdentifier(
id: IdentifierId,
source: Identifier,
): Identifier {
return {
...source,
mutableRange: {start: makeInstructionId(0), end: makeInstructionId(0)},
id,
};
}
/**
* Creates a valid identifier name. This should *not* be used for synthesizing
* identifier names: only call this method for identifier names that appear in the

View File

@ -6,14 +6,25 @@
*/
import {
BasicBlock,
BlockId,
BuiltinTag,
DeclarationId,
Effect,
forkTemporaryIdentifier,
GotoTerminal,
GotoVariant,
HIRFunction,
Identifier,
IfTerminal,
Instruction,
InstructionKind,
JsxAttribute,
makeInstructionId,
ObjectProperty,
Phi,
Place,
promoteTemporary,
SpreadPattern,
} from '../HIR';
import {
@ -24,6 +35,365 @@ import {
reversePostorderBlocks,
} from '../HIR/HIRBuilder';
import {CompilerError, EnvironmentConfig} from '..';
import {
mapInstructionLValues,
mapInstructionOperands,
mapInstructionValueOperands,
mapTerminalOperands,
} from '../HIR/visitors';
type InlinedJsxDeclarationMap = Map<
DeclarationId,
{identifier: Identifier; blockIdsToIgnore: Set<BlockId>}
>;
/**
* A prod-only, RN optimization to replace JSX with inlined ReactElement object literals
*
* Example:
* <>foo</>
* _______________
* let t1;
* if (__DEV__) {
* t1 = <>foo</>
* } else {
* t1 = {...}
* }
*
*/
export function inlineJsxTransform(
fn: HIRFunction,
inlineJsxTransformConfig: NonNullable<
EnvironmentConfig['inlineJsxTransform']
>,
): void {
const inlinedJsxDeclarations: InlinedJsxDeclarationMap = new Map();
/**
* Step 1: Codegen the conditional and ReactElement object literal
*/
for (const [_, currentBlock] of [...fn.body.blocks]) {
let fallthroughBlockInstructions: Array<Instruction> | null = null;
const instructionCount = currentBlock.instructions.length;
for (let i = 0; i < instructionCount; i++) {
const instr = currentBlock.instructions[i]!;
// TODO: Support value blocks
if (currentBlock.kind === 'value') {
fn.env.logger?.logEvent(fn.env.filename, {
kind: 'CompileDiagnostic',
fnLoc: null,
detail: {
reason: 'JSX Inlining is not supported on value blocks',
loc: instr.loc,
},
});
continue;
}
switch (instr.value.kind) {
case 'JsxExpression':
case 'JsxFragment': {
/**
* Split into blocks for new IfTerminal:
* current, then, else, fallthrough
*/
const currentBlockInstructions = currentBlock.instructions.slice(
0,
i,
);
const thenBlockInstructions = currentBlock.instructions.slice(
i,
i + 1,
);
const elseBlockInstructions: Array<Instruction> = [];
fallthroughBlockInstructions ??= currentBlock.instructions.slice(
i + 1,
);
const fallthroughBlockId = fn.env.nextBlockId;
const fallthroughBlock: BasicBlock = {
kind: currentBlock.kind,
id: fallthroughBlockId,
instructions: fallthroughBlockInstructions,
terminal: currentBlock.terminal,
preds: new Set(),
phis: new Set(),
};
/**
* Complete current block
* - Add instruction for variable declaration
* - Add instruction for LoadGlobal used by conditional
* - End block with a new IfTerminal
*/
const varPlace = createTemporaryPlace(fn.env, instr.value.loc);
promoteTemporary(varPlace.identifier);
const varLValuePlace = createTemporaryPlace(fn.env, instr.value.loc);
const thenVarPlace = {
...varPlace,
identifier: forkTemporaryIdentifier(
fn.env.nextIdentifierId,
varPlace.identifier,
),
};
const elseVarPlace = {
...varPlace,
identifier: forkTemporaryIdentifier(
fn.env.nextIdentifierId,
varPlace.identifier,
),
};
const varInstruction: Instruction = {
id: makeInstructionId(0),
lvalue: {...varLValuePlace},
value: {
kind: 'DeclareLocal',
lvalue: {place: {...varPlace}, kind: InstructionKind.Let},
type: null,
loc: instr.value.loc,
},
loc: instr.loc,
};
currentBlockInstructions.push(varInstruction);
const devGlobalPlace = createTemporaryPlace(fn.env, instr.value.loc);
const devGlobalInstruction: Instruction = {
id: makeInstructionId(0),
lvalue: {...devGlobalPlace, effect: Effect.Mutate},
value: {
kind: 'LoadGlobal',
binding: {
kind: 'Global',
name: inlineJsxTransformConfig.globalDevVar,
},
loc: instr.value.loc,
},
loc: instr.loc,
};
currentBlockInstructions.push(devGlobalInstruction);
const thenBlockId = fn.env.nextBlockId;
const elseBlockId = fn.env.nextBlockId;
const ifTerminal: IfTerminal = {
kind: 'if',
test: {...devGlobalPlace, effect: Effect.Read},
consequent: thenBlockId,
alternate: elseBlockId,
fallthrough: fallthroughBlockId,
loc: instr.loc,
id: makeInstructionId(0),
};
currentBlock.instructions = currentBlockInstructions;
currentBlock.terminal = ifTerminal;
/**
* Set up then block where we put the original JSX return
*/
const thenBlock: BasicBlock = {
id: thenBlockId,
instructions: thenBlockInstructions,
kind: 'block',
phis: new Set(),
preds: new Set(),
terminal: {
kind: 'goto',
block: fallthroughBlockId,
variant: GotoVariant.Break,
id: makeInstructionId(0),
loc: instr.loc,
},
};
fn.body.blocks.set(thenBlockId, thenBlock);
const resassignElsePlace = createTemporaryPlace(
fn.env,
instr.value.loc,
);
const reassignElseInstruction: Instruction = {
id: makeInstructionId(0),
lvalue: {...resassignElsePlace},
value: {
kind: 'StoreLocal',
lvalue: {
place: elseVarPlace,
kind: InstructionKind.Reassign,
},
value: {...instr.lvalue},
type: null,
loc: instr.value.loc,
},
loc: instr.loc,
};
thenBlockInstructions.push(reassignElseInstruction);
/**
* Set up else block where we add new codegen
*/
const elseBlockTerminal: GotoTerminal = {
kind: 'goto',
block: fallthroughBlockId,
variant: GotoVariant.Break,
id: makeInstructionId(0),
loc: instr.loc,
};
const elseBlock: BasicBlock = {
id: elseBlockId,
instructions: elseBlockInstructions,
kind: 'block',
phis: new Set(),
preds: new Set(),
terminal: elseBlockTerminal,
};
fn.body.blocks.set(elseBlockId, elseBlock);
/**
* ReactElement object literal codegen
*/
const {refProperty, keyProperty, propsProperty} =
createPropsProperties(
fn,
instr,
elseBlockInstructions,
instr.value.kind === 'JsxExpression' ? instr.value.props : [],
instr.value.children,
);
const reactElementInstructionPlace = createTemporaryPlace(
fn.env,
instr.value.loc,
);
const reactElementInstruction: Instruction = {
id: makeInstructionId(0),
lvalue: {...reactElementInstructionPlace, effect: Effect.Store},
value: {
kind: 'ObjectExpression',
properties: [
createSymbolProperty(
fn,
instr,
elseBlockInstructions,
'$$typeof',
inlineJsxTransformConfig.elementSymbol,
),
instr.value.kind === 'JsxExpression'
? createTagProperty(
fn,
instr,
elseBlockInstructions,
instr.value.tag,
)
: createSymbolProperty(
fn,
instr,
elseBlockInstructions,
'type',
'react.fragment',
),
refProperty,
keyProperty,
propsProperty,
],
loc: instr.value.loc,
},
loc: instr.loc,
};
elseBlockInstructions.push(reactElementInstruction);
const reassignConditionalInstruction: Instruction = {
id: makeInstructionId(0),
lvalue: {...createTemporaryPlace(fn.env, instr.value.loc)},
value: {
kind: 'StoreLocal',
lvalue: {
place: {...elseVarPlace},
kind: InstructionKind.Reassign,
},
value: {...reactElementInstruction.lvalue},
type: null,
loc: instr.value.loc,
},
loc: instr.loc,
};
elseBlockInstructions.push(reassignConditionalInstruction);
/**
* Create phis to reassign the var
*/
const operands: Map<BlockId, Place> = new Map();
operands.set(thenBlockId, {
...elseVarPlace,
});
operands.set(elseBlockId, {
...thenVarPlace,
});
const phiIdentifier = forkTemporaryIdentifier(
fn.env.nextIdentifierId,
varPlace.identifier,
);
const phiPlace = {
...createTemporaryPlace(fn.env, instr.value.loc),
identifier: phiIdentifier,
};
const phis: Set<Phi> = new Set([
{
kind: 'Phi',
operands,
place: phiPlace,
},
]);
fallthroughBlock.phis = phis;
fn.body.blocks.set(fallthroughBlockId, fallthroughBlock);
/**
* Track this JSX instruction so we can replace references in step 2
*/
inlinedJsxDeclarations.set(instr.lvalue.identifier.declarationId, {
identifier: phiIdentifier,
blockIdsToIgnore: new Set([thenBlockId, elseBlockId]),
});
break;
}
case 'FunctionExpression':
case 'ObjectMethod': {
inlineJsxTransform(
instr.value.loweredFunc.func,
inlineJsxTransformConfig,
);
break;
}
}
}
}
/**
* Step 2: Replace declarations with new phi values
*/
for (const [blockId, block] of fn.body.blocks) {
for (const instr of block.instructions) {
mapInstructionOperands(instr, place =>
handlePlace(place, blockId, inlinedJsxDeclarations),
);
mapInstructionLValues(instr, lvalue =>
handlelValue(lvalue, blockId, inlinedJsxDeclarations),
);
mapInstructionValueOperands(instr.value, place =>
handlePlace(place, blockId, inlinedJsxDeclarations),
);
}
mapTerminalOperands(block.terminal, place =>
handlePlace(place, blockId, inlinedJsxDeclarations),
);
}
/**
* Step 3: Fixup the HIR
* Restore RPO, ensure correct predecessors, renumber instructions, fix scope and ranges.
*/
reversePostorderBlocks(fn.body);
markPredecessors(fn.body);
markInstructionIds(fn.body);
fixScopeAndIdentifierRanges(fn.body);
}
function createSymbolProperty(
fn: HIRFunction,
@ -315,123 +685,38 @@ function createPropsProperties(
return {refProperty, keyProperty, propsProperty};
}
// TODO: Make PROD only with conditional statements
export function inlineJsxTransform(
fn: HIRFunction,
inlineJsxTransformConfig: NonNullable<
EnvironmentConfig['inlineJsxTransform']
>,
): void {
for (const [, block] of fn.body.blocks) {
let nextInstructions: Array<Instruction> | null = null;
for (let i = 0; i < block.instructions.length; i++) {
const instr = block.instructions[i]!;
switch (instr.value.kind) {
case 'JsxExpression': {
nextInstructions ??= block.instructions.slice(0, i);
const {refProperty, keyProperty, propsProperty} =
createPropsProperties(
fn,
instr,
nextInstructions,
instr.value.props,
instr.value.children,
);
const reactElementInstruction: Instruction = {
id: makeInstructionId(0),
lvalue: {...instr.lvalue, effect: Effect.Store},
value: {
kind: 'ObjectExpression',
properties: [
createSymbolProperty(
fn,
instr,
nextInstructions,
'$$typeof',
inlineJsxTransformConfig.elementSymbol,
),
createTagProperty(fn, instr, nextInstructions, instr.value.tag),
refProperty,
keyProperty,
propsProperty,
],
loc: instr.value.loc,
},
loc: instr.loc,
};
nextInstructions.push(reactElementInstruction);
break;
}
case 'JsxFragment': {
nextInstructions ??= block.instructions.slice(0, i);
const {refProperty, keyProperty, propsProperty} =
createPropsProperties(
fn,
instr,
nextInstructions,
[],
instr.value.children,
);
const reactElementInstruction: Instruction = {
id: makeInstructionId(0),
lvalue: {...instr.lvalue, effect: Effect.Store},
value: {
kind: 'ObjectExpression',
properties: [
createSymbolProperty(
fn,
instr,
nextInstructions,
'$$typeof',
inlineJsxTransformConfig.elementSymbol,
),
createSymbolProperty(
fn,
instr,
nextInstructions,
'type',
'react.fragment',
),
refProperty,
keyProperty,
propsProperty,
],
loc: instr.value.loc,
},
loc: instr.loc,
};
nextInstructions.push(reactElementInstruction);
break;
}
case 'FunctionExpression':
case 'ObjectMethod': {
inlineJsxTransform(
instr.value.loweredFunc.func,
inlineJsxTransformConfig,
);
if (nextInstructions !== null) {
nextInstructions.push(instr);
}
break;
}
default: {
if (nextInstructions !== null) {
nextInstructions.push(instr);
}
}
}
}
if (nextInstructions !== null) {
block.instructions = nextInstructions;
}
function handlePlace(
place: Place,
blockId: BlockId,
inlinedJsxDeclarations: InlinedJsxDeclarationMap,
): Place {
const inlinedJsxDeclaration = inlinedJsxDeclarations.get(
place.identifier.declarationId,
);
if (
inlinedJsxDeclaration == null ||
inlinedJsxDeclaration.blockIdsToIgnore.has(blockId)
) {
return {...place};
}
// Fixup the HIR to restore RPO, ensure correct predecessors, and renumber instructions.
reversePostorderBlocks(fn.body);
markPredecessors(fn.body);
markInstructionIds(fn.body);
// The renumbering instructions invalidates scope and identifier ranges
fixScopeAndIdentifierRanges(fn.body);
return {...place, identifier: {...inlinedJsxDeclaration.identifier}};
}
function handlelValue(
lvalue: Place,
blockId: BlockId,
inlinedJsxDeclarations: InlinedJsxDeclarationMap,
): Place {
const inlinedJsxDeclaration = inlinedJsxDeclarations.get(
lvalue.identifier.declarationId,
);
if (
inlinedJsxDeclaration == null ||
inlinedJsxDeclaration.blockIdsToIgnore.has(blockId)
) {
return {...lvalue};
}
return {...lvalue, identifier: {...inlinedJsxDeclaration.identifier}};
}

View File

@ -50,6 +50,22 @@ function PropsSpread() {
);
}
function ConditionalJsx({shouldWrap}) {
let content = <div>Hello</div>;
if (shouldWrap) {
content = <Parent>{content}</Parent>;
}
return content;
}
// TODO: Support value blocks
function TernaryJsx({cond}) {
return cond ? <div /> : null;
}
global.DEV = true;
export const FIXTURE_ENTRYPOINT = {
fn: ParentAndChildren,
params: [{foo: 'abc'}],
@ -67,13 +83,17 @@ function Parent(t0) {
const { children, ref } = t0;
let t1;
if ($[0] !== children) {
t1 = {
$$typeof: Symbol.for("react.transitional.element"),
type: "div",
ref: ref,
key: null,
props: { children: children },
};
if (DEV) {
t1 = <div ref={ref}>{children}</div>;
} else {
t1 = {
$$typeof: Symbol.for("react.transitional.element"),
type: "div",
ref: ref,
key: null,
props: { children: children },
};
}
$[0] = children;
$[1] = t1;
} else {
@ -87,13 +107,17 @@ function Child(t0) {
const { children } = t0;
let t1;
if ($[0] !== children) {
t1 = {
$$typeof: Symbol.for("react.transitional.element"),
type: Symbol.for("react.fragment"),
ref: null,
key: null,
props: { children: children },
};
if (DEV) {
t1 = <>{children}</>;
} else {
t1 = {
$$typeof: Symbol.for("react.transitional.element"),
type: Symbol.for("react.fragment"),
ref: null,
key: null,
props: { children: children },
};
}
$[0] = children;
$[1] = t1;
} else {
@ -107,26 +131,34 @@ function GrandChild(t0) {
const { className } = t0;
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = {
$$typeof: Symbol.for("react.transitional.element"),
type: React.Fragment,
ref: null,
key: "fragmentKey",
props: { children: "Hello world" },
};
if (DEV) {
t1 = <React.Fragment key="fragmentKey">Hello world</React.Fragment>;
} else {
t1 = {
$$typeof: Symbol.for("react.transitional.element"),
type: React.Fragment,
ref: null,
key: "fragmentKey",
props: { children: "Hello world" },
};
}
$[0] = t1;
} else {
t1 = $[0];
}
let t2;
if ($[1] !== className) {
t2 = {
$$typeof: Symbol.for("react.transitional.element"),
type: "span",
ref: null,
key: null,
props: { className: className, children: t1 },
};
if (DEV) {
t2 = <span className={className}>{t1}</span>;
} else {
t2 = {
$$typeof: Symbol.for("react.transitional.element"),
type: "span",
ref: null,
key: null,
props: { className: className, children: t1 },
};
}
$[1] = className;
$[2] = t2;
} else {
@ -140,13 +172,17 @@ function ParentAndRefAndKey(props) {
const testRef = useRef();
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = {
$$typeof: Symbol.for("react.transitional.element"),
type: Parent,
ref: testRef,
key: "testKey",
props: { a: "a", b: { b: "b" }, c: C },
};
if (DEV) {
t0 = <Parent a="a" b={{ b: "b" }} c={C} key="testKey" ref={testRef} />;
} else {
t0 = {
$$typeof: Symbol.for("react.transitional.element"),
type: Parent,
ref: testRef,
key: "testKey",
props: { a: "a", b: { b: "b" }, c: C },
};
}
$[0] = t0;
} else {
t0 = $[0];
@ -158,13 +194,21 @@ function ParentAndChildren(props) {
const $ = _c2(14);
let t0;
if ($[0] !== props.foo) {
t0 = () => ({
$$typeof: Symbol.for("react.transitional.element"),
type: "div",
ref: null,
key: "d",
props: { children: props.foo },
});
t0 = () => {
let t1;
if (DEV) {
t1 = <div key="d">{props.foo}</div>;
} else {
t1 = {
$$typeof: Symbol.for("react.transitional.element"),
type: "div",
ref: null,
key: "d",
props: { children: props.foo },
};
}
return t1;
};
$[0] = props.foo;
$[1] = t0;
} else {
@ -173,71 +217,99 @@ function ParentAndChildren(props) {
const render = t0;
let t1;
if ($[2] !== props) {
t1 = {
$$typeof: Symbol.for("react.transitional.element"),
type: Child,
ref: null,
key: "a",
props: props,
};
if (DEV) {
t1 = <Child key="a" {...props} />;
} else {
t1 = {
$$typeof: Symbol.for("react.transitional.element"),
type: Child,
ref: null,
key: "a",
props: props,
};
}
$[2] = props;
$[3] = t1;
} else {
t1 = $[3];
}
let t2;
if ($[4] !== props) {
t2 = {
$$typeof: Symbol.for("react.transitional.element"),
type: GrandChild,
ref: null,
key: "c",
props: { className: props.foo, ...props },
};
$[4] = props;
$[5] = t2;
} else {
t2 = $[5];
}
const t2 = props.foo;
let t3;
if ($[6] !== render) {
t3 = render();
$[6] = render;
$[7] = t3;
if ($[4] !== props) {
if (DEV) {
t3 = <GrandChild key="c" className={t2} {...props} />;
} else {
t3 = {
$$typeof: Symbol.for("react.transitional.element"),
type: GrandChild,
ref: null,
key: "c",
props: { className: t2, ...props },
};
}
$[4] = props;
$[5] = t3;
} else {
t3 = $[7];
t3 = $[5];
}
let t4;
if ($[8] !== t2 || $[9] !== t3) {
t4 = {
$$typeof: Symbol.for("react.transitional.element"),
type: Child,
ref: null,
key: "b",
props: { children: [t2, t3] },
};
$[8] = t2;
$[9] = t3;
$[10] = t4;
if ($[6] !== render) {
t4 = render();
$[6] = render;
$[7] = t4;
} else {
t4 = $[10];
t4 = $[7];
}
let t5;
if ($[11] !== t1 || $[12] !== t4) {
t5 = {
$$typeof: Symbol.for("react.transitional.element"),
type: Parent,
ref: null,
key: null,
props: { children: [t1, t4] },
};
$[11] = t1;
$[12] = t4;
$[13] = t5;
if ($[8] !== t3 || $[9] !== t4) {
if (DEV) {
t5 = (
<Child key="b">
{t3}
{t4}
</Child>
);
} else {
t5 = {
$$typeof: Symbol.for("react.transitional.element"),
type: Child,
ref: null,
key: "b",
props: { children: [t3, t4] },
};
}
$[8] = t3;
$[9] = t4;
$[10] = t5;
} else {
t5 = $[13];
t5 = $[10];
}
return t5;
let t6;
if ($[11] !== t1 || $[12] !== t5) {
if (DEV) {
t6 = (
<Parent>
{t1}
{t5}
</Parent>
);
} else {
t6 = {
$$typeof: Symbol.for("react.transitional.element"),
type: Parent,
ref: null,
key: null,
props: { children: [t1, t5] },
};
}
$[11] = t1;
$[12] = t5;
$[13] = t6;
} else {
t6 = $[13];
}
return t6;
}
const propsToSpread = { a: "a", b: "b", c: "c" };
@ -245,30 +317,46 @@ function PropsSpread() {
const $ = _c2(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = {
$$typeof: Symbol.for("react.transitional.element"),
type: Symbol.for("react.fragment"),
ref: null,
key: null,
props: {
children: [
{
$$typeof: Symbol.for("react.transitional.element"),
type: Test,
ref: null,
key: "a",
props: propsToSpread,
},
{
$$typeof: Symbol.for("react.transitional.element"),
type: Test,
ref: null,
key: "b",
props: { ...propsToSpread, a: "z" },
},
],
},
};
let t1;
if (DEV) {
t1 = <Test key="a" {...propsToSpread} />;
} else {
t1 = {
$$typeof: Symbol.for("react.transitional.element"),
type: Test,
ref: null,
key: "a",
props: propsToSpread,
};
}
let t2;
if (DEV) {
t2 = <Test key="b" {...propsToSpread} a="z" />;
} else {
t2 = {
$$typeof: Symbol.for("react.transitional.element"),
type: Test,
ref: null,
key: "b",
props: { ...propsToSpread, a: "z" },
};
}
if (DEV) {
t0 = (
<>
{t1}
{t2}
</>
);
} else {
t0 = {
$$typeof: Symbol.for("react.transitional.element"),
type: Symbol.for("react.fragment"),
ref: null,
key: null,
props: { children: [t1, t2] },
};
}
$[0] = t0;
} else {
t0 = $[0];
@ -276,6 +364,67 @@ function PropsSpread() {
return t0;
}
function ConditionalJsx(t0) {
const $ = _c2(2);
const { shouldWrap } = t0;
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
if (DEV) {
t1 = <div>Hello</div>;
} else {
t1 = {
$$typeof: Symbol.for("react.transitional.element"),
type: "div",
ref: null,
key: null,
props: { children: "Hello" },
};
}
$[0] = t1;
} else {
t1 = $[0];
}
let content = t1;
if (shouldWrap) {
const t2 = content;
let t3;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
if (DEV) {
t3 = <Parent>{t2}</Parent>;
} else {
t3 = {
$$typeof: Symbol.for("react.transitional.element"),
type: Parent,
ref: null,
key: null,
props: { children: t2 },
};
}
$[1] = t3;
} else {
t3 = $[1];
}
content = t3;
}
return content;
}
// TODO: Support value blocks
function TernaryJsx(t0) {
const $ = _c2(2);
const { cond } = t0;
let t1;
if ($[0] !== cond) {
t1 = cond ? <div /> : null;
$[0] = cond;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
global.DEV = true;
export const FIXTURE_ENTRYPOINT = {
fn: ParentAndChildren,
params: [{ foo: "abc" }],

View File

@ -46,6 +46,22 @@ function PropsSpread() {
);
}
function ConditionalJsx({shouldWrap}) {
let content = <div>Hello</div>;
if (shouldWrap) {
content = <Parent>{content}</Parent>;
}
return content;
}
// TODO: Support value blocks
function TernaryJsx({cond}) {
return cond ? <div /> : null;
}
global.DEV = true;
export const FIXTURE_ENTRYPOINT = {
fn: ParentAndChildren,
params: [{foo: 'abc'}],

View File

@ -207,7 +207,10 @@ function makePluginOptions(
let inlineJsxTransform: EnvironmentConfig['inlineJsxTransform'] = null;
if (firstLine.includes('@enableInlineJsxTransform')) {
inlineJsxTransform = {elementSymbol: 'react.transitional.element'};
inlineJsxTransform = {
elementSymbol: 'react.transitional.element',
globalDevVar: 'DEV',
};
}
let logs: Array<{filename: string | null; event: LoggerEvent}> = [];