From 5ef0558690bf7bbc5c7f2c520af0cc9753df1269 Mon Sep 17 00:00:00 2001 From: 0phoff <0phoff@users.noreply.github.com> Date: Mon, 13 Jun 2022 18:40:02 +0200 Subject: [PATCH 1/5] Create the _mdxMdxContent component outside of the MDXContent component --- packages/mdx/lib/plugin/recma-document.js | 94 +++++++++++--------- packages/mdx/lib/plugin/recma-jsx-rewrite.js | 32 +++---- 2 files changed, 69 insertions(+), 57 deletions(-) diff --git a/packages/mdx/lib/plugin/recma-document.js b/packages/mdx/lib/plugin/recma-document.js index 5ffa5af7f..7fa75f808 100644 --- a/packages/mdx/lib/plugin/recma-document.js +++ b/packages/mdx/lib/plugin/recma-document.js @@ -231,7 +231,7 @@ export function recmaDocument(options = {}) { child.expression.type === 'JSXElement') ) { content = true - replacement.push(createMdxContent(child.expression)) + replacement.push(...createMdxContent(child.expression, Boolean(layout))) // The following catch-all branch is because plugins might’ve added // other things. // Normally, we only have import/export/jsx, but just add whatever’s @@ -244,7 +244,7 @@ export function recmaDocument(options = {}) { // If there was no JSX content at all, add an empty function. if (!content) { - replacement.push(createMdxContent()) + replacement.push(...createMdxContent(undefined, Boolean(layout))) } exportedIdentifiers.push(['MDXContent', 'default']) @@ -481,9 +481,10 @@ export function recmaDocument(options = {}) { /** * @param {Expression} [content] - * @returns {FunctionDeclaration} + * @param {boolean} [hasLayout] + * @returns {FunctionDeclaration[]} */ - function createMdxContent(content) { + function createMdxContent(content, hasLayout) { /** @type {JSXElement} */ const element = { type: 'JSXElement', @@ -508,7 +509,12 @@ export function recmaDocument(options = {}) { openingElement: { type: 'JSXOpeningElement', name: {type: 'JSXIdentifier', name: '_createMdxContent'}, - attributes: [], + attributes: [ + { + type: 'JSXSpreadAttribute', + argument: {type: 'Identifier', name: 'props'} + } + ], selfClosing: true }, closingElement: null, @@ -518,7 +524,21 @@ export function recmaDocument(options = {}) { } // @ts-expect-error: JSXElements are expressions. - const consequent = /** @type {Expression} */ (element) + let consequent = /** @type {Expression} */ (element) + + if (!hasLayout) { + consequent = { + type: 'ConditionalExpression', + test: {type: 'Identifier', name: 'MDXLayout'}, + consequent, + alternate: { + type: 'CallExpression', + callee: {type: 'Identifier', name: '_createMdxContent'}, + arguments: [{type: 'Identifier', name: 'props'}], + optional: false + } + } + } let argument = content || {type: 'Literal', value: null} @@ -535,44 +555,36 @@ export function recmaDocument(options = {}) { argument = argument.children[0] } - return { - type: 'FunctionDeclaration', - id: {type: 'Identifier', name: 'MDXContent'}, - params: [ - { - type: 'AssignmentPattern', - left: {type: 'Identifier', name: 'props'}, - right: {type: 'ObjectExpression', properties: []} + return [ + { + type: 'FunctionDeclaration', + id: {type: 'Identifier', name: '_createMdxContent'}, + params: [{type: 'Identifier', name: 'props'}], + body: { + type: 'BlockStatement', + body: [{type: 'ReturnStatement', argument}] } - ], - body: { - type: 'BlockStatement', - body: [ - { - type: 'ReturnStatement', - argument: { - type: 'ConditionalExpression', - test: {type: 'Identifier', name: 'MDXLayout'}, - consequent, - alternate: { - type: 'CallExpression', - callee: {type: 'Identifier', name: '_createMdxContent'}, - arguments: [], - optional: false - } - } - }, + }, + { + type: 'FunctionDeclaration', + id: {type: 'Identifier', name: 'MDXContent'}, + params: [ { - type: 'FunctionDeclaration', - id: {type: 'Identifier', name: '_createMdxContent'}, - params: [], - body: { - type: 'BlockStatement', - body: [{type: 'ReturnStatement', argument}] - } + type: 'AssignmentPattern', + left: {type: 'Identifier', name: 'props'}, + right: {type: 'ObjectExpression', properties: []} } - ] + ], + body: { + type: 'BlockStatement', + body: [ + { + type: 'ReturnStatement', + argument: consequent + } + ] + } } - } + ] } } diff --git a/packages/mdx/lib/plugin/recma-jsx-rewrite.js b/packages/mdx/lib/plugin/recma-jsx-rewrite.js index e1d8dbf50..4539e3fd4 100644 --- a/packages/mdx/lib/plugin/recma-jsx-rewrite.js +++ b/packages/mdx/lib/plugin/recma-jsx-rewrite.js @@ -76,43 +76,43 @@ export function recmaJsxRewrite(options = {}) { walk(tree, { enter(_node) { const node = /** @type {Node} */ (_node) + const newScope = /** @type {Scope|undefined} */ ( + // @ts-expect-error: periscopic doesn’t support JSX. + scopeInfo.map.get(node) + ) if ( node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression' ) { - fnStack.push({ + const stackLength = fnStack.push({ objects: [], components: [], tags: [], references: {}, node }) - } - let fnScope = fnStack[0] + // MDXContent only ever contains MDXLayout + if ( + isNamedFunction(node, 'MDXContent') && + newScope && + !inScope(newScope, 'MDXLayout') + ) { + fnStack[stackLength - 1].components.push('MDXLayout') + } + } + const fnScope = fnStack[0] if ( !fnScope || - (!isNamedFunction(fnScope.node, 'MDXContent') && + (!isNamedFunction(fnScope.node, '_createMdxContent') && !providerImportSource) ) { return } - if ( - fnStack[1] && - isNamedFunction(fnStack[1].node, '_createMdxContent') - ) { - fnScope = fnStack[1] - } - - const newScope = /** @type {Scope|undefined} */ ( - // @ts-expect-error: periscopic doesn’t support JSX. - scopeInfo.map.get(node) - ) - if (newScope) { newScope.node = node currentScope = newScope From a5cf6a0c14df779f691cce8fcec1e615db44ca4c Mon Sep 17 00:00:00 2001 From: 0phoff <0phoff@users.noreply.github.com> Date: Mon, 13 Jun 2022 21:12:17 +0200 Subject: [PATCH 2/5] Fixed wrong tests --- packages/mdx/test/compile.js | 74 +++++++++++++++--------------- packages/rollup/test/index.test.js | 2 +- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/packages/mdx/test/compile.js b/packages/mdx/test/compile.js index 4385ec205..c09165c82 100644 --- a/packages/mdx/test/compile.js +++ b/packages/mdx/test/compile.js @@ -778,16 +778,16 @@ test('jsx', async () => { String(compileSync('*a*', {jsx: true})), [ '/*@jsxRuntime automatic @jsxImportSource react*/', + 'function _createMdxContent(props) {', + ' const _components = Object.assign({', + ' p: "p",', + ' em: "em"', + ' }, props.components);', + ' return <_components.p><_components.em>{"a"};', + '}', 'function MDXContent(props = {}) {', ' const {wrapper: MDXLayout} = props.components || ({});', - ' return MDXLayout ? <_createMdxContent /> : _createMdxContent();', - ' function _createMdxContent() {', - ' const _components = Object.assign({', - ' p: "p",', - ' em: "em"', - ' }, props.components);', - ' return <_components.p><_components.em>{"a"};', - ' }', + ' return MDXLayout ? <_createMdxContent {...props} /> : _createMdxContent(props);', '}', 'export default MDXContent;', '' @@ -799,12 +799,12 @@ test('jsx', async () => { String(compileSync('', {jsx: true})), [ '/*@jsxRuntime automatic @jsxImportSource react*/', + 'function _createMdxContent(props) {', + ' return ;', + '}', 'function MDXContent(props = {}) {', ' const {wrapper: MDXLayout} = props.components || ({});', - ' return MDXLayout ? <_createMdxContent /> : _createMdxContent();', - ' function _createMdxContent() {', - ' return ;', - ' }', + ' return MDXLayout ? <_createMdxContent {...props} /> : _createMdxContent(props);', '}', 'export default MDXContent;', '' @@ -816,15 +816,15 @@ test('jsx', async () => { String(compileSync('<>', {jsx: true})), [ '/*@jsxRuntime automatic @jsxImportSource react*/', + 'function _createMdxContent(props) {', + ' const {c} = props.components || ({});', + ' if (!c) _missingMdxReference("c", false);', + ' if (!c.d) _missingMdxReference("c.d", true);', + ' return <><>;', + '}', 'function MDXContent(props = {}) {', ' const {wrapper: MDXLayout} = props.components || ({});', - ' return MDXLayout ? <_createMdxContent /> : _createMdxContent();', - ' function _createMdxContent() {', - ' const {c} = props.components || ({});', - ' if (!c) _missingMdxReference("c", false);', - ' if (!c.d) _missingMdxReference("c.d", true);', - ' return <><>;', - ' }', + ' return MDXLayout ? <_createMdxContent {...props} /> : _createMdxContent(props);', '}', 'export default MDXContent;', 'function _missingMdxReference(id, component) {', @@ -840,12 +840,12 @@ test('jsx', async () => { [ '/*@jsxRuntime automatic @jsxImportSource react*/', '/*1*/', + 'function _createMdxContent(props) {', + ' return <><>{"a "}{}{" b"};', + '}', 'function MDXContent(props = {}) {', ' const {wrapper: MDXLayout} = props.components || ({});', - ' return MDXLayout ? <_createMdxContent /> : _createMdxContent();', - ' function _createMdxContent() {', - ' return <><>{"a "}{}{" b"};', - ' }', + ' return MDXLayout ? <_createMdxContent {...props} /> : _createMdxContent(props);', '}', 'export default MDXContent;', '' @@ -857,15 +857,15 @@ test('jsx', async () => { String(compileSync('{}', {jsx: true})), [ '/*@jsxRuntime automatic @jsxImportSource react*/', + 'function _createMdxContent(props) {', + ' const _components = Object.assign({', + ' "a-b": "a-b"', + ' }, props.components);', + ' return <>{<_components.a-b>};', + '}', 'function MDXContent(props = {}) {', ' const {wrapper: MDXLayout} = props.components || ({});', - ' return MDXLayout ? <_createMdxContent /> : _createMdxContent();', - ' function _createMdxContent() {', - ' const _components = Object.assign({', - ' "a-b": "a-b"', - ' }, props.components);', - ' return <>{<_components.a-b>};', - ' }', + ' return MDXLayout ? <_createMdxContent {...props} /> : _createMdxContent(props);', '}', 'export default MDXContent;', '' @@ -877,15 +877,15 @@ test('jsx', async () => { String(compileSync('Hello {props.name}', {jsx: true})), [ '/*@jsxRuntime automatic @jsxImportSource react*/', + 'function _createMdxContent(props) {', + ' const _components = Object.assign({', + ' p: "p"', + ' }, props.components);', + ' return <_components.p>{"Hello "}{props.name};', + '}', 'function MDXContent(props = {}) {', ' const {wrapper: MDXLayout} = props.components || ({});', - ' return MDXLayout ? <_createMdxContent /> : _createMdxContent();', - ' function _createMdxContent() {', - ' const _components = Object.assign({', - ' p: "p"', - ' }, props.components);', - ' return <_components.p>{"Hello "}{props.name};', - ' }', + ' return MDXLayout ? <_createMdxContent {...props} /> : _createMdxContent(props);', '}', 'export default MDXContent;', '' diff --git a/packages/rollup/test/index.test.js b/packages/rollup/test/index.test.js index 0f6168215..ae0b0781b 100644 --- a/packages/rollup/test/index.test.js +++ b/packages/rollup/test/index.test.js @@ -34,7 +34,7 @@ test('@mdx-js/rollup', async () => { assert.equal( output[0].map ? output[0].map.mappings : undefined, - ';;;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;;;;;;;', + ';;;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;;;;;;;;;;;;', 'should add a source map' ) From 2e7267c67409ae4a5fedf6fbfc0f2c6033408683 Mon Sep 17 00:00:00 2001 From: 0phoff <0phoff@users.noreply.github.com> Date: Tue, 14 Jun 2022 18:04:41 +0200 Subject: [PATCH 3/5] Renamed variables after PR Review - `hasLayout` -> `hasInternalLayout` - `consequent` -> `result` --- packages/mdx/lib/plugin/recma-document.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/mdx/lib/plugin/recma-document.js b/packages/mdx/lib/plugin/recma-document.js index 7fa75f808..2b07a2866 100644 --- a/packages/mdx/lib/plugin/recma-document.js +++ b/packages/mdx/lib/plugin/recma-document.js @@ -481,10 +481,10 @@ export function recmaDocument(options = {}) { /** * @param {Expression} [content] - * @param {boolean} [hasLayout] + * @param {boolean} [hasInternalLayout] * @returns {FunctionDeclaration[]} */ - function createMdxContent(content, hasLayout) { + function createMdxContent(content, hasInternalLayout) { /** @type {JSXElement} */ const element = { type: 'JSXElement', @@ -524,13 +524,13 @@ export function recmaDocument(options = {}) { } // @ts-expect-error: JSXElements are expressions. - let consequent = /** @type {Expression} */ (element) + let result = /** @type {Expression} */ (element) - if (!hasLayout) { - consequent = { + if (!hasInternalLayout) { + result = { type: 'ConditionalExpression', test: {type: 'Identifier', name: 'MDXLayout'}, - consequent, + consequent: result, alternate: { type: 'CallExpression', callee: {type: 'Identifier', name: '_createMdxContent'}, @@ -580,7 +580,7 @@ export function recmaDocument(options = {}) { body: [ { type: 'ReturnStatement', - argument: consequent + argument: result } ] } From 174ee1f0718537199c97db686a3202c7b5a32016 Mon Sep 17 00:00:00 2001 From: 0phoff <0phoff@users.noreply.github.com> Date: Thu, 16 Jun 2022 16:06:40 +0200 Subject: [PATCH 4/5] Assume MDXContent is always the 0th function in the stack --- packages/mdx/lib/plugin/recma-jsx-rewrite.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mdx/lib/plugin/recma-jsx-rewrite.js b/packages/mdx/lib/plugin/recma-jsx-rewrite.js index 4539e3fd4..4934eb7f8 100644 --- a/packages/mdx/lib/plugin/recma-jsx-rewrite.js +++ b/packages/mdx/lib/plugin/recma-jsx-rewrite.js @@ -86,7 +86,7 @@ export function recmaJsxRewrite(options = {}) { node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression' ) { - const stackLength = fnStack.push({ + fnStack.push({ objects: [], components: [], tags: [], @@ -100,7 +100,7 @@ export function recmaJsxRewrite(options = {}) { newScope && !inScope(newScope, 'MDXLayout') ) { - fnStack[stackLength - 1].components.push('MDXLayout') + fnStack[0].components.push('MDXLayout') } } From cc100be2c55d48b813b6fd5cde7d142136ec2b79 Mon Sep 17 00:00:00 2001 From: 0phoff <0phoff@users.noreply.github.com> Date: Thu, 16 Jun 2022 16:07:03 +0200 Subject: [PATCH 5/5] Add jsx specific test for internal layouts --- packages/mdx/test/compile.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/packages/mdx/test/compile.js b/packages/mdx/test/compile.js index c09165c82..8b6d457a3 100644 --- a/packages/mdx/test/compile.js +++ b/packages/mdx/test/compile.js @@ -893,6 +893,33 @@ test('jsx', async () => { 'should allow using props' ) + assert.equal( + String( + compileSync( + 'export default function Layout({components, ...props}) { return
}\n\na', + {jsx: true} + ) + ), + [ + '/*@jsxRuntime automatic @jsxImportSource react*/', + 'const MDXLayout = function Layout({components, ...props}) {', + ' return
;', + '};', + 'function _createMdxContent(props) {', + ' const _components = Object.assign({', + ' p: "p"', + ' }, props.components);', + ' return <_components.p>{"a"};', + '}', + 'function MDXContent(props = {}) {', + ' return <_createMdxContent {...props} />;', + '}', + 'export default MDXContent;', + '' + ].join('\n'), + 'should not have a conditional expression for MDXLayout when there is an internal layout' + ) + assert.match( String(compileSync("{}", {jsx: true})), /x="y " z"/,