Skip to content

Commit 4a48f1f

Browse files
authoredJan 25, 2022
Fix custom elements (#1911)
Backports: wooorm/xdm@ddcebdb. Backports: wooorm/xdm@0c6d8ac. Related-to: remarkjs/remark-math#72. Related-to: wooorm/xdm#106.
1 parent d4d969f commit 4a48f1f

File tree

4 files changed

+98
-16
lines changed

4 files changed

+98
-16
lines changed
 

‎package.json

+1
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@
176176
"node/file-extension-in-import": "off",
177177
"react/prop-types": "off",
178178
"unicorn/no-await-expression-member": "off",
179+
"unicorn/prefer-code-point": "off",
179180
"unicorn/prefer-node-protocol": "off",
180181
"capitalized-comments": "off",
181182
"complexity": "off",

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

+7-6
Original file line numberDiff line numberDiff line change
@@ -180,11 +180,10 @@ export function recmaJsxRewrite(options = {}) {
180180
fnScope.tags.push(id)
181181
}
182182

183-
node.openingElement.name = {
184-
type: 'JSXMemberExpression',
185-
object: {type: 'JSXIdentifier', name: '_components'},
186-
property: name
187-
}
183+
node.openingElement.name = toJsxIdOrMemberExpression([
184+
'_components',
185+
id
186+
])
188187

189188
if (node.closingElement) {
190189
node.closingElement.name = toJsxIdOrMemberExpression([
@@ -224,7 +223,9 @@ export function recmaJsxRewrite(options = {}) {
224223
defaults.push({
225224
type: 'Property',
226225
kind: 'init',
227-
key: {type: 'Identifier', name},
226+
key: isIdentifierName(name)
227+
? {type: 'Identifier', name}
228+
: {type: 'Literal', value: name},
228229
value: {type: 'Literal', value: name},
229230
method: false,
230231
shorthand: false,

‎packages/mdx/lib/util/estree-util-to-id-or-member-expression.js

+54-10
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,35 @@
66
* @typedef {import('estree-jsx').JSXMemberExpression} JSXMemberExpression
77
*/
88

9-
import {name as isIdentifierName} from 'estree-util-is-identifier-name'
9+
import {
10+
start as esStart,
11+
cont as esCont,
12+
name as isIdentifierName
13+
} from 'estree-util-is-identifier-name'
1014

1115
export const toIdOrMemberExpression = toIdOrMemberExpressionFactory(
1216
'Identifier',
13-
'MemberExpression'
17+
'MemberExpression',
18+
isIdentifierName
1419
)
1520

1621
export const toJsxIdOrMemberExpression =
1722
// @ts-expect-error: fine
1823
/** @type {(ids: Array<string|number>) => JSXIdentifier|JSXMemberExpression)} */
19-
(toIdOrMemberExpressionFactory('JSXIdentifier', 'JSXMemberExpression'))
24+
(
25+
toIdOrMemberExpressionFactory(
26+
'JSXIdentifier',
27+
'JSXMemberExpression',
28+
isJsxIdentifierName
29+
)
30+
)
2031

2132
/**
22-
* @param {string} [idType]
23-
* @param {string} [memberType]
33+
* @param {string} idType
34+
* @param {string} memberType
35+
* @param {(value: string) => boolean} isIdentifier
2436
*/
25-
function toIdOrMemberExpressionFactory(idType, memberType) {
37+
function toIdOrMemberExpressionFactory(idType, memberType, isIdentifier) {
2638
return toIdOrMemberExpression
2739
/**
2840
* @param {Array<string|number>} ids
@@ -35,12 +47,18 @@ function toIdOrMemberExpressionFactory(idType, memberType) {
3547

3648
while (++index < ids.length) {
3749
const name = ids[index]
50+
const valid = typeof name === 'string' && isIdentifier(name)
51+
52+
// A value of `asd.123` could be turned into `asd['123']` in the JS form,
53+
// but JSX does not have a form for it, so throw.
54+
/* c8 ignore next 3 */
55+
if (idType === 'JSXIdentifier' && !valid) {
56+
throw new Error('Cannot turn `' + name + '` into a JSX identifier')
57+
}
58+
3859
/** @type {Identifier|Literal} */
3960
// @ts-expect-error: JSX is fine.
40-
const id =
41-
typeof name === 'string' && isIdentifierName(name)
42-
? {type: idType, name}
43-
: {type: 'Literal', value: name}
61+
const id = valid ? {type: idType, name} : {type: 'Literal', value: name}
4462
// @ts-expect-error: JSX is fine.
4563
object = object
4664
? {
@@ -62,3 +80,29 @@ function toIdOrMemberExpressionFactory(idType, memberType) {
6280
return object
6381
}
6482
}
83+
84+
/**
85+
* Checks if the given string is a valid JSX identifier name.
86+
* @param {string} name
87+
*/
88+
function isJsxIdentifierName(name) {
89+
let index = -1
90+
91+
while (++index < name.length) {
92+
// We currently receive valid input, but this catches bugs and is needed
93+
// when externalized.
94+
/* c8 ignore next */
95+
if (!(index ? jsxCont : esStart)(name.charCodeAt(index))) return false
96+
}
97+
98+
// `false` if `name` is empty.
99+
return index > 0
100+
}
101+
102+
/**
103+
* Checks if the given character code can continue a JSX identifier.
104+
* @param {number} code
105+
*/
106+
function jsxCont(code) {
107+
return code === 45 /* `-` */ || esCont(code)
108+
}

‎packages/mdx/test/compile.js

+36
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,22 @@ test('compile', async () => {
134134
'should compile a non-element document (rehype, single element)'
135135
)
136136

137+
assert.equal(
138+
renderToStaticMarkup(
139+
React.createElement(
140+
await run(
141+
compileSync('y', {
142+
rehypePlugins: [
143+
() => () => ({type: 'element', tagName: 'a-b', children: []})
144+
]
145+
})
146+
)
147+
)
148+
),
149+
'<a-b></a-b>',
150+
'should compile custom elements'
151+
)
152+
137153
assert.equal(
138154
renderToStaticMarkup(
139155
React.createElement(
@@ -798,6 +814,26 @@ test('jsx', async () => {
798814
'should serialize fragments, expressions'
799815
)
800816

817+
assert.equal(
818+
String(compileSync('{<a-b></a-b>}', {jsx: true})),
819+
[
820+
'/*@jsxRuntime automatic @jsxImportSource react*/',
821+
'function MDXContent(props = {}) {',
822+
' const {wrapper: MDXLayout} = props.components || ({});',
823+
' return MDXLayout ? <MDXLayout {...props}><_createMdxContent /></MDXLayout> : _createMdxContent();',
824+
' function _createMdxContent() {',
825+
' const _components = Object.assign({',
826+
' "a-b": "a-b"',
827+
' }, props.components);',
828+
' return <>{<_components.a-b></_components.a-b>}</>;',
829+
' }',
830+
'}',
831+
'export default MDXContent;',
832+
''
833+
].join('\n'),
834+
'should serialize custom elements inside expressions'
835+
)
836+
801837
assert.equal(
802838
String(compileSync('Hello {props.name}', {jsx: true})),
803839
[

1 commit comments

Comments
 (1)

vercel[bot] commented on Jan 25, 2022

@vercel[bot]

Successfully deployed to the following URLs:

Please sign in to comment.