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

Add Option to override built-in HTML elements #2052

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions docs/migrating/v2.server.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const info = {
* [`@mdx-js/loader`](#mdx-jsloader)
* [`@mdx-js/react`, `@mdx-js/preact`, `@mdx-js/vue`](#mdx-jsreact-mdx-jspreact-mdx-jsvue)
* [`@mdx-js/mdx`](#mdx-jsmdx)
* [`@mdx-js/mdx` Sandboxing](#mdx-jsmdx-sandboxing)
* [`@mdx-js/runtime`](#mdx-jsruntime)
* [`remark-mdx`](#remark-mdx)
* [`@mdx-js/vue-loader`](#mdx-jsvue-loader)
Expand Down Expand Up @@ -343,6 +344,28 @@ If you used them:

For more information, please see [§ API in `@mdx-js/mdx`][mdx-api].

### `@mdx-js/mdx` Sandboxing

`@mdx-js/mdx` now is “sandboxed” and by default will not override native HTML
elements. If you neeed to override the default handling for native HTML
elements provide the `options.overrideBuiltIn` option.

```js path="new-overrideBuiltIn.js"
import {evaluate} from '@mdx-js/mdx'

const {default: Content} = await evaluate('<h1>hi</hi>', {overrideBuiltIn: true})

return <Content components={{h1: ()=><h1>override</h1>}} />
```

###### Changes

`@mdx-js/mdx` in version 1 automatically replaced native HTML tags with their
matching components. In version 2 this needs to be excplicitly defined.

* `options.overrideBuiltIn` is now used to tell the compiler to accept
replacing native HTML elements

### `@mdx-js/runtime`

We’ve deprecated `@mdx-js/runtime`.
Expand Down Expand Up @@ -631,6 +654,7 @@ The interface of the generated JavaScript from MDX changed:
* We now “sandbox” components, for lack of a better name.
It means that when you pass a component for `h1`, it does get used for
`# hi` but not for `<h1>hi</h1>`
Disable “sandbox” in order to override native HTML elements. See [`@mdx-js/mdx` Sandboxing](#mdx-jsmdx-sandboxing)
* You can now pass and use objects of components: if you pass
`components={{theme}}`, where `theme` is an object with a `Box` component,
it can be used with: `<theme.Box>stuff</theme.Box>`
Expand Down
8 changes: 7 additions & 1 deletion packages/mdx/lib/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export function createProcessor(options = {}) {
format,
outputFormat,
providerImportSource,
overrideBuiltIn,
recmaPlugins,
rehypePlugins,
remarkPlugins,
Expand Down Expand Up @@ -122,7 +123,12 @@ export function createProcessor(options = {}) {
pipeline
.use(rehypeRecma)
.use(recmaDocument, {...rest, outputFormat})
.use(recmaJsxRewrite, {development, providerImportSource, outputFormat})
.use(recmaJsxRewrite, {
development,
providerImportSource,
outputFormat,
overrideBuiltIn
})

if (!jsx) {
pipeline.use(recmaJsxBuild, {outputFormat})
Expand Down
18 changes: 13 additions & 5 deletions packages/mdx/lib/plugin/recma-jsx-rewrite.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
* @property {boolean} [development=false]
* Whether to add extra information to error messages in generated code (can
* also be passed in Node.js by setting `NODE_ENV=development`).
* @property {boolean} [overrideBuiltIn=false]
* Allow MDXProvider to override literal HTML elements if provided in `components`
*
* @typedef StackEntry
* @property {Array<string>} objects
Expand Down Expand Up @@ -59,7 +61,8 @@ const own = {}.hasOwnProperty
* @type {import('unified').Plugin<[RecmaJsxRewriteOptions]|[], Program>}
*/
export function recmaJsxRewrite(options = {}) {
const {development, providerImportSource, outputFormat} = options
const {development, providerImportSource, overrideBuiltIn, outputFormat} =
options

return (tree, file) => {
// Find everything that’s defined in the top-level scope.
Expand Down Expand Up @@ -180,10 +183,15 @@ export function recmaJsxRewrite(options = {}) {
fnScope.components.push(id)
}
}
}
// @ts-expect-error Allow fields passed through from mdast through hast to
// esast.
else if (node.data && node.data._mdxExplicitJsx) {
} else if (
// @ts-expect-error Allow fields passed through from mdast through hast to
// esast.
node.data &&
// @ts-expect-error Allow fields passed through from mdast through hast to
// esast.
node.data._mdxExplicitJsx &&
!overrideBuiltIn
) {
// Do not turn explicit JSX into components from `_components`.
// As in, a given `h1` component is used for `# heading` (next case),
// but not for `<h1>heading</h1>`.
Expand Down
33 changes: 32 additions & 1 deletion packages/mdx/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,38 @@ When in the `classic` runtime, this is used to import the `pragma` function.
To illustrate with an example: when `pragma` is `'a.b'` and `pragmaImportSource`
is `'c'` this following will be generated: `import a from 'c'`.

See `options.pragma` for an example.
###### `options.overrideBuiltIn`

By default `mdx-js/mdx` will never use `useMDXComponents` in order to override
a native HTML element like `<h1>`. If `<h1>hi</h1>` should behave the same as
`# hi` then you’ll want to pass `options.overrideBuiltIn=true`.

<details>
<summary>Example</summary>

```js
compile('<h1>hi</h1>\n# hi', {overrideBuiltIn: true})
```

…yields this difference:

```diff
/*@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({
h1: "h1"
}, props.components);
- return <><h1>{"hi"}</h1>{"\\n"}<_components.h1>{"hi"}</_components.h1></>;
+ return <><_components.h1>{"hi"}</_components.h1>{"\\n"}<_components.h1>{"hi"}</_components.h1></>;
}
}
export default MDXContent;
```

</details>

###### Returns

Expand Down
21 changes: 21 additions & 0 deletions packages/react/test/test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,27 @@ test('should support components as a function', async () => {
)
})

test('should support overriding literal HTML elements with components', async () => {
const {default: Content} = await evaluate('<h1>test</h1>\n# test', {
...runtime,
useMDXComponents,
overrideBuiltIn: true
})

assert.equal(
renderToString(
<MDXProvider
components={{
h1: () => <h1>overridden</h1>
}}
>
<Content />
</MDXProvider>
),
'<h1>overridden</h1>\n<h1>overridden</h1>'
)
})

test('should support a `disableParentContext` prop (sandbox)', async () => {
const {default: Content} = await evaluate('# hi', {
...runtime,
Expand Down