Skip to content

Commit

Permalink
Add improved support for non-React frameworks
Browse files Browse the repository at this point in the history
React uses their own style of attributes (similar to the DOM), such as `className` instead of `class`.
Some ther frameworks don’t support that: they want you to pass `class`. Similarly, React wants `WebkitBoxShadow`, while other frameworks want `-webkit-box-shadow`.

This particularly becomes an issue around more complex things such as SVG.
With this commit, you can now configure the exact style that your particular framework accepts.

Closes GH-2247.
Closes GH-2255.

Reviewed-by: Christian Murphy <christian.murphy.42@gmail.com>
  • Loading branch information
wooorm authored Feb 9, 2023
1 parent 97b9d59 commit 12e63e8
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 5 deletions.
7 changes: 5 additions & 2 deletions packages/mdx/lib/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* @typedef {import('remark-rehype').Options} RemarkRehypeOptions
* @typedef {import('unified').PluggableList} PluggableList
* @typedef {import('unified').Processor} Processor
* @typedef {import('./plugin/rehype-recma.js').Options} RehypeRecmaOptions
* @typedef {import('./plugin/recma-document.js').RecmaDocumentOptions} RecmaDocumentOptions
* @typedef {import('./plugin/recma-stringify.js').RecmaStringifyOptions} RecmaStringifyOptions
* @typedef {import('./plugin/recma-jsx-rewrite.js').RecmaJsxRewriteOptions} RecmaJsxRewriteOptions
Expand Down Expand Up @@ -29,7 +30,7 @@
* @property {RemarkRehypeOptions | null | undefined} [remarkRehypeOptions]
* Options to pass through to `remark-rehype`.
*
* @typedef {Omit<RecmaDocumentOptions & RecmaStringifyOptions & RecmaJsxRewriteOptions, 'outputFormat'>} PluginOptions
* @typedef {Omit<RehypeRecmaOptions & RecmaDocumentOptions & RecmaStringifyOptions & RecmaJsxRewriteOptions, 'outputFormat'>} PluginOptions
* Configuration for internal plugins.
*
* @typedef {BaseProcessorOptions & PluginOptions} ProcessorOptions
Expand Down Expand Up @@ -82,6 +83,8 @@ export function createProcessor(options) {
rehypePlugins,
remarkPlugins,
remarkRehypeOptions,
elementAttributeNameCase,
stylePropertyNameCase,
SourceMapGenerator,
...rest
} = options || {}
Expand Down Expand Up @@ -136,7 +139,7 @@ export function createProcessor(options) {
}

pipeline
.use(rehypeRecma)
.use(rehypeRecma, {elementAttributeNameCase, stylePropertyNameCase})
.use(recmaDocument, {...rest, outputFormat})
.use(recmaJsxRewrite, {
development: dev,
Expand Down
33 changes: 30 additions & 3 deletions packages/mdx/lib/plugin/rehype-recma.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,41 @@
* @typedef {import('hast').Root} Root
*/

/**
* @typedef {'html' | 'react'} ElementAttributeNameCase
* Specify casing to use for attribute names.
*
* HTML casing is for example `class`, `stroke-linecap`, `xml:lang`.
* React casing is for example `className`, `strokeLinecap`, `xmlLang`.
*
* @typedef {'css' | 'dom'} StylePropertyNameCase
* Casing to use for property names in `style` objects.
*
* CSS casing is for example `background-color` and `-webkit-line-clamp`.
* DOM casing is for example `backgroundColor` and `WebkitLineClamp`.
*
* @typedef Options
* Configuration for internal plugin `rehype-recma`.
* @property {ElementAttributeNameCase | null | undefined} [elementAttributeNameCase='react']
* Specify casing to use for attribute names.
*
* This casing is used for hast elements, not for embedded MDX JSX nodes
* (components that someone authored manually).
* @property {StylePropertyNameCase | null | undefined} [stylePropertyNameCase='dom']
* Specify casing to use for property names in `style` objects.
*
* This casing is used for hast elements, not for embedded MDX JSX nodes
* (components that someone authored manually).
*/

import {toEstree} from 'hast-util-to-estree'

/**
* A plugin to transform an HTML (hast) tree to a JS (estree).
* `hast-util-to-estree` does all the work for us!
*
* @type {import('unified').Plugin<[], Root, Program>}
* @type {import('unified').Plugin<[Options | null | undefined] | [], Root, Program>}
*/
export function rehypeRecma() {
return (tree) => toEstree(tree)
export function rehypeRecma(options) {
return (tree) => toEstree(tree, options)
}
16 changes: 16 additions & 0 deletions packages/mdx/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,22 @@ is `'c'` this following will be generated: `import a from 'c'`.

See `options.pragma` for an example.

###### `options.elementAttributeNameCase`

Specify casing to use for attribute names (`'html' | 'react`, default:
`'react'`).

This casing is used for hast elements, not for embedded MDX JSX nodes
(components that someone authored manually).

###### `options.stylePropertyNameCase`

Specify casing to use for property names in `style` objects (`'css' | 'dom`,
default: `'dom'`).

This casing is used for hast elements, not for embedded MDX JSX nodes
(components that someone authored manually).

###### Returns

`Promise<VFile>` — Promise that resolves to the compiled JS as a [vfile][].
Expand Down
54 changes: 54 additions & 0 deletions packages/mdx/test/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -943,6 +943,60 @@ test('jsx', async () => {
/a & b &#123; c &lt; d/,
'should serialize `<` and `{` in JSX text'
)

assert.match(
String(
compileSync('', {
rehypePlugins: [
/** @type {import('unified').Plugin<[], import('hast').Root>} */
function () {
return function (tree) {
tree.children.push({
type: 'element',
tagName: 'a',
properties: {
className: 'b',
style: '-webkit-box-shadow: 0 0 1px 0 red'
},
children: []
})
}
}
],
jsx: true
})
),
/className="b"/,
'should use React props and DOM styles by default'
)

assert.match(
String(
compileSync('', {
rehypePlugins: [
/** @type {import('unified').Plugin<[], import('hast').Root>} */
function () {
return function (tree) {
tree.children.push({
type: 'element',
tagName: 'a',
properties: {
className: 'b',
style: '-webkit-box-shadow: 0 0 1px 0 red'
},
children: []
})
}
}
],
elementAttributeNameCase: 'html',
stylePropertyNameCase: 'css',
jsx: true
})
),
/class="b"/,
'should support `elementAttributeNameCase` and `stylePropertyNameCase`'
)
})

test('markdown (CM)', async () => {
Expand Down

1 comment on commit 12e63e8

@vercel
Copy link

@vercel vercel bot commented on 12e63e8 Feb 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

mdx – ./

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

Please sign in to comment.