diff --git a/packages/mdx/lib/core.js b/packages/mdx/lib/core.js index f028dbfec..feed19d77 100644 --- a/packages/mdx/lib/core.js +++ b/packages/mdx/lib/core.js @@ -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 @@ -29,7 +30,7 @@ * @property {RemarkRehypeOptions | null | undefined} [remarkRehypeOptions] * Options to pass through to `remark-rehype`. * - * @typedef {Omit} PluginOptions + * @typedef {Omit} PluginOptions * Configuration for internal plugins. * * @typedef {BaseProcessorOptions & PluginOptions} ProcessorOptions @@ -82,6 +83,8 @@ export function createProcessor(options) { rehypePlugins, remarkPlugins, remarkRehypeOptions, + elementAttributeNameCase, + stylePropertyNameCase, SourceMapGenerator, ...rest } = options || {} @@ -136,7 +139,7 @@ export function createProcessor(options) { } pipeline - .use(rehypeRecma) + .use(rehypeRecma, {elementAttributeNameCase, stylePropertyNameCase}) .use(recmaDocument, {...rest, outputFormat}) .use(recmaJsxRewrite, { development: dev, diff --git a/packages/mdx/lib/plugin/rehype-recma.js b/packages/mdx/lib/plugin/rehype-recma.js index a55abbc31..e8279369d 100644 --- a/packages/mdx/lib/plugin/rehype-recma.js +++ b/packages/mdx/lib/plugin/rehype-recma.js @@ -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) } diff --git a/packages/mdx/readme.md b/packages/mdx/readme.md index 23d98d4be..826ed66fe 100644 --- a/packages/mdx/readme.md +++ b/packages/mdx/readme.md @@ -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` — Promise that resolves to the compiled JS as a [vfile][]. diff --git a/packages/mdx/test/compile.js b/packages/mdx/test/compile.js index 65c5ed128..5920b414f 100644 --- a/packages/mdx/test/compile.js +++ b/packages/mdx/test/compile.js @@ -943,6 +943,60 @@ test('jsx', async () => { /a & b { c < 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 () => {