Introducing MDXC: A new way to write Markdown for React

Read the README and browse the source on GitHub, or mess around with the live demo.

Markdown is a fantastic tool. It makes writing simple. Markdown documents are plain text, so you don’t need to worry about tags and HTML and all that. You just write. And when you need a <a>, or a <h1>, it’s already there. It’s like magic!

But say you’re writing a React app with pushState, and you don’t want your links to use <a> tags; they break your smooth navigation. You want them to use react-router or react-junctions’ <Link> tags instead. And you’re shit outta luck, because you can’t. Or you couldn’t, before MDXC.

MDXC is a tool to make Markdown and React best buddies. Unlike traditional Markdown, MDXC produces actual JavaScript — not strings of HTML. MDXC lets you embed raw JSX within your Markdown, and even lets you import components and accept props. And to top it all off, MDXC lets you configure how each element is rendered, giving you all sorts of ways to confuse your users and also a few ways to genuinely improve their experience.

If you can’t tell yet, I’m pretty excited about MDXC. So please indulge me and take a gander at this example:

Example 0: Hello, {yourName}!

One of the things about React components is that they take props. And since MDXC produces React Components, MDX documents can have props too!

To use a prop within your document, just declare it at the top of your MDX file with a prop <propname> line. Once declared, it will be available within any {brace} expressions.

But how do brace expressions work? Actually, they’re almost the same as brace expressions in JSX. In fact, when your paragraph starts and ends with a matching pair of tags, they’re exactly the same — the entire paragraph is processed as JSX. For example:

prop value
prop onChange

# Hello!

May I kindly ask your name?

<input
  value={value}
  onChange={onChange}
/>

<p>{value && `Thanks, ${value}!`}</p>

MDXC will compile the above document to something like this:

import { createElement, createFactory } from 'react'

export default function({ factories={}, value, onChange }) {
    const wrapper = factories.wrapper || createFactory('div')
    const h1 = factories.h1 || createFactory('h1')
    const p = factories.p || createFactory('p')

    return wrapper({},
        h1({}, 'Hello!'),
        p({}, "May I kindly ask your name?"),
        createElement("input", {
            value: value,
            onChange: onChange
        })
        createElement("p", null, value && `Thanks, ${value}!`),
    )
}

But why is it that one of our paragraphs compiles to createElement("p", ...), while the other compiles to p(...)?

Factories vs. Elements

When you use <tags> in an MDX document, they’re treated exactly like tags in JSX. That is, they’re compiled to React.createElement(...) calls.

But what about the elements that are generated by Markdown itself; for example, <p> and <h1> elements? As you might have noticed from the example above, this markup is actually produced by factory functions. These functions take props and children, and return a ReactElement. Each factory function is kind of like a special createElement call for a single type of tag.

The advantage of factory functions is that they’re configurable. Unfortunately, this does add a teensy bit of complexity to the generated components. But this is ok, because it can also add a whole lot of awesome to your users’ experience.

Using <Link> components with Markdown

If you’re loading content within a React app, then in all likelihood your app is using HTML5 history through a library like react-router or react-junctions.

The great thing about HTML5 history is that it provides a way to navigate without reloading the page. The crap thing about it is that ye olde’ <a> tags will still reload the page.

If you want your content to feel snappy and nice, you’ll want to use <Link> elements instead. And happily, this is as simple as wrapping your Document component and replacing the default <a> factory with a <Link> factory.

// Assume './document' is a file generated by mdxc
import Document from './document'
import React, { createFactory } from 'react'
import { Link } from 'react-router'

export default function DocumentWrapper(props) {
  return (
    <Document
      {...props}
      factories={{ a: createFactory(Link) }}
    />
  )
}

Of course, the usefulness of factories is not limited to links. You can also use them to do cool things like adding anchor links to your headings, or even to replace code blocks with code editors. The world is your burrito.

MDX also let’s you do a bunch of other cool things — you can import external React components and even write their children in Markdown! But a reader can only stand so much excitement at once, and the docs are waiting for you 24-7. So let’s move on to how to actually use MDXC.

