Skip to content

Commit

Permalink
Add jsxFrag pragma, use fragment for shortcode warning
Browse files Browse the repository at this point in the history
This adds a `/* @jsxFrag mdx.Fragment */` next to the existing
`/* @jsx mdx */` pragma.
From MDX runtimes, this exports as `mdx.Fragment` either `React.Fragment`
or `Preact.Fragment`.

Vue 2 does not support fragments, but as JSX and hence MDX is already
specific to React or Vue, well: folks shouldn’t use fragments in MDX
files targeting Vue.

As we have fragments, we can also use that to pass children through
missing components: `<>{props.children}</>`.
This fixes runtimes where HTML is not available, such as React Native.
But, as Vue doesn’t like that, there’s a hidden flag to still use
the original behavior: `<div {...props} />`.
Still, there remains a difference in frameworks: Vue does not put
`children` in `props`, so `{...props}` has never passed children along
in Vue.

Closes GH-972.
Closes GH-990.
Closes GH-1014.
  • Loading branch information
wooorm committed Dec 20, 2020
1 parent e760cee commit 80cbc9a
Show file tree
Hide file tree
Showing 10 changed files with 64 additions and 37 deletions.
3 changes: 2 additions & 1 deletion packages/mdx/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ const mdxAstToMdxHast = require('./mdx-ast-to-mdx-hast')
const mdxHastToJsx = require('./mdx-hast-to-jsx')

const pragma = `/* @jsxRuntime classic */
/* @jsx mdx */`
/* @jsx mdx */
/* @jsxFrag mdx.Fragment */`

function createMdxAstCompiler(options = {}) {
return unified()
Expand Down
54 changes: 36 additions & 18 deletions packages/mdx/mdx-hast-to-jsx.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ function serializeEstree(estree, options) {
)

const shortcodes = createMakeShortcodeHelper(
uniq(jsxNames).filter(name => !importExportNames.includes(name))
uniq(jsxNames).filter(name => !importExportNames.includes(name)),
options.mdxFragment === false
)

const exports = []
Expand Down Expand Up @@ -261,7 +262,7 @@ function createMdxLayout(declaration, mdxLayoutDefault) {
}

// Note: this creates a Babel AST, not an estree.
function createMakeShortcodeHelper(names) {
function createMakeShortcodeHelper(names, useElement) {
const func = {
type: 'VariableDeclaration',
declarations: [
Expand Down Expand Up @@ -309,22 +310,39 @@ function createMakeShortcodeHelper(names) {
},
{
type: 'ReturnStatement',
argument: {
type: 'JSXElement',
openingElement: {
type: 'JSXOpeningElement',
attributes: [
{
type: 'JSXSpreadAttribute',
argument: {type: 'Identifier', name: 'props'}
}
],
name: {type: 'JSXIdentifier', name: 'div'},
selfClosing: true
},
closingElement: null,
children: []
}
argument: useElement
? {
type: 'JSXElement',
openingElement: {
type: 'JSXOpeningElement',
attributes: [
{
type: 'JSXSpreadAttribute',
argument: {type: 'Identifier', name: 'props'}
}
],
name: {type: 'JSXIdentifier', name: 'div'},
selfClosing: true
},
closingElement: null,
children: []
}
: {
type: 'JSXFragment',
openingFragment: {type: 'JSXOpeningFragment'},
closingFragment: {type: 'JSXClosingFragment'},
children: [
{
type: 'JSXExpressionContainer',
expression: {
type: 'MemberExpression',
object: {type: 'Identifier', name: 'props'},
property: {type: 'Identifier', name: 'children'},
computed: false
}
}
]
}
}
]
}
Expand Down
12 changes: 3 additions & 9 deletions packages/mdx/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -564,23 +564,17 @@ describe('@mdx-js/mdx', () => {
})

it('should not crash but issue a warning when an undefined component is used', async () => {
const Content = await run('x <Y /> z')
const Content = await run('w <X>y</X> z')
const warn = console.warn
console.warn = jest.fn()

// To do: a fragment would probably be better?
// Maybe the components children?
expect(renderToStaticMarkup(<Content />)).toEqual(
renderToStaticMarkup(
<p>
x <div /> z
</p>
)
renderToStaticMarkup(<p>w y z</p>)
)

expect(console.warn).toHaveBeenCalledWith(
'Component `%s` was not imported, exported, or provided by MDXProvider as global scope',
'Y'
'X'
)

console.warn = warn
Expand Down
6 changes: 4 additions & 2 deletions packages/preact/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@
],
"scripts": {
"build": "microbundle -f modern,es,cjs src/index.js",
"test": "jest test --coverage",
"test-types": "dtslint types"
"test-api": "jest test",
"test-coverage": "jest test --coverage",
"test-types": "dtslint types",
"test": "yarn test-coverage && yarn test-types"
},
"peerDependencies": {
"preact": "^10.4.6"
Expand Down
6 changes: 5 additions & 1 deletion packages/preact/src/create-element.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const MDXCreateElement = forwardRef((props, ref) => {

MDXCreateElement.displayName = 'MDXCreateElement'

export default function (type, props) {
function mdx(type, props) {
const args = arguments
const mdxType = props && props.mdxType

Expand Down Expand Up @@ -73,3 +73,7 @@ export default function (type, props) {

return h.apply(null, args)
}

mdx.Fragment = Fragment

export default mdx
2 changes: 1 addition & 1 deletion packages/preact/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ describe('@mdx-js/preact', () => {
const warn = console.warn
console.warn = jest.fn()

expect(render(<Content />)).toEqual('<div></div>')
expect(render(<Content />)).toEqual('')

expect(console.warn).toHaveBeenCalledWith(
'Component `%s` was not imported, exported, or provided by MDXProvider as global scope',
Expand Down
6 changes: 5 additions & 1 deletion packages/react/src/create-element.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const MDXCreateElement = React.forwardRef((props, ref) => {

MDXCreateElement.displayName = 'MDXCreateElement'

export default function (type, props) {
function mdx(type, props) {
const args = arguments
const mdxType = props && props.mdxType

Expand Down Expand Up @@ -72,3 +72,7 @@ export default function (type, props) {

return React.createElement.apply(null, args)
}

mdx.Fragment = React.Fragment

export default mdx
4 changes: 2 additions & 2 deletions packages/react/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ describe('@mdx-js/react', () => {
})

test('should warn on missing components', async () => {
const Content = await run('<Component />')
const Content = await run('<Component>x</Component>')
const warn = console.warn
console.warn = jest.fn()

expect(renderToString(<Content />)).toEqual('<div></div>')
expect(renderToString(<Content />)).toEqual('<p>x</p>')

expect(console.warn).toHaveBeenCalledWith(
'Component `%s` was not imported, exported, or provided by MDXProvider as global scope',
Expand Down
6 changes: 5 additions & 1 deletion packages/vue-loader/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ async function mdxLoader(content) {
let result

try {
result = await mdx(content, {...options, skipExport: true})
result = await mdx(content, {
...options,
skipExport: true,
mdxFragment: false
})
} catch (err) {
return callback(err)
}
Expand Down
2 changes: 1 addition & 1 deletion packages/vue/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {MDXProvider, mdx} from '../src'

const run = async value => {
// Turn the serialized MDX code into serialized JSX…
const doc = await mdxTransform(value, {skipExport: true})
const doc = await mdxTransform(value, {skipExport: true, mdxFragment: false})

// …and that into serialized JS.
const {code} = await babelTransform(doc, {
Expand Down

0 comments on commit 80cbc9a

Please sign in to comment.