Skip to content

Commit 12e63e8

Browse files
authored
Add improved support for non-React frameworks
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 <[email protected]>
1 parent 97b9d59 commit 12e63e8

File tree

4 files changed

+105
-5
lines changed

4 files changed

+105
-5
lines changed

packages/mdx/lib/core.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* @typedef {import('remark-rehype').Options} RemarkRehypeOptions
33
* @typedef {import('unified').PluggableList} PluggableList
44
* @typedef {import('unified').Processor} Processor
5+
* @typedef {import('./plugin/rehype-recma.js').Options} RehypeRecmaOptions
56
* @typedef {import('./plugin/recma-document.js').RecmaDocumentOptions} RecmaDocumentOptions
67
* @typedef {import('./plugin/recma-stringify.js').RecmaStringifyOptions} RecmaStringifyOptions
78
* @typedef {import('./plugin/recma-jsx-rewrite.js').RecmaJsxRewriteOptions} RecmaJsxRewriteOptions
@@ -29,7 +30,7 @@
2930
* @property {RemarkRehypeOptions | null | undefined} [remarkRehypeOptions]
3031
* Options to pass through to `remark-rehype`.
3132
*
32-
* @typedef {Omit<RecmaDocumentOptions & RecmaStringifyOptions & RecmaJsxRewriteOptions, 'outputFormat'>} PluginOptions
33+
* @typedef {Omit<RehypeRecmaOptions & RecmaDocumentOptions & RecmaStringifyOptions & RecmaJsxRewriteOptions, 'outputFormat'>} PluginOptions
3334
* Configuration for internal plugins.
3435
*
3536
* @typedef {BaseProcessorOptions & PluginOptions} ProcessorOptions
@@ -82,6 +83,8 @@ export function createProcessor(options) {
8283
rehypePlugins,
8384
remarkPlugins,
8485
remarkRehypeOptions,
86+
elementAttributeNameCase,
87+
stylePropertyNameCase,
8588
SourceMapGenerator,
8689
...rest
8790
} = options || {}
@@ -136,7 +139,7 @@ export function createProcessor(options) {
136139
}
137140

138141
pipeline
139-
.use(rehypeRecma)
142+
.use(rehypeRecma, {elementAttributeNameCase, stylePropertyNameCase})
140143
.use(recmaDocument, {...rest, outputFormat})
141144
.use(recmaJsxRewrite, {
142145
development: dev,

packages/mdx/lib/plugin/rehype-recma.js

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,41 @@
33
* @typedef {import('hast').Root} Root
44
*/
55

6+
/**
7+
* @typedef {'html' | 'react'} ElementAttributeNameCase
8+
* Specify casing to use for attribute names.
9+
*
10+
* HTML casing is for example `class`, `stroke-linecap`, `xml:lang`.
11+
* React casing is for example `className`, `strokeLinecap`, `xmlLang`.
12+
*
13+
* @typedef {'css' | 'dom'} StylePropertyNameCase
14+
* Casing to use for property names in `style` objects.
15+
*
16+
* CSS casing is for example `background-color` and `-webkit-line-clamp`.
17+
* DOM casing is for example `backgroundColor` and `WebkitLineClamp`.
18+
*
19+
* @typedef Options
20+
* Configuration for internal plugin `rehype-recma`.
21+
* @property {ElementAttributeNameCase | null | undefined} [elementAttributeNameCase='react']
22+
* Specify casing to use for attribute names.
23+
*
24+
* This casing is used for hast elements, not for embedded MDX JSX nodes
25+
* (components that someone authored manually).
26+
* @property {StylePropertyNameCase | null | undefined} [stylePropertyNameCase='dom']
27+
* Specify casing to use for property names in `style` objects.
28+
*
29+
* This casing is used for hast elements, not for embedded MDX JSX nodes
30+
* (components that someone authored manually).
31+
*/
32+
633
import {toEstree} from 'hast-util-to-estree'
734

835
/**
936
* A plugin to transform an HTML (hast) tree to a JS (estree).
1037
* `hast-util-to-estree` does all the work for us!
1138
*
12-
* @type {import('unified').Plugin<[], Root, Program>}
39+
* @type {import('unified').Plugin<[Options | null | undefined] | [], Root, Program>}
1340
*/
14-
export function rehypeRecma() {
15-
return (tree) => toEstree(tree)
41+
export function rehypeRecma(options) {
42+
return (tree) => toEstree(tree, options)
1643
}

packages/mdx/readme.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,22 @@ is `'c'` this following will be generated: `import a from 'c'`.
702702

703703
See `options.pragma` for an example.
704704

705+
###### `options.elementAttributeNameCase`
706+
707+
Specify casing to use for attribute names (`'html' | 'react`, default:
708+
`'react'`).
709+
710+
This casing is used for hast elements, not for embedded MDX JSX nodes
711+
(components that someone authored manually).
712+
713+
###### `options.stylePropertyNameCase`
714+
715+
Specify casing to use for property names in `style` objects (`'css' | 'dom`,
716+
default: `'dom'`).
717+
718+
This casing is used for hast elements, not for embedded MDX JSX nodes
719+
(components that someone authored manually).
720+
705721
###### Returns
706722

707723
`Promise<VFile>` — Promise that resolves to the compiled JS as a [vfile][].

packages/mdx/test/compile.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -943,6 +943,60 @@ test('jsx', async () => {
943943
/a & b &#123; c &lt; d/,
944944
'should serialize `<` and `{` in JSX text'
945945
)
946+
947+
assert.match(
948+
String(
949+
compileSync('', {
950+
rehypePlugins: [
951+
/** @type {import('unified').Plugin<[], import('hast').Root>} */
952+
function () {
953+
return function (tree) {
954+
tree.children.push({
955+
type: 'element',
956+
tagName: 'a',
957+
properties: {
958+
className: 'b',
959+
style: '-webkit-box-shadow: 0 0 1px 0 red'
960+
},
961+
children: []
962+
})
963+
}
964+
}
965+
],
966+
jsx: true
967+
})
968+
),
969+
/className="b"/,
970+
'should use React props and DOM styles by default'
971+
)
972+
973+
assert.match(
974+
String(
975+
compileSync('', {
976+
rehypePlugins: [
977+
/** @type {import('unified').Plugin<[], import('hast').Root>} */
978+
function () {
979+
return function (tree) {
980+
tree.children.push({
981+
type: 'element',
982+
tagName: 'a',
983+
properties: {
984+
className: 'b',
985+
style: '-webkit-box-shadow: 0 0 1px 0 red'
986+
},
987+
children: []
988+
})
989+
}
990+
}
991+
],
992+
elementAttributeNameCase: 'html',
993+
stylePropertyNameCase: 'css',
994+
jsx: true
995+
})
996+
),
997+
/class="b"/,
998+
'should support `elementAttributeNameCase` and `stylePropertyNameCase`'
999+
)
9461000
})
9471001

9481002
test('markdown (CM)', async () => {

0 commit comments

Comments
 (0)