Skip to content

Commit 7bcd705

Browse files
authoredJun 17, 2022
Fix some performance by improving vdom diffing
Closes GH-2029. Closes GH-2062. Reviewed-by: Titus Wormer <tituswormer@gmail.com> Reviewed-by: Christian Murphy <christian.murphy.42@gmail.com>
1 parent ac44dc7 commit 7bcd705

File tree

4 files changed

+133
-94
lines changed

4 files changed

+133
-94
lines changed
 

‎packages/mdx/lib/plugin/recma-document.js

+53-41
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ export function recmaDocument(options = {}) {
231231
child.expression.type === 'JSXElement')
232232
) {
233233
content = true
234-
replacement.push(createMdxContent(child.expression))
234+
replacement.push(...createMdxContent(child.expression, Boolean(layout)))
235235
// The following catch-all branch is because plugins might’ve added
236236
// other things.
237237
// Normally, we only have import/export/jsx, but just add whatever’s
@@ -244,7 +244,7 @@ export function recmaDocument(options = {}) {
244244

245245
// If there was no JSX content at all, add an empty function.
246246
if (!content) {
247-
replacement.push(createMdxContent())
247+
replacement.push(...createMdxContent(undefined, Boolean(layout)))
248248
}
249249

250250
exportedIdentifiers.push(['MDXContent', 'default'])
@@ -481,9 +481,10 @@ export function recmaDocument(options = {}) {
481481

482482
/**
483483
* @param {Expression} [content]
484-
* @returns {FunctionDeclaration}
484+
* @param {boolean} [hasInternalLayout]
485+
* @returns {FunctionDeclaration[]}
485486
*/
486-
function createMdxContent(content) {
487+
function createMdxContent(content, hasInternalLayout) {
487488
/** @type {JSXElement} */
488489
const element = {
489490
type: 'JSXElement',
@@ -508,7 +509,12 @@ export function recmaDocument(options = {}) {
508509
openingElement: {
509510
type: 'JSXOpeningElement',
510511
name: {type: 'JSXIdentifier', name: '_createMdxContent'},
511-
attributes: [],
512+
attributes: [
513+
{
514+
type: 'JSXSpreadAttribute',
515+
argument: {type: 'Identifier', name: 'props'}
516+
}
517+
],
512518
selfClosing: true
513519
},
514520
closingElement: null,
@@ -518,7 +524,21 @@ export function recmaDocument(options = {}) {
518524
}
519525

520526
// @ts-expect-error: JSXElements are expressions.
521-
const consequent = /** @type {Expression} */ (element)
527+
let result = /** @type {Expression} */ (element)
528+
529+
if (!hasInternalLayout) {
530+
result = {
531+
type: 'ConditionalExpression',
532+
test: {type: 'Identifier', name: 'MDXLayout'},
533+
consequent: result,
534+
alternate: {
535+
type: 'CallExpression',
536+
callee: {type: 'Identifier', name: '_createMdxContent'},
537+
arguments: [{type: 'Identifier', name: 'props'}],
538+
optional: false
539+
}
540+
}
541+
}
522542

523543
let argument = content || {type: 'Literal', value: null}
524544

@@ -535,44 +555,36 @@ export function recmaDocument(options = {}) {
535555
argument = argument.children[0]
536556
}
537557

538-
return {
539-
type: 'FunctionDeclaration',
540-
id: {type: 'Identifier', name: 'MDXContent'},
541-
params: [
542-
{
543-
type: 'AssignmentPattern',
544-
left: {type: 'Identifier', name: 'props'},
545-
right: {type: 'ObjectExpression', properties: []}
558+
return [
559+
{
560+
type: 'FunctionDeclaration',
561+
id: {type: 'Identifier', name: '_createMdxContent'},
562+
params: [{type: 'Identifier', name: 'props'}],
563+
body: {
564+
type: 'BlockStatement',
565+
body: [{type: 'ReturnStatement', argument}]
546566
}
547-
],
548-
body: {
549-
type: 'BlockStatement',
550-
body: [
551-
{
552-
type: 'ReturnStatement',
553-
argument: {
554-
type: 'ConditionalExpression',
555-
test: {type: 'Identifier', name: 'MDXLayout'},
556-
consequent,
557-
alternate: {
558-
type: 'CallExpression',
559-
callee: {type: 'Identifier', name: '_createMdxContent'},
560-
arguments: [],
561-
optional: false
562-
}
563-
}
564-
},
567+
},
568+
{
569+
type: 'FunctionDeclaration',
570+
id: {type: 'Identifier', name: 'MDXContent'},
571+
params: [
565572
{
566-
type: 'FunctionDeclaration',
567-
id: {type: 'Identifier', name: '_createMdxContent'},
568-
params: [],
569-
body: {
570-
type: 'BlockStatement',
571-
body: [{type: 'ReturnStatement', argument}]
572-
}
573+
type: 'AssignmentPattern',
574+
left: {type: 'Identifier', name: 'props'},
575+
right: {type: 'ObjectExpression', properties: []}
573576
}
574-
]
577+
],
578+
body: {
579+
type: 'BlockStatement',
580+
body: [
581+
{
582+
type: 'ReturnStatement',
583+
argument: result
584+
}
585+
]
586+
}
575587
}
576-
}
588+
]
577589
}
578590
}

‎packages/mdx/lib/plugin/recma-jsx-rewrite.js

+15-15
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ export function recmaJsxRewrite(options = {}) {
7676
walk(tree, {
7777
enter(_node) {
7878
const node = /** @type {Node} */ (_node)
79+
const newScope = /** @type {Scope|undefined} */ (
80+
// @ts-expect-error: periscopic doesn’t support JSX.
81+
scopeInfo.map.get(node)
82+
)
7983

8084
if (
8185
node.type === 'FunctionDeclaration' ||
@@ -89,30 +93,26 @@ export function recmaJsxRewrite(options = {}) {
8993
references: {},
9094
node
9195
})
92-
}
9396

94-
let fnScope = fnStack[0]
97+
// MDXContent only ever contains MDXLayout
98+
if (
99+
isNamedFunction(node, 'MDXContent') &&
100+
newScope &&
101+
!inScope(newScope, 'MDXLayout')
102+
) {
103+
fnStack[0].components.push('MDXLayout')
104+
}
105+
}
95106

107+
const fnScope = fnStack[0]
96108
if (
97109
!fnScope ||
98-
(!isNamedFunction(fnScope.node, 'MDXContent') &&
110+
(!isNamedFunction(fnScope.node, '_createMdxContent') &&
99111
!providerImportSource)
100112
) {
101113
return
102114
}
103115

104-
if (
105-
fnStack[1] &&
106-
isNamedFunction(fnStack[1].node, '_createMdxContent')
107-
) {
108-
fnScope = fnStack[1]
109-
}
110-
111-
const newScope = /** @type {Scope|undefined} */ (
112-
// @ts-expect-error: periscopic doesn’t support JSX.
113-
scopeInfo.map.get(node)
114-
)
115-
116116
if (newScope) {
117117
newScope.node = node
118118
currentScope = newScope

‎packages/mdx/test/compile.js

+64-37
Original file line numberDiff line numberDiff line change
@@ -778,16 +778,16 @@ test('jsx', async () => {
778778
String(compileSync('*a*', {jsx: true})),
779779
[
780780
'/*@jsxRuntime automatic @jsxImportSource react*/',
781+
'function _createMdxContent(props) {',
782+
' const _components = Object.assign({',
783+
' p: "p",',
784+
' em: "em"',
785+
' }, props.components);',
786+
' return <_components.p><_components.em>{"a"}</_components.em></_components.p>;',
787+
'}',
781788
'function MDXContent(props = {}) {',
782789
' const {wrapper: MDXLayout} = props.components || ({});',
783-
' return MDXLayout ? <MDXLayout {...props}><_createMdxContent /></MDXLayout> : _createMdxContent();',
784-
' function _createMdxContent() {',
785-
' const _components = Object.assign({',
786-
' p: "p",',
787-
' em: "em"',
788-
' }, props.components);',
789-
' return <_components.p><_components.em>{"a"}</_components.em></_components.p>;',
790-
' }',
790+
' return MDXLayout ? <MDXLayout {...props}><_createMdxContent {...props} /></MDXLayout> : _createMdxContent(props);',
791791
'}',
792792
'export default MDXContent;',
793793
''
@@ -799,12 +799,12 @@ test('jsx', async () => {
799799
String(compileSync('<a {...b} c d="1" e={1} />', {jsx: true})),
800800
[
801801
'/*@jsxRuntime automatic @jsxImportSource react*/',
802+
'function _createMdxContent(props) {',
803+
' return <a {...b} c d="1" e={1} />;',
804+
'}',
802805
'function MDXContent(props = {}) {',
803806
' const {wrapper: MDXLayout} = props.components || ({});',
804-
' return MDXLayout ? <MDXLayout {...props}><_createMdxContent /></MDXLayout> : _createMdxContent();',
805-
' function _createMdxContent() {',
806-
' return <a {...b} c d="1" e={1} />;',
807-
' }',
807+
' return MDXLayout ? <MDXLayout {...props}><_createMdxContent {...props} /></MDXLayout> : _createMdxContent(props);',
808808
'}',
809809
'export default MDXContent;',
810810
''
@@ -816,15 +816,15 @@ test('jsx', async () => {
816816
String(compileSync('<><a:b /><c.d/></>', {jsx: true})),
817817
[
818818
'/*@jsxRuntime automatic @jsxImportSource react*/',
819+
'function _createMdxContent(props) {',
820+
' const {c} = props.components || ({});',
821+
' if (!c) _missingMdxReference("c", false);',
822+
' if (!c.d) _missingMdxReference("c.d", true);',
823+
' return <><><a:b /><c.d /></></>;',
824+
'}',
819825
'function MDXContent(props = {}) {',
820826
' const {wrapper: MDXLayout} = props.components || ({});',
821-
' return MDXLayout ? <MDXLayout {...props}><_createMdxContent /></MDXLayout> : _createMdxContent();',
822-
' function _createMdxContent() {',
823-
' const {c} = props.components || ({});',
824-
' if (!c) _missingMdxReference("c", false);',
825-
' if (!c.d) _missingMdxReference("c.d", true);',
826-
' return <><><a:b /><c.d /></></>;',
827-
' }',
827+
' return MDXLayout ? <MDXLayout {...props}><_createMdxContent {...props} /></MDXLayout> : _createMdxContent(props);',
828828
'}',
829829
'export default MDXContent;',
830830
'function _missingMdxReference(id, component) {',
@@ -840,12 +840,12 @@ test('jsx', async () => {
840840
[
841841
'/*@jsxRuntime automatic @jsxImportSource react*/',
842842
'/*1*/',
843+
'function _createMdxContent(props) {',
844+
' return <><>{"a "}{}{" b"}</></>;',
845+
'}',
843846
'function MDXContent(props = {}) {',
844847
' const {wrapper: MDXLayout} = props.components || ({});',
845-
' return MDXLayout ? <MDXLayout {...props}><_createMdxContent /></MDXLayout> : _createMdxContent();',
846-
' function _createMdxContent() {',
847-
' return <><>{"a "}{}{" b"}</></>;',
848-
' }',
848+
' return MDXLayout ? <MDXLayout {...props}><_createMdxContent {...props} /></MDXLayout> : _createMdxContent(props);',
849849
'}',
850850
'export default MDXContent;',
851851
''
@@ -857,15 +857,15 @@ test('jsx', async () => {
857857
String(compileSync('{<a-b></a-b>}', {jsx: true})),
858858
[
859859
'/*@jsxRuntime automatic @jsxImportSource react*/',
860+
'function _createMdxContent(props) {',
861+
' const _components = Object.assign({',
862+
' "a-b": "a-b"',
863+
' }, props.components);',
864+
' return <>{<_components.a-b></_components.a-b>}</>;',
865+
'}',
860866
'function MDXContent(props = {}) {',
861867
' const {wrapper: MDXLayout} = props.components || ({});',
862-
' return MDXLayout ? <MDXLayout {...props}><_createMdxContent /></MDXLayout> : _createMdxContent();',
863-
' function _createMdxContent() {',
864-
' const _components = Object.assign({',
865-
' "a-b": "a-b"',
866-
' }, props.components);',
867-
' return <>{<_components.a-b></_components.a-b>}</>;',
868-
' }',
868+
' return MDXLayout ? <MDXLayout {...props}><_createMdxContent {...props} /></MDXLayout> : _createMdxContent(props);',
869869
'}',
870870
'export default MDXContent;',
871871
''
@@ -877,22 +877,49 @@ test('jsx', async () => {
877877
String(compileSync('Hello {props.name}', {jsx: true})),
878878
[
879879
'/*@jsxRuntime automatic @jsxImportSource react*/',
880+
'function _createMdxContent(props) {',
881+
' const _components = Object.assign({',
882+
' p: "p"',
883+
' }, props.components);',
884+
' return <_components.p>{"Hello "}{props.name}</_components.p>;',
885+
'}',
880886
'function MDXContent(props = {}) {',
881887
' const {wrapper: MDXLayout} = props.components || ({});',
882-
' return MDXLayout ? <MDXLayout {...props}><_createMdxContent /></MDXLayout> : _createMdxContent();',
883-
' function _createMdxContent() {',
884-
' const _components = Object.assign({',
885-
' p: "p"',
886-
' }, props.components);',
887-
' return <_components.p>{"Hello "}{props.name}</_components.p>;',
888-
' }',
888+
' return MDXLayout ? <MDXLayout {...props}><_createMdxContent {...props} /></MDXLayout> : _createMdxContent(props);',
889889
'}',
890890
'export default MDXContent;',
891891
''
892892
].join('\n'),
893893
'should allow using props'
894894
)
895895

896+
assert.equal(
897+
String(
898+
compileSync(
899+
'export default function Layout({components, ...props}) { return <section {...props} /> }\n\na',
900+
{jsx: true}
901+
)
902+
),
903+
[
904+
'/*@jsxRuntime automatic @jsxImportSource react*/',
905+
'const MDXLayout = function Layout({components, ...props}) {',
906+
' return <section {...props} />;',
907+
'};',
908+
'function _createMdxContent(props) {',
909+
' const _components = Object.assign({',
910+
' p: "p"',
911+
' }, props.components);',
912+
' return <_components.p>{"a"}</_components.p>;',
913+
'}',
914+
'function MDXContent(props = {}) {',
915+
' return <MDXLayout {...props}><_createMdxContent {...props} /></MDXLayout>;',
916+
'}',
917+
'export default MDXContent;',
918+
''
919+
].join('\n'),
920+
'should not have a conditional expression for MDXLayout when there is an internal layout'
921+
)
922+
896923
assert.match(
897924
String(compileSync("{<w x='y \" z' />}", {jsx: true})),
898925
/x="y &quot; z"/,

‎packages/rollup/test/index.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ test('@mdx-js/rollup', async () => {
3434

3535
assert.equal(
3636
output[0].map ? output[0].map.mappings : undefined,
37-
';;;MAAaA,OAAU,GAAA,MAAAC,GAAA,CAAAC,QAAA,EAAA;AAAQ,EAAA,QAAA,EAAA,QAAA;;;;;;;;;;;;AAE7B,MAAA,QAAA,EAAA,CAAA,SAAA,EAAAD,GAAA,CAAA,OAAA,EAAA,EAAA,CAAA,CAAA;;;;;;;',
37+
';;;MAAaA,OAAU,GAAA,MAAAC,GAAA,CAAAC,QAAA,EAAA;AAAQ,EAAA,QAAA,EAAA,QAAA;;;;;;;AAE7B,IAAA,QAAA,EAAA,CAAA,SAAA,EAAAD,GAAA,CAAA,OAAA,EAAA,EAAA,CAAA,CAAA;;;;;;;;;;;;',
3838
'should add a source map'
3939
)
4040

1 commit comments

Comments
 (1)

vercel[bot] commented on Jun 17, 2022

@vercel[bot]

Successfully deployed to the following URLs:

mdx – ./

mdxjs.com
mdx-mdx.vercel.app
mdx-git-main-mdx.vercel.app
v2.mdxjs.com

Please sign in to comment.