Skip to content

Commit 1aa7f10

Browse files
committed
Optimize the transformed output of JSXSpreadAttributes containing an ObjectLiteralExpression
1 parent 3c87170 commit 1aa7f10

8 files changed

+66
-40
lines changed

src/compiler/sys.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1381,7 +1381,8 @@ namespace ts {
13811381
disableCPUProfiler,
13821382
cpuProfilingEnabled: () => !!activeSession || contains(process.execArgv, "--cpu-prof") || contains(process.execArgv, "--prof"),
13831383
realpath,
1384-
debugMode: !!process.env.NODE_INSPECTOR_IPC || !!process.env.VSCODE_INSPECTOR_OPTIONS || some(process.execArgv as string[], arg => /^--(inspect|debug)(-brk)?(=\d+)?$/i.test(arg)),
1384+
// debugMode: !!process.env.NODE_INSPECTOR_IPC || !!process.env.VSCODE_INSPECTOR_OPTIONS || some(process.execArgv as string[], arg => /^--(inspect|debug)(-brk)?(=\d+)?$/i.test(arg)),
1385+
debugMode: true,
13851386
tryEnableSourceMapsForHost() {
13861387
try {
13871388
require("source-map-support").install();

src/compiler/transformers/jsx.ts

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ namespace ts {
170170
function hasKeyAfterPropsSpread(node: JsxOpeningLikeElement) {
171171
let spread = false;
172172
for (const elem of node.attributes.properties) {
173-
if (isJsxSpreadAttribute(elem)) {
173+
if (isJsxSpreadAttribute(elem) && (!isObjectLiteralExpression(elem.expression) || elem.expression.properties.some(isSpreadAssignment))) {
174174
spread = true;
175175
}
176176
else if (spread && isJsxAttribute(elem) && elem.name.escapedText === "key") {
@@ -348,7 +348,10 @@ namespace ts {
348348
return element;
349349
}
350350

351-
function transformJsxSpreadAttributeToSpreadAssignment(node: JsxSpreadAttribute) {
351+
function transformJsxSpreadAttributeToProps(node: JsxSpreadAttribute) {
352+
if (isObjectLiteralExpression(node.expression)) {
353+
return node.expression.properties;
354+
}
352355
return factory.createSpreadAssignment(visitNode(node.expression, visitor, isExpression));
353356
}
354357

@@ -359,39 +362,61 @@ namespace ts {
359362
}
360363

361364
function transformJsxAttributesToProps(attrs: readonly(JsxSpreadAttribute | JsxAttribute)[], children?: PropertyAssignment) {
362-
const props = flatten<SpreadAssignment | PropertyAssignment>(spanMap(attrs, isJsxSpreadAttribute, (attrs, isSpread) =>
363-
map(attrs, attr => isSpread ? transformJsxSpreadAttributeToSpreadAssignment(attr as JsxSpreadAttribute) : transformJsxAttributeToObjectLiteralElement(attr as JsxAttribute))));
365+
const props = flatten(spanMap(attrs, isJsxSpreadAttribute, (attrs, isSpread) =>
366+
flatten(map(attrs, attr => isSpread ? transformJsxSpreadAttributeToProps(attr as JsxSpreadAttribute) : transformJsxAttributeToObjectLiteralElement(attr as JsxAttribute)))));
364367
if (children) {
365368
props.push(children);
366369
}
367370
return props;
368371
}
369372

370373
function transformJsxAttributesToExpression(attrs: readonly(JsxSpreadAttribute | JsxAttribute)[], children?: PropertyAssignment) {
371-
// Map spans of JsxAttribute nodes into object literals and spans
372-
// of JsxSpreadAttribute nodes into expressions.
373-
const expressions = flatten(
374-
spanMap(attrs, isJsxSpreadAttribute, (attrs, isSpread) => isSpread
375-
? map(attrs, transformJsxSpreadAttributeToExpression)
376-
: factory.createObjectLiteralExpression(map(attrs, transformJsxAttributeToObjectLiteralElement))
377-
)
378-
);
379-
380-
if (isJsxSpreadAttribute(attrs[0])) {
381-
// We must always emit at least one object literal before a spread
382-
// argument.factory.createObjectLiteral
383-
expressions.unshift(factory.createObjectLiteralExpression());
374+
const expressions: Expression[] = [];
375+
let properties: ObjectLiteralElementLike[] = [];
376+
377+
for (const attr of attrs) {
378+
if (isJsxSpreadAttribute(attr)) {
379+
// as an optimization we try to flatten the first level of spread inline object
380+
// as if its props would be passed as JSX attributes
381+
if (isObjectLiteralExpression(attr.expression)) {
382+
for (const prop of attr.expression.properties) {
383+
if (isSpreadAssignment(prop)) {
384+
finishObjectLiteralIfNeeded();
385+
expressions.push(prop.expression);
386+
continue;
387+
}
388+
properties.push(prop);
389+
}
390+
continue;
391+
}
392+
finishObjectLiteralIfNeeded();
393+
expressions.push(attr.expression);
394+
continue;
395+
}
396+
properties.push(transformJsxAttributeToObjectLiteralElement(attr));
384397
}
385398

386399
if (children) {
387-
expressions.push(factory.createObjectLiteralExpression([children]));
400+
properties.push(children);
401+
}
402+
403+
finishObjectLiteralIfNeeded();
404+
405+
if (expressions.length && !isObjectLiteralExpression(expressions[0])) {
406+
// We must always emit at least one object literal before a spread attribute
407+
// as the JSX always factory expects a fresh object, so we need to make a copy here
408+
// we also avoid mutating an external reference by doing this (first expression is used as assign's target)
409+
expressions.unshift(factory.createObjectLiteralExpression());
388410
}
389411

390412
return singleOrUndefined(expressions) || emitHelpers().createAssignHelper(expressions);
391-
}
392413

393-
function transformJsxSpreadAttributeToExpression(node: JsxSpreadAttribute) {
394-
return visitNode(node.expression, visitor, isExpression);
414+
function finishObjectLiteralIfNeeded() {
415+
if (properties.length) {
416+
expressions.push(factory.createObjectLiteralExpression(properties));
417+
properties = [];
418+
}
419+
}
395420
}
396421

397422
function transformJsxAttributeToObjectLiteralElement(node: JsxAttribute) {

tests/baselines/reference/tsxEmitSpreadAttribute(target=es2015).js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ export function T3(a, b) {
3737
return React.createElement("div", Object.assign({}, a, { className: "T3" }, b), "T3");
3838
}
3939
export function T4(a, b) {
40-
return React.createElement("div", Object.assign({ className: "T4" }, Object.assign(Object.assign({}, a), b)), "T4");
40+
return React.createElement("div", Object.assign({ className: "T4" }, a, b), "T4");
4141
}
4242
export function T5(a, b, c, d) {
43-
return React.createElement("div", Object.assign({ className: "T5" }, Object.assign(Object.assign(Object.assign({}, a), b), { c, d })), "T5");
43+
return React.createElement("div", Object.assign({ className: "T5" }, a, b, { c, d }), "T5");
4444
}
4545
export function T6(a, b, c, d) {
46-
return React.createElement("div", Object.assign({ className: "T6" }, Object.assign(Object.assign(Object.assign({}, a), b), Object.assign(Object.assign({}, c), d))), "T6");
46+
return React.createElement("div", Object.assign({ className: "T6" }, a, b, Object.assign(Object.assign({}, c), d)), "T6");
4747
}

tests/baselines/reference/tsxEmitSpreadAttribute(target=es2018).js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ export function T3(a, b) {
3737
return React.createElement("div", { ...a, className: "T3", ...b }, "T3");
3838
}
3939
export function T4(a, b) {
40-
return React.createElement("div", { className: "T4", ...{ ...a, ...b } }, "T4");
40+
return React.createElement("div", { className: "T4", ...a, ...b }, "T4");
4141
}
4242
export function T5(a, b, c, d) {
43-
return React.createElement("div", { className: "T5", ...{ ...a, ...b, ...{ c, d } } }, "T5");
43+
return React.createElement("div", { className: "T5", ...a, ...b, ...{ c, d } }, "T5");
4444
}
4545
export function T6(a, b, c, d) {
46-
return React.createElement("div", { className: "T6", ...{ ...a, ...b, ...{ ...c, ...d } } }, "T6");
46+
return React.createElement("div", { className: "T6", ...a, ...b, ...{ ...c, ...d } }, "T6");
4747
}

tests/baselines/reference/tsxEmitSpreadAttribute(target=esnext).js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ export function T3(a, b) {
3737
return React.createElement("div", { ...a, className: "T3", ...b }, "T3");
3838
}
3939
export function T4(a, b) {
40-
return React.createElement("div", { className: "T4", ...{ ...a, ...b } }, "T4");
40+
return React.createElement("div", { className: "T4", ...a, ...b }, "T4");
4141
}
4242
export function T5(a, b, c, d) {
43-
return React.createElement("div", { className: "T5", ...{ ...a, ...b, ...{ c, d } } }, "T5");
43+
return React.createElement("div", { className: "T5", ...a, ...b, ...{ c, d } }, "T5");
4444
}
4545
export function T6(a, b, c, d) {
46-
return React.createElement("div", { className: "T6", ...{ ...a, ...b, ...{ ...c, ...d } } }, "T6");
46+
return React.createElement("div", { className: "T6", ...a, ...b, ...{ ...c, ...d } }, "T6");
4747
}

tests/baselines/reference/tsxReactEmitSpreadAttribute(target=es2015).js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,13 @@ export function T3(a, b) {
4343
return _jsx("div", Object.assign({}, a, { className: "T3" }, b, { children: "T3" }));
4444
}
4545
export function T4(a, b) {
46-
return _jsx("div", Object.assign({ className: "T4" }, Object.assign(Object.assign({}, a), b), { children: "T4" }));
46+
return _jsx("div", Object.assign({ className: "T4" }, a, b, { children: "T4" }));
4747
}
4848
export function T5(a, b, c, d) {
49-
return _jsx("div", Object.assign({ className: "T5" }, Object.assign(Object.assign(Object.assign({}, a), b), { c, d }), { children: "T5" }));
49+
return _jsx("div", Object.assign({ className: "T5" }, a, b, { c, d }, { children: "T5" }));
5050
}
5151
export function T6(a, b, c, d) {
52-
return _jsx("div", Object.assign({ className: "T6" }, Object.assign(Object.assign(Object.assign({}, a), b), Object.assign(Object.assign({}, c), d)), { children: "T6" }));
52+
return _jsx("div", Object.assign({ className: "T6" }, a, b, Object.assign(Object.assign({}, c), d), { children: "T6" }));
5353
}
5454
export function T7(a, b, c, d) {
5555
return _jsx("div", { children: "T7" });

tests/baselines/reference/tsxReactEmitSpreadAttribute(target=es2018).js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,13 @@ export function T3(a, b) {
4343
return _jsx("div", { ...a, className: "T3", ...b, children: "T3" });
4444
}
4545
export function T4(a, b) {
46-
return _jsx("div", { className: "T4", ...{ ...a, ...b }, children: "T4" });
46+
return _jsx("div", { className: "T4", ...a, ...b, children: "T4" });
4747
}
4848
export function T5(a, b, c, d) {
49-
return _jsx("div", { className: "T5", ...{ ...a, ...b, ...{ c, d } }, children: "T5" });
49+
return _jsx("div", { className: "T5", ...a, ...b, ...{ c, d }, children: "T5" });
5050
}
5151
export function T6(a, b, c, d) {
52-
return _jsx("div", { className: "T6", ...{ ...a, ...b, ...{ ...c, ...d } }, children: "T6" });
52+
return _jsx("div", { className: "T6", ...a, ...b, ...{ ...c, ...d }, children: "T6" });
5353
}
5454
export function T7(a, b, c, d) {
5555
return _jsx("div", { children: "T7" });

tests/baselines/reference/tsxReactEmitSpreadAttribute(target=esnext).js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,13 @@ export function T3(a, b) {
4343
return _jsx("div", { ...a, className: "T3", ...b, children: "T3" });
4444
}
4545
export function T4(a, b) {
46-
return _jsx("div", { className: "T4", ...{ ...a, ...b }, children: "T4" });
46+
return _jsx("div", { className: "T4", ...a, ...b, children: "T4" });
4747
}
4848
export function T5(a, b, c, d) {
49-
return _jsx("div", { className: "T5", ...{ ...a, ...b, ...{ c, d } }, children: "T5" });
49+
return _jsx("div", { className: "T5", ...a, ...b, ...{ c, d }, children: "T5" });
5050
}
5151
export function T6(a, b, c, d) {
52-
return _jsx("div", { className: "T6", ...{ ...a, ...b, ...{ ...c, ...d } }, children: "T6" });
52+
return _jsx("div", { className: "T6", ...a, ...b, ...{ ...c, ...d }, children: "T6" });
5353
}
5454
export function T7(a, b, c, d) {
5555
return _jsx("div", { children: "T7" });

0 commit comments

Comments
 (0)