Skip to content

Commit

Permalink
[compiler] Pass through unmodified props spread when inlining jsx (#3…
Browse files Browse the repository at this point in the history
…0995)

If JSX receives a props spread without additional attributes (besides
`ref` and `key`), we can pass the spread object as a property directly
to avoid the extra object copy.

```
<Test {...propsToSpread} />
// {props: propsToSpread}
<Test {...propsToSpread} a="z" />
// {props: {...propsToSpread, a: "z"}}
```
  • Loading branch information
jackpope committed Sep 19, 2024
1 parent a86afe8 commit d5e955d
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
markPredecessors,
reversePostorderBlocks,
} from '../HIR/HIRBuilder';
import {CompilerError} from '..';

function createSymbolProperty(
fn: HIRFunction,
Expand Down Expand Up @@ -151,6 +152,16 @@ function createPropsProperties(
let refProperty: ObjectProperty | undefined;
let keyProperty: ObjectProperty | undefined;
const props: Array<ObjectProperty | SpreadPattern> = [];
const jsxAttributesWithoutKeyAndRef = propAttributes.filter(
p => p.kind === 'JsxAttribute' && p.name !== 'key' && p.name !== 'ref',
);
const jsxSpreadAttributes = propAttributes.filter(
p => p.kind === 'JsxSpreadAttribute',
);
const spreadPropsOnly =
jsxAttributesWithoutKeyAndRef.length === 0 &&
jsxSpreadAttributes.length === 1;

propAttributes.forEach(prop => {
switch (prop.kind) {
case 'JsxAttribute': {
Expand Down Expand Up @@ -180,7 +191,6 @@ function createPropsProperties(
break;
}
case 'JsxSpreadAttribute': {
// TODO: Optimize spreads to pass object directly if none of its properties are mutated
props.push({
kind: 'Spread',
place: {...prop.argument},
Expand All @@ -189,6 +199,7 @@ function createPropsProperties(
}
}
});

const propsPropertyPlace = createTemporaryPlace(fn.env, instr.value.loc);
if (children) {
let childrenPropProperty: ObjectProperty;
Expand Down Expand Up @@ -268,23 +279,39 @@ function createPropsProperties(
nextInstructions.push(keyInstruction);
}

const propsInstruction: Instruction = {
id: makeInstructionId(0),
lvalue: {...propsPropertyPlace, effect: Effect.Mutate},
value: {
kind: 'ObjectExpression',
properties: props,
loc: instr.value.loc,
},
loc: instr.loc,
};
const propsProperty: ObjectProperty = {
kind: 'ObjectProperty',
key: {name: 'props', kind: 'string'},
type: 'property',
place: {...propsPropertyPlace, effect: Effect.Capture},
};
nextInstructions.push(propsInstruction);
let propsProperty: ObjectProperty;
if (spreadPropsOnly) {
const spreadProp = jsxSpreadAttributes[0];
CompilerError.invariant(spreadProp.kind === 'JsxSpreadAttribute', {
reason: 'Spread prop attribute must be of kind JSXSpreadAttribute',
loc: instr.loc,
});
propsProperty = {
kind: 'ObjectProperty',
key: {name: 'props', kind: 'string'},
type: 'property',
place: {...spreadProp.argument, effect: Effect.Mutate},
};
} else {
const propsInstruction: Instruction = {
id: makeInstructionId(0),
lvalue: {...propsPropertyPlace, effect: Effect.Mutate},
value: {
kind: 'ObjectExpression',
properties: props,
loc: instr.value.loc,
},
loc: instr.loc,
};
propsProperty = {
kind: 'ObjectProperty',
key: {name: 'props', kind: 'string'},
type: 'property',
place: {...propsPropertyPlace, effect: Effect.Capture},
};
nextInstructions.push(propsInstruction);
}

return {refProperty, keyProperty, propsProperty};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,22 @@ function ParentAndRefAndKey(props) {
function ParentAndChildren(props) {
return (
<Parent>
<Child key="a" />
<Child key="a" {...props} />
<Child key="b">
<GrandChild className={props.foo} />
<GrandChild className={props.foo} {...props} />
</Child>
</Parent>
);
}

const propsToSpread = {a: 'a', b: 'b', c: 'c'};
function PropsSpread() {
return <Test {...propsToSpread} />;
return (
<>
<Test {...propsToSpread} />
<Test {...propsToSpread} a="z" />
</>
);
}

export const FIXTURE_ENTRYPOINT = {
Expand Down Expand Up @@ -146,54 +151,59 @@ function ParentAndRefAndKey(props) {
}

function ParentAndChildren(props) {
const $ = _c2(3);
const $ = _c2(7);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
if ($[0] !== props) {
t0 = {
$$typeof: Symbol.for("react.transitional.element"),
type: Child,
ref: null,
key: "a",
props: {},
props: props,
};
$[0] = t0;
$[0] = props;
$[1] = t0;
} else {
t0 = $[0];
t0 = $[1];
}
let t1;
if ($[1] !== props.foo) {
if ($[2] !== props) {
t1 = {
$$typeof: Symbol.for("react.transitional.element"),
type: Parent,
type: Child,
ref: null,
key: null,
key: "b",
props: {
children: [
t0,
{
$$typeof: Symbol.for("react.transitional.element"),
type: Child,
ref: null,
key: "b",
props: {
children: {
$$typeof: Symbol.for("react.transitional.element"),
type: GrandChild,
ref: null,
key: null,
props: { className: props.foo },
},
},
},
],
children: {
$$typeof: Symbol.for("react.transitional.element"),
type: GrandChild,
ref: null,
key: null,
props: { className: props.foo, ...props },
},
},
};
$[1] = props.foo;
$[2] = t1;
$[2] = props;
$[3] = t1;
} else {
t1 = $[2];
t1 = $[3];
}
return t1;
let t2;
if ($[4] !== t0 || $[5] !== t1) {
t2 = {
$$typeof: Symbol.for("react.transitional.element"),
type: Parent,
ref: null,
key: null,
props: { children: [t0, t1] },
};
$[4] = t0;
$[5] = t1;
$[6] = t2;
} else {
t2 = $[6];
}
return t2;
}

const propsToSpread = { a: "a", b: "b", c: "c" };
Expand All @@ -203,10 +213,27 @@ function PropsSpread() {
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = {
$$typeof: Symbol.for("react.transitional.element"),
type: Test,
type: Symbol.for("react.fragment"),
ref: null,
key: null,
props: { ...propsToSpread },
props: {
children: [
{
$$typeof: Symbol.for("react.transitional.element"),
type: Test,
ref: null,
key: null,
props: propsToSpread,
},
{
$$typeof: Symbol.for("react.transitional.element"),
type: Test,
ref: null,
key: null,
props: { ...propsToSpread, a: "z" },
},
],
},
};
$[0] = t0;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,22 @@ function ParentAndRefAndKey(props) {
function ParentAndChildren(props) {
return (
<Parent>
<Child key="a" />
<Child key="a" {...props} />
<Child key="b">
<GrandChild className={props.foo} />
<GrandChild className={props.foo} {...props} />
</Child>
</Parent>
);
}

const propsToSpread = {a: 'a', b: 'b', c: 'c'};
function PropsSpread() {
return <Test {...propsToSpread} />;
return (
<>
<Test {...propsToSpread} />
<Test {...propsToSpread} a="z" />
</>
);
}

export const FIXTURE_ENTRYPOINT = {
Expand Down

0 comments on commit d5e955d

Please sign in to comment.