Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix custom elements #1911

Merged
merged 1 commit into from Jan 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -176,6 +176,7 @@
"node/file-extension-in-import": "off",
"react/prop-types": "off",
"unicorn/no-await-expression-member": "off",
"unicorn/prefer-code-point": "off",
"unicorn/prefer-node-protocol": "off",
"capitalized-comments": "off",
"complexity": "off",
Expand Down
13 changes: 7 additions & 6 deletions packages/mdx/lib/plugin/recma-jsx-rewrite.js
Expand Up @@ -180,11 +180,10 @@ export function recmaJsxRewrite(options = {}) {
fnScope.tags.push(id)
}

node.openingElement.name = {
type: 'JSXMemberExpression',
object: {type: 'JSXIdentifier', name: '_components'},
property: name
}
node.openingElement.name = toJsxIdOrMemberExpression([
'_components',
id
])

if (node.closingElement) {
node.closingElement.name = toJsxIdOrMemberExpression([
Expand Down Expand Up @@ -224,7 +223,9 @@ export function recmaJsxRewrite(options = {}) {
defaults.push({
type: 'Property',
kind: 'init',
key: {type: 'Identifier', name},
key: isIdentifierName(name)
? {type: 'Identifier', name}
: {type: 'Literal', value: name},
value: {type: 'Literal', value: name},
method: false,
shorthand: false,
Expand Down
64 changes: 54 additions & 10 deletions packages/mdx/lib/util/estree-util-to-id-or-member-expression.js
Expand Up @@ -6,23 +6,35 @@
* @typedef {import('estree-jsx').JSXMemberExpression} JSXMemberExpression
*/

import {name as isIdentifierName} from 'estree-util-is-identifier-name'
import {
start as esStart,
cont as esCont,
name as isIdentifierName
} from 'estree-util-is-identifier-name'

export const toIdOrMemberExpression = toIdOrMemberExpressionFactory(
'Identifier',
'MemberExpression'
'MemberExpression',
isIdentifierName
)

export const toJsxIdOrMemberExpression =
// @ts-expect-error: fine
/** @type {(ids: Array<string|number>) => JSXIdentifier|JSXMemberExpression)} */
(toIdOrMemberExpressionFactory('JSXIdentifier', 'JSXMemberExpression'))
(
toIdOrMemberExpressionFactory(
'JSXIdentifier',
'JSXMemberExpression',
isJsxIdentifierName
)
)

/**
* @param {string} [idType]
* @param {string} [memberType]
* @param {string} idType
* @param {string} memberType
* @param {(value: string) => boolean} isIdentifier
*/
function toIdOrMemberExpressionFactory(idType, memberType) {
function toIdOrMemberExpressionFactory(idType, memberType, isIdentifier) {
return toIdOrMemberExpression
/**
* @param {Array<string|number>} ids
Expand All @@ -35,12 +47,18 @@ function toIdOrMemberExpressionFactory(idType, memberType) {

while (++index < ids.length) {
const name = ids[index]
const valid = typeof name === 'string' && isIdentifier(name)

// A value of `asd.123` could be turned into `asd['123']` in the JS form,
// but JSX does not have a form for it, so throw.
/* c8 ignore next 3 */
if (idType === 'JSXIdentifier' && !valid) {
throw new Error('Cannot turn `' + name + '` into a JSX identifier')
}

/** @type {Identifier|Literal} */
// @ts-expect-error: JSX is fine.
const id =
typeof name === 'string' && isIdentifierName(name)
? {type: idType, name}
: {type: 'Literal', value: name}
const id = valid ? {type: idType, name} : {type: 'Literal', value: name}
// @ts-expect-error: JSX is fine.
object = object
? {
Expand All @@ -62,3 +80,29 @@ function toIdOrMemberExpressionFactory(idType, memberType) {
return object
}
}

/**
* Checks if the given string is a valid JSX identifier name.
* @param {string} name
*/
function isJsxIdentifierName(name) {
let index = -1

while (++index < name.length) {
// We currently receive valid input, but this catches bugs and is needed
// when externalized.
/* c8 ignore next */
if (!(index ? jsxCont : esStart)(name.charCodeAt(index))) return false
}

// `false` if `name` is empty.
return index > 0
}

/**
* Checks if the given character code can continue a JSX identifier.
* @param {number} code
*/
function jsxCont(code) {
return code === 45 /* `-` */ || esCont(code)
}
36 changes: 36 additions & 0 deletions packages/mdx/test/compile.js
Expand Up @@ -134,6 +134,22 @@ test('compile', async () => {
'should compile a non-element document (rehype, single element)'
)

assert.equal(
renderToStaticMarkup(
React.createElement(
await run(
compileSync('y', {
rehypePlugins: [
() => () => ({type: 'element', tagName: 'a-b', children: []})
]
})
)
)
),
'<a-b></a-b>',
'should compile custom elements'
)

assert.equal(
renderToStaticMarkup(
React.createElement(
Expand Down Expand Up @@ -798,6 +814,26 @@ test('jsx', async () => {
'should serialize fragments, expressions'
)

assert.equal(
String(compileSync('{<a-b></a-b>}', {jsx: true})),
[
'/*@jsxRuntime automatic @jsxImportSource react*/',
'function MDXContent(props = {}) {',
' const {wrapper: MDXLayout} = props.components || ({});',
' return MDXLayout ? <MDXLayout {...props}><_createMdxContent /></MDXLayout> : _createMdxContent();',
' function _createMdxContent() {',
' const _components = Object.assign({',
' "a-b": "a-b"',
' }, props.components);',
' return <>{<_components.a-b></_components.a-b>}</>;',
' }',
'}',
'export default MDXContent;',
''
].join('\n'),
'should serialize custom elements inside expressions'
)

assert.equal(
String(compileSync('Hello {props.name}', {jsx: true})),
[
Expand Down