How to actually use MDXC

Why, I thought you’d never ask!

A tool wouldn’t be a tool if it didn’t include a command-line tool. So let’s start with the command line tool.

# Install the `mdxc` command line tool using npm
> npm install -g mdxc

# Create a boring markdown file for an example
echo '# Hello, World' > example.mdx

# Pass the boring example to the command line tool to compile it
mdxc example.mdx

# You'll forget all this anyway so just use `--help`
mdxc --help

But with that out of the way, let’s talk about how you should actually use MDXC.

Webpack

If you’d like to use MDX within an existing React app, chances are you’ll want to use mdx-loader. To do so, just add it to your project and then update the your webpack.config.js. Bear in mind that MDXC outputs ES2015, so you’ll need to run the output through Babel if you want to support older browsers or Webpack 1.

# Add mdx-loader to your project
npm install --save-dev mdx-loader

Assuming you’re using Webpack 2, you’ll need to add an entry to your module.rules array:

module: {
  rules: [
    /**
     * MDX is a tool that converts Markdown files to React components. This 
     * loader uses MDX to create Page objects for Markdown files. As it
     * produces ES2015, the result is then passed through babel.
     */
    { test: /\.mdx?$/,
      use: [
        'babel-loader',
        'mdx-loader',
      ]
    },

    // ...
  ]
},

Then import and use your components as you’d do with standard JavaScript!

But what if you haven’t got an existing React app? Or what if you don’t want an app, but want a website? In that case, I have another treat for you.

Sitepack

MDXC was originally created for Sitepack. But what is Sitepack?

Sitepack is a wrapper around Webpack. It gives you a way to add pages to your website using plain-old require(), it handles the nasty parts of Webpack configuration for you, and it does all this while performing the magic required to build a static HTML version of each of your pages. As it happens, my MDXC and junctions.js websites are built with Sitepack.

Sitepack’s documentation is still in its infancy (I’ll let you know via my Newsletter when it improves). But if your plan is to use MDX to write an actual website (you know, with pages and links and no “login” button), it is definitely worth trying out. To do so, use the sitepack-react-starter-kit — it supports MDX out of the box.

git clone https://github.com/jamesknelson/sitepack-react-starter-kit.git
cd sitepack-react-starter-kit
npm install
npm start

Once you’ve cloned, installed and started your Sitepack website, open your browser at http://localhost:4000 and start editing the md files in the content directory — changes will be reflected live! When you’re ready to release, build your site with npm run build. You’ll be a webmaster faster than you can say 1995.

The API. Or, class MDXC extends MarkdownIt

At its core, MDXC is just a wrapper around the excellent and highly configurable markdown-it project. This means MDXC has an API. It also means that the API is excellent and highly configurable. But probably not as excellent or highly configurable as markdown-it.

I won’t go into any more details here, but if you’re the type of person who likes details then the README has a little more info.

Contributing

MDXC is a young project. That means a lot of it is still pretty shit. Help would be greatly appreciated, especially in the following areas:

  • Improved tests
  • Refactoring existing code to be prettier and faster
  • Generating more concise JavaScript output

If you’d like to contribute, you can create an Issue or PR on GitHub.

But if tests and refactoring aren’t your thing, I’d still be pretty darn happy if you use MDXC to make awesome stuff. Or quirky stuff. Or whatever, really. Then tell me about what you’ve made so I can add it to the list.

Finally, if you’re not one of the over 9000 people who’ve joined already, join my Newsletter! I don’t send many e-mails. But when I do send e-mails, they’re guaranteed to contain useful information and/or tools that you don’t want to miss. And in return for your e-mail, you’ll get a bunch of free React and JavaScript cheatsheets the moment you join.

I will send you useful articles, cheatsheets and code.

I won't send you useless inbox filler. No spam, ever.
Unsubscribe at any time.

Oh, and one more thing – I love hearing your questions, offers and opinions. If you have something to say, leave a comment or send me an e-mail at james@jamesknelson.com. I’m looking forward to hearing from you!

Leave a Reply

Your email address will not be published.