mdx-js/mdx

Don't replace literal HTML elements with MDXProvider

adamwathan opened this issue · 12 comments

Right now when you provide a component mapping to an MDXProvider, every matching markdown element is replaced, even if those elements are authored in HTML instead of markdown.

For example, given this configuration:

// App.js
import { MDXProvider } from '@mdx-js/react'

const components = {
  h2: () => (<span>Boo!</span>),
}

export default props =>
  <MDXProvider components={components}>
    <div {...props} />
  </MDXProvider>

...the following MDX:

// Page.mdx
# My Page

## Should become "Boo!"

<h2>But please leave this one alone</h2>

...is rendered as:

<h1>My Page</h1>

<span>Boo!</span>

<span>Boo!</span>

This can be very surprising and challenging to work with when you are embedding components in your MDX that receive children:

// Page.mdx
import MyComponent from '../components/MyComponent'

# My Page

## Should become "Boo!"

<MyComponent>
  <h2>Please leave this alone!</h2>
</MyComponent>

I would love a way to be able to tell MDX "please only transform markdown tags, not literal HTML tags" so that literal HTML can be used as an escape hatch.

Hope this makes sense, and thanks for the awesome work on MDX!

Hmm, my initial feeling is that it makes more sense that they’re treated the same, whether they’re written as JSX or as Markdown.

If you want them rendered differently, you should be able to use a <MyH2>Please leave this alone!</MyH2> in <MyComponent>, no?

This example clearly illustrates the problem, but it’s a bit weird to map h2 elements to spans, could you expand on what the you were actually doing?

Hey thanks for following up on this with me! The specific use case was writing documentation with inline code samples (specifically I was working on a redesign of the Tailwind CSS docs), and I wanted to bake styles into my headings, paragraphs, etc., but didn't want those styles leaking into the examples:

// App.js
import { MDXProvider } from '@mdx-js/react'

const components = {
  h2: ({ children }) => (<h2 class="text-2xl font-bold mt-6 mb-4">{children}</h2),
  // ...
}

export default props =>
  <MDXProvider components={components}>
    <div {...props} />
  </MDXProvider>
// Page.mdx
# My Page

## Some documentation title

A paragraph explaining something.

<div>
  <h2>An embedded example that I don't want to have my markdown h2 styles.</h2>
</div>

Think stuff like this screenshot, where there I don't want my markdown styles interfering with my demo styles:

image

Historically I have solved this with a bunch of convoluted CSS that uses direct descendant selectors and stuff to make sure the markdown styles never leak down into the examples, but was excited about being able to ditch that with MDX.

Thanks! I think that makes sense, some form of sandboxing.

I can see how the current behavior also makes sense though, say you had a component where for whatever reason you’re not using a <h2> in a component instead of a Markdown heading, and do want them to be the same. 🤔

I agree with @adamwathan, if you want to use the markdown heading style you can simply import the react component used in the mdx provider.

The html and jsx elements should be rendered without any change in styles

if you want to use the markdown heading style you can simply import the react component used in the mdx provider

Yeah I was going to say the same thing, there's a way to get the desired behavior if the default is "don't replace normal elements" but the reverse is not true.

My mental model when first using MDX was that it was interpreting markdown and translating it to components, and I had the ability to control the components. I don't think of a raw <h2> element in a markdown file as actual markdown so I think that's why I was surprised that it was replacing those elements.

Thanks again for being open to the discussion, MDX is such cool tech 🙌

johno commented

We've definitely got this requested a few times so it's something we plan on supporting as an option in v2.

My mental model when first using MDX was that it was interpreting markdown and translating it to components, and I had the ability to control the components.

Yeah, I can empathize with this mental model. Interestingly we've found a lot of users with opposite mental model as well, and would end up confused when a h2 or p tag in an MDX document had different styling.

So, perhaps we could support opting out of HTML element rendering with an API similar to?

<MDXProvider components={myComponents} ignoreInlineTags={true}>
  {/* stuff */}
</MDXProvider>

I imagine there's probably a better prop name than ignoreInlineTags but I haven't given it a lot of thought yet. Definitely open to thoughts and suggestions!

I think skipJsxTagsStyling is a better name, but everyone knows naming is the hard part

johno commented

I think skipJsxTagsStyling is a better name

Yeahhhh, something to think about here. Styling is a bit too specific because components in MDXProvider can add more than styles (functionality and dynamic behavior). But we have time to think about naming while we work on implementation and the rest of v2 this next month.

but everyone knows naming is the hard part

✅🤣

Something like sandbox maybe?

Honestly even a Sandbox component could be an interesting angle (I like the config prop too though):

// Page.mdx
# My Page

## Some documentation title

A paragraph explaining something.

<MDXSandbox>
  <div>
    <h2>An embedded example that I don't want to have my markdown h2 styles.</h2>
  </div>
</MDXSandbox>
johno commented

I really like the idea of an MDXSandbox component since it is more composable and explicit.

johno commented

Going to close this now since it's implemented in the canary release of v2. You can take it for a spin with yarn add @mdx-js/mdx@next @mdx-js/react@next.

And then use it like so:

<MDXProvider disableParentContext={true}>
  {/* All MDX contained within will render as literal HTML elements */}
</MDXProvider>

Additionally, you can create a component if you wanted to "sandbox" elements:

export const MDXSandbox = props => <MDXProvider {...props} disableParentContext={true} />

# I'm using the `h1` from MDXProvider!

<MDXSandbox>
  <h1>I'm a literal h1!</h1>
</MDXSanbox>

Thanks for the input all!