docs icon indicating copy to clipboard operation
docs copied to clipboard

[md/mdx] components export for `mdx` in integration guide

Open kylebutts opened this issue 1 year ago • 4 comments

One thing I really like about MDX is that you can pass custom components for default html elements (h1/6, img, a, code, pre, etc.) with export const components = {}. The names comes from the Table of Components for MDX. Much easier than writing remark/rehype plugins IMO.

There's an example of it here, but could be nice to highlight in the docs: https://stackblitz.com/github/withastro/astro/tree/latest/examples/with-mdx?file=src%2Fpages%2Findex.mdx

@bholmesdev, Would you welcome an additional section in the MDX integration guide? I was told that you were working on those docs, so I don't want to interfere or do work you may already have done!

kylebutts avatar Aug 08 '22 20:08 kylebutts

Adding to this, you can also do this after importing MDX:

---
import Content from '../content.mdx';
import Heading from '../Heading.astro';
---

<Content components={{ h1: Heading }} />

This is particularly useful in a dynamic route where you can glob all your .mdx content and render each to its own page while passing in default replacement components. (This is how I handled custom image components on the new astro.build site.)

---
// src/pages/[slug].astro
import path from "node:path";
import Layout from "../layouts/Layout.astro";
import Heading from "../components/Heading.astro";

export async function getStaticPaths() {
  const posts = await Astro.glob('../content/*.mdx');
  return posts.map(post => ({
    params: { slug: path.parse(post.file).name },
    props: post,
  }));
}
---

<Layout>
  <Astro.props.default components={{ h1: Heading }} />
</Layout>

delucis avatar Aug 12 '22 15:08 delucis

Thanks @kylebutts, @delucis! This sounds useful, and I'd absolutely want @bholmesdev's input on this.

We are actively (continuously? 😅) working on our MD/MDX documentation right now, including some reorganizing of content, so there is stuff happening all the time, and yes, I think adding halpful patterns is right up our alley at the moment!

sarah11918 avatar Aug 15 '22 12:08 sarah11918

@sarah11918 I totally agree we should document this, especially @delucis' components-as-props snippet above. We've gotten enough support questions on component rendering that warrants some docs. This could slide into the integration README for now, but I know we've talked about "proper" MDX docs separate from the README too. Worth aligning there.

bholmesdev avatar Aug 15 '22 13:08 bholmesdev

Want me to add a draft gist @sarah11918?

kylebutts avatar Aug 15 '22 15:08 kylebutts

@kylebutts That would be great! Do your worst! Thanks for offering. 🧑‍🚀

sarah11918 avatar Aug 20 '22 16:08 sarah11918

I'm not sure where this would go, maybe after Layouts in the mdx integration?

Custom components

Under the hood, MDX will convert markdown into html components. For example,

> A blockquote with *some* emphasis.

will be converted into

<blockquote>
  <p>A blockquote with <em>some</em> emphasis.</p>
</blockquote>

MDX provides a way to tap into this rendering and use your own custom components. In the above example, you could create a custom Blockquote component (in any language) that has either a <slot /> component or accepts a children prop. Then in the MDX file you import the component and export it to the components export.

import Blockquote from '../components/Blockquote.astro';
export const components = { blockquote: Blockquote };

The advantage of this is it allows for the simplicity of writing in markdown without having to write the custom component or writing a remark/rehype plugin. A full list of components that can have custom components is on the [MDX website]https://mdxjs.com/table-of-components/).

Custom components with imported mdx

Custom components can also be passed to the components prop when rending imported MDX content.

---
import Content from '../content.mdx';
import Heading from '../Heading.astro';
---

<Content components={{ h1: Heading }} />

This is particularly useful in a dynamic route where you can glob all your .mdx content and render each to its own page while passing in default replacement components.

---
// src/pages/[slug].astro
import path from "node:path";
import Layout from "../layouts/Layout.astro";
import Heading from "../components/Heading.astro";

export async function getStaticPaths() {
  const posts = await Astro.glob('../content/*.mdx');
  return posts.map(post => ({
    params: { slug: path.parse(post.file).name },
    props: post,
  }));
}
---

<Layout>
  <Astro.props.default components={{ h1: Heading }} />
</Layout>

kylebutts avatar Aug 22 '22 19:08 kylebutts

@kylebutts Couldn't have written this section better myself 🤩 My only critique is the "This is particularly useful in a dynamic route..." concluding section feels pretty advanced. I might want to keep that part out, and put the rest just after the "layouts" section as you suggested. Happy to review if you submit a PR for this!

bholmesdev avatar Aug 26 '22 21:08 bholmesdev

(Reminder to self that this has been PR'd in withastro/astro!)

sarah11918 avatar Aug 30 '22 11:08 sarah11918

Closing as withastro/astro#4530 has been merged 🥳

delucis avatar Sep 03 '22 19:09 delucis

Thank you for the documentation.

I wondered if there is a way how to set the component overrides globally?

I would like to automatically replace <img /> tags with <Image /> in my mdx files.

jtomek avatar Sep 03 '22 19:09 jtomek

Hi @jtomek! The best place to ask this is in our Discord: https://astro.build/chat

If there isn't documentation that answers your question on this page: https://docs.astro.build/en/guides/integrations-guide/mdx/ or in MDX's documentation itself, then asking in our support-threads channel is the best place to have this discussion!

sarah11918 avatar Sep 03 '22 19:09 sarah11918

Hello @sarah11918 , thank you. I will ask there.

jtomek avatar Sep 03 '22 19:09 jtomek

I wondered if there is a way how to set the component overrides globally?

@jtomek I think the closest we have at the moment is what I describe above in my comment — importing all your MDX files and then rendering them by passing in the component overrides.

But as @sarah11918 says, Discord would be a great way to ask others and chat about solutions. Or otherwise a discussion in https://github.com/withastro/rfcs would be the place to start thinking of ways to support this.

delucis avatar Sep 03 '22 19:09 delucis

I think it would be a natural extension to include custom MDX components in the layout component. I can make an RFC for this if folks agree?

kylebutts avatar Sep 03 '22 19:09 kylebutts

Definitely worth at least a discussion in the RFCs repo!

delucis avatar Sep 03 '22 20:09 delucis