Skip to content

RFC: Interleaving Markdown in JSX #628

Closed
@johno

Description

@johno

Summary

Interleaving Markdown and JSX in MDX has been asked for by a lot of folks and is something we should officially support. It's been on the roadmap, but we've decided to add it as a minor version after v1. Now is that time 😸

It's a common need to wrap a block of content to achieve things like image + captions, notifs, callouts, and custom background/text colors for given sections.

Basic (proposed) example

Inline JSX blocks are automatically interleaved and block-level JSX requires an blank lines to open back up Markdown parsing:

# Hello, <span>*world*</span>, from <>{props.name}</>!

<Note>

This *is* **markdown**!

</Note>
Output JSX
<MDXLayout
  {...layoutProps}
  {...props}
  components={components}
  mdxType="MDXLayout"
>
  <h1>
    {`Hello, `}
    <span>
      <em parentName="h1">{`world`}</em>
    </span>
    {`, from `}
    <>{props.name}</>
    {`!`}
  </h1>
  <Note mdxType="Note">
    <p>
      {`This `}
      <em parentName="p">{`is`}</em>
      {` `}
      <strong parentName="p">{`markdown`}</strong>
      {`!`}
    </p>
  </Note>
</MDXLayout>

Motivation

This approach is currently being used in the wild and in production. As @jxnblk mentioned previously this approach comes with the benefit that it's in the Commonmark Spec.

Detailed design

The majority of parsing is already implemented. However, there are a few things we need to be able to address.

Handling embedded expressions {props.foo}

There are two additional scenarios where we need to be able to handle embedded expressions, which all essentially boil down to inline JSX.

# Hello, from <>{props.name}</>

<Note>

Here's another expression: <>{props.thing}</>

</Note>

This can get a bit tricky because technically the following should also be handled:

# Hello, <>from {props.name}</>

Handling more complex JSX blocks

When there is indented JSX across two lines the parser currently misses it. Something that doesn't currently work, but should:

# Hello, world!

<div>
  <div>

# I should be an h1 inside two divs

  </div>
<div>

Drawbacks

This will likely make the last JSX block parsing issue more difficult. The biggest remaining block issue is where we have empty lines in the JSX. This causes parsing breakages for render props and template strings.

Render props

# Hello, world

<SomeComponent>
  {prop => {
    const newValue = doStuff(prop)

    return <h1>{newValue}</h1>
  }}
</SomeComponent>

Template strings

# Hello, world!

<SomeComponent
  someProp={`
    Here's a template string

    with empty lines
  `}
/>

Alternatives

The other main alternative we've considered is an MDX component.

# Hello, <span><MDX>*world*</MDX></span>!

<Note>
  <MDX>
    This *is* **markdown**!
  </MDX>
</Note>

An additional alternative from PHP Markdown Extra is using a markdown attribute:

# Hello, <span markdown="1">*world*</span>!

<Note markdown="1">
  This *is* **markdown**!
</Note>

This comes with the benefit of being more explicit, but it is confusing for folks coming from traditional Markdown as they slowly adopt JSX. It also feels syntactically noisy.

I've toyed with different syntax in place of MDX like aliasing it to something terser: <_>. However, this isn't as approachable.

Adoption strategy

Considering this already mostly works and is already being used (just not officially supported) the adoption strategy should be straightforward. We'd need to fix some parsing edge cases mentioned above and add documentation.

Related issues


cc/ @wooorm @jxnblk @ChristopherBiscardi @timneutkens @ChristianMurphy @silvenon

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions