Skip to content

Commit

Permalink
Add guide on injecting components
Browse files Browse the repository at this point in the history
  • Loading branch information
wooorm committed Oct 24, 2023
1 parent f48d038 commit a9f0c04
Showing 1 changed file with 120 additions and 0 deletions.
120 changes: 120 additions & 0 deletions docs/guides/injecting-components.mdx
@@ -0,0 +1,120 @@
export const info = {
author: [
{github: 'wooorm', name: 'Titus Wormer', twitter: 'wooorm'}
],
modified: new Date('2023-10-24'),
published: new Date('2023-10-24')
}
export const navSortSelf = 7

# Injecting components

This guide shows how to inject arbitrary components into MDX when it
runs. {/* more */}
It shows how the underlying features used by our providers (`@mdx-js/react`,
`@mdx-js/preact`) and the [`mdx-components.tsx`][next-mdx-components] file
supported by Next.js work,
and how you can take advantage of that functionality yourself.

In many cases you do not need this,
as you can pass components to MDX:

```mdx path="example.mdx"
# Hello *<Planet />*
```

You can pass `Planet` and say a component used instead of the `h1`:

```tsx path="example.jsx"
import Example from './example.mdx' // Assumes an integration is used to compile MDX -> JS.
<Example
components={{
Planet() {
return 'Pluto'
},
h1(props) {
return <h2 {...props} />
}
}}
/>
```

When you find yourself passing that `components` prop around a lot,
you might want to look at an alternative.
You might reach for our context based providers (`@mdx-js/react`,
`@mdx-js/preact`),
but context has performance downsides and context doesn’t always work (such as
in RSC).

But first,
how does component passing work?
That can be illustrated by looking at the code generated by MDX for the above
`example.mdx`.
Here is a diff that shows what the example normally compiles to and what
changes when `providerImportSource: 'xxx'` is passed:

```diff
@@ -1,7 +1,13 @@
import {jsx as _jsx, jsxs as _jsxs} from 'react/jsx-runtime'
+import {useMDXComponents as _provideComponents} from 'xxx'

function _createMdxContent(props) {
- const _components = {em: 'em', h1: 'h1', ...props.components}
+ const _components = {
+ em: 'em',
+ h1: 'h1',
+ ..._provideComponents(),
+ ...props.components
+ }
const {Planet} = _components
if (!Planet) _missingMdxReference('Planet', true)
return _jsxs(_components.h1, {
@@ -10,7 +16,7 @@ function _createMdxContent(props) {
}

export default function MDXContent(props = {}) {
- const {wrapper: MDXLayout} = props.components || {}
+ const {wrapper: MDXLayout} = {..._provideComponents(), ...props.components}
return MDXLayout
? _jsx(MDXLayout, {...props, children: _jsx(_createMdxContent, {...props})})
: _createMdxContent(props)
```

Observe that components have defaults (such as that `h1` will use `'h1'`) and
that components are taken from `props.components`.
What changes is an added call to `_provideComponents`,
which refers to an `useMDXComponents` export from the module we specified
(`xxx`).

We can use this interface to inject components from a file.
In that file,
we need a `useMDXComponents` function that returns our components.

```tsx path="mdx-components.js"
/** @returns {import('mdx/types.js').MDXComponents} */
export function useMDXComponents() {
return {
Planet() {
return 'Pluto'
},
h1(props) {
return <h2 {...props} />
}
}
}
```

And now passing a file path or URL to that file as `providerImportSource`,
such as with `import.meta.resolve('./mdx-components.js')`:

```diff
@@ -1,5 +1,5 @@
import {jsx as _jsx, jsxs as _jsxs} from 'react/jsx-runtime'
-import {useMDXComponents as _provideComponents} from 'xxx'
+import {useMDXComponents as _provideComponents} from 'file:///Users/tilde/…/mdx-components.js'
```

Now our locally defined components will be used in all MDX files!

[next-mdx-components]: https://nextjs.org/docs/pages/building-your-application/configuring/mdx

0 comments on commit a9f0c04

Please sign in to comment.