From a9f0c046bb4cdd4add24648cb7be70739399e62e Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Tue, 24 Oct 2023 16:52:44 +0200 Subject: [PATCH] Add guide on injecting components --- docs/guides/injecting-components.mdx | 120 +++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 docs/guides/injecting-components.mdx diff --git a/docs/guides/injecting-components.mdx b/docs/guides/injecting-components.mdx new file mode 100644 index 000000000..bcd5fced8 --- /dev/null +++ b/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 ** +``` + +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. + + + } + }} +/> +``` + +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

+ } + } +} +``` + +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