Adding Line Numbers and Code Highlighting to MDX
Tags: gatsby, mdx, codeblock, highlighting
Published: 2021-08-06 13:55:28

In this very short quick tip you'll learn how to set up code blocks in [MDX](https://mdxjs.com/) and Gatsby that support line numbers and code highlighting using the code renderer [prism-react-renderer](https://github.com/FormidableLabs/prism-react-renderer). A preview can be found on [CodeSandbox](https://codesandbox.io/s/mdx-line-numbers-code-highlighting-99wb5).

First, make sure that you have a MDX blog set up. If you have that already, you can skip to the packages. If not, you should first read [Getting Started with MDX](https://www.gatsbyjs.com/docs/mdx/getting-started/) on Gatsby's documentation.

Install the necessary packages for this quick tip:

```sh
npm install mdx-utils prism-react-renderer
```

Create a Code React component in `src/components/code.js` and leave the file empty for now.

Also create a CSS file:

```css
html,
body {
  margin: 0;
  padding: 0;
}

html {
  font-family: sans-serif;
  -ms-text-size-adjust: 100%;
  -webkit-text-size-adjust: 100%;
}

.prism-code {
  font-size: 1rem;
  padding-top: 1rem;
  padding-bottom: 1rem;
  -webkit-overflow-scrolling: touch;
  background-color: transparent;
  overflow: initial;
}

.token {
  display: inline-block;
}

p > code,
li > code {
  background: rgb(1, 22, 39);
  color: rgb(214, 222, 235);
  padding: 0.4em 0.3rem;
}

.gatsby-highlight {
  font-size: 1rem;
  position: relative;
  -webkit-overflow-scrolling: touch;
  overflow: auto;
}

gatsby-highlight > code[class*="language-"],
.gatsby-highlight > pre[class*="language-"] {
  word-spacing: normal;
  word-break: normal;
  overflow-wrap: normal;
  line-height: 1.5;
  tab-size: 4;
  hyphens: none;
}

.line-number-style {
  display: inline-block;
  padding-left: 1em;
  padding-right: 1em;
  width: 1.2em;
  user-select: none;
  opacity: 0.3;
  text-align: center;
  position: relative;
}

.highlight-line {
  background-color: rgb(2, 55, 81);
  border-left: 4px solid rgb(2, 155, 206);
}

.highlight-line .line-number-style {
  opacity: 0.5;
  width: calc(1.2em - 4px);
  left: -2px;
}
```

Switch to your file that contains the `MDXProvider`. If you use e.g. the `defaultLayouts` option inside `gatsby-plugin-mdx` of `gatsby-config.js`:

```js
module.exports = {
  plugins: [
    {
      resolve: "gatsby-plugin-mdx",
      options: {
        defaultLayouts: {
          default: require.resolve("./src/components/layout.js"),
        },
      },
    },
  ],
}
```

You'll need to add the `MDXProvider` and the rest of the following code (it's essentially the components placed into the wrapping `MDXProvider`) to your Layout file:

```js
import * as React from "react"
import { MDXProvider } from "@mdx-js/react"
import { preToCodeBlock } from "mdx-utils"
import Code from "./code"
import "./layout.css"

const components = {
  pre: (preProps) => {
    const props = preToCodeBlock(preProps)
    if (props) {
      return <Code {...props} />
    } else {
      return <pre {...preProps} />
    }
  },
}

const Layout = ({ children }) => (
  <MDXProvider components={components}>
    <div style={{ margin: "0 auto", maxWidth: 960, padding: "2rem" }}>
      {children}
    </div>
  </MDXProvider>
)

export default Layout
```

The important bit is that you pass `components` into the provider and the previously created Code React component is used.

Add the following to said component:

```js
import * as React from "react"
import Highlight, { defaultProps } from "prism-react-renderer"
import theme from "prism-react-renderer/themes/nightOwl"

const RE = /{([\d,-]+)}/

const calculateLinesToHighlight = (meta) => {
  if (!RE.test(meta)) {
    return () => false
  }
  const lineNumbers = RE.exec(meta)[1]
    .split(`,`)
    .map((v) => v.split(`-`).map((x) => parseInt(x, 10)))
  return (index) => {
    const lineNumber = index + 1
    const inRange = lineNumbers.some(([start, end]) =>
      end ? lineNumber >= start && lineNumber <= end : lineNumber === start
    )
    return inRange
  }
}

const Code = ({ codeString, language, metastring, ...props }) => {
  const shouldHighlightLine = calculateLinesToHighlight(metastring)

  return (
    <Highlight
      {...defaultProps}
      code={codeString}
      language={language}
      theme={theme}
      {...props}
    >
      {({ className, style, tokens, getLineProps, getTokenProps }) => (
        <div className="gatsby-highlight" data-language={language}>
          <pre className={className} style={style}>
            {tokens.map((line, i) => {
              const lineProps = getLineProps({ line, key: i })

              if (shouldHighlightLine(i)) {
                lineProps.className = `${lineProps.className} highlight-line`
              }

              return (
                <div {...lineProps}>
                  <span className="line-number-style">{i + 1}</span>
                  {line.map((token, key) => (
                    <span {...getTokenProps({ token, key })} />
                  ))}
                </div>
              )
            })}
          </pre>
        </div>
      )}
    </Highlight>
  )
}

export default Code
```

The `calculateLinesToHighlight` helper function gets the `metastring` as an input which is the notation for the line highlighting. As you can see in the linked [CodeSandbox](https://codesandbox.io/s/mdx-line-numbers-code-highlighting-99wb5) example you can write `{1,9-12}` to highlight the first and 9th to 12th line.