This article was originally written in the 20s numbers of September, 2022 and was covering gatsby-plugin-mdx
v4 which is using new MDX v2.
Later in May, 2024 I’ve updated the article when I upgraded my own setup from gatsby-plugin-mdx
to v5 as well as prism-react-renderer
from ^1.0.0 to ^2.0.0. The gatsby-plugin-mdx
does not require any migration steps if upgrading from v4 to v5. However, to use prism-react-renderer@^2.0.0
if used to use ^1.0.0
you’d need to change a few things. I’ve updated the article to reflect those changes. Any time prism-react-renderer
code is showcased, it will be followed by a spoiler with changes required from the previous version.
If you are migrating gatsby-plugin-mdx
v3 to v4 there is a list of breaking changes
Intro
This website is written using GatsbyJs. Technology behind gatsby is ReactJs. I am quite new to both, which isn’t surprising since I’m mainly a backend engineer. As such, it took me quite a while to sort things out, and I wanted to share some gotchas.
I started off with a gatsby-starter-blog preset which is using gatsby-remark-prismjs as a plugin to gatsby-transformer-remark.
This means gatsby-transformer-remark
will transform blog posts written in markdown
to HTML while during the transformation the gatsby-remark-prismjs
plugin will take care of the code blocks.
The problem is very soon markdown feels too restricting. If you ever used something like notion or confluence, or even wordpress, you know it’s super easy to use beautiful callouts, different styles of quotations, different colors and backgrounds for text, etc. Markdown does not have special syntax for those.
After a little googling I stumbled upon abandoned gatsby-remark-custom-blocks which has a dependency on an equally as abandoned gatsby-remark-blocks and ruled it out as a possible solution.
Luckily a better solution was just around the corner. I mean, in the gatsby plugins library:
MDX = MD + JSX
MDX allows JSX embeds in Markdown files. Very elegant and powerful solution. Just an example of what it might look like:
## Using mdx
Head over to [mdxjs website](https://mdxjs.com/) for comprehensive intro.
<Callout icon={warn} bgColor="yellow">If you are using mdx v1, please make sure to upgrade to v2, it's 30% faster!</Callout>
<WrappedQuote> > To write and enjoy MDX, you should be familiar with both markdown and JavaScript (specifically JSX) <QuoteSource>[official mdx documentation](https://mdxjs.com/docs/what-is-mdx/)</QuoteSource><WrappedQuote>
I haven’t tried it yet, but it looks like it means I can use any antd component (or material UI, or chakra-ui, or any other react component library) inside my .md(x)
files. What more to dream about? SOLD!
Caveats using with gatsby and gatsby-remark-prismjs
I’ve create a mdx
branch in this website’s git repo and very quickly carved out gatsby-transformer-remark
. Thankfully, gatsby has an awesome blog post on how to migrate from remark to mdx.
BUT. If there wasn’t this but…
I noticed my inline code blocks
are screwed. Specifically, they are wrapped in divs
, and as such they aren’t very “inline” anymore. Apparently, when used with gatsby-remark-prismjs
, new version of mdx plugin (the one that is using mdx 2) will grab the results and visit all the html nodes (all the nodes that aren’t recognized as JSX components) and wrap them in a div
. I will not go into whys, but if you are curious, it’s right there - in the source code of the plugin.
I didn’t see a non-hacky way to circumvent this. I could’ve gone with a hacky one, but I just don’t like hacky ways. I’d rather find a proper solution once and wouldn’t have to maintain hacks.
Proper solution definition
Alright, alright, alright. So I’m not askin for much. All I want is:
Inline
code highlighting- Several languages support (python, php, go, js, css, xml, etc)
- Ability to highlight specific lines (using range notion like
{1,2-10,33-35}
)
Sounds easy? Let’s dig into this.
Migrating from gatsby-transformer-remark
to gatsby-plugin-mdx
This part is pretty straightforward and described quite well in the Migrating Remark to MDX post on gatsbyjs.com. One caveat which is NOT described there is a bug in gatsby that is preventing named GraphQL queries in templates. The issues is described here and at the moment of writing (22st of Sep, 2022) there is an open PR to fix this issue.
The issue, if exists on your site, will manifest itself with an error page which will say something like this:
Multiple "root" queries found: "PostById" and "PostById".
Only the first ("PostById") will be registered.
In case it happens, go to that component, and simply remove the name of the query, i.e.
export const pageQuery = graphql`- query PostById (+ query ( $id: String! $previousPostId: String
Migrating from gatsby-remark-prismjs
to prism-react-renderer
Once remark to MDX migration was done, I noticed all my inline codes are wrapped in div
s. I might solve it by intervening into mdx-to-html compilation process, but then I’ve found a solution that would satisfy me more. And the solution was to use prism-react-renderer
. The idea is to prodive <code>
and <pre>
components into an mdx
document. Of course, we will be in cotrol of how those are rendered. Inline code will be compiled into html as <code>inline code</code>
, whereas multiline code blocks will be more complex, and that’s where the prism-react-renderer
will come into play.
How do we render multiline blocks with prism-react-renderer?
We need to be able to compile something like this
# Function for nth Fibonacci number
def fib(n):
""" @TODO nahdle n < 0 """
if n == 0:
return 0
elif n == 1 or n == 2:
return 1
else:
return fib(n-1) + fib(n-2)
to something like this
# Function for nth Fibonacci numberdef fib(n): """ @TODO nahdle n < 0 """ if n == 0: return 0 elif n == 1 or n == 2: return 1 else: return fib(n-1) + fib(n-2)
There are many articles demonstrating how to use prism-react-renderer
available online. I’ve been following this one by Paul Scanlon (if you are not using tailwind.css, completely misregard it). What are we interested in is the PrismSyntaxHighlight
component, which I simply copied over. One thing that I’ve done was removing theme (I feel a little more comfortable with defining colors in my src/styles.css
).
The code may look a little mouthful and unwieldy, but it allows for a great control over what can you do inside the code blocks.
You can have additional business-logic for every single line, and for every single token.
You will see how it’s useful in the later part of this article.
import React from 'react';import { Highlight } from 'prism-react-renderer';
const PrismSyntaxHighlight = ({ children, className }) => { const language = className.replace(/language-/gm, '');
return ( <Highlight code={children} language={language}> {({ className, tokens, getLineProps, getTokenProps }) => { return (<code className={className}> {tokens.slice(0, -1).map((line, i) => { const lineProps = getLineProps({ line, key: i }) delete lineProps.style return ( <div key={i} {...lineProps}> {line.map((token, key) => { const tokenProps = getTokenProps({ token, key }) delete tokenProps.style return ( <span {...tokenProps} /> ) })} </div> ) })} </code>) }} </Highlight> );};
export default PrismSyntaxHighlight;
Oh, by the way, you didn’t forget to install dependencies, did you? If you did, here is the one-liner:
npm i prism-react-renderer prismjs @mdx-js/react gatsby-plugin-mdx
Now what’s been achieved is our block renders to an HTML which is looking something like this:
<code class="prism-code language-python"> <div class="token-line"> <span class="token comment"># Function for nth Fibonacci number</span> <span class="token plain"></span> </div> <div class="token-line"> <span class="token plain"></span> <span class="token keyword">def</span> <span class="token plain"></span> <span class="token function">fib</span> <span class="token punctuation">(</span> <span class="token plain">n</span> <span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token plain"></span> </div>
<!-- some more html... -->
</code>
As I said, I’ve removed theme that come with prism-react-renderer
, I’m preferring to style the code in the src/style.css
:
.token.tag,.token.attr-name,.token.namespace,.token.deleted { color: var(--code-color-fg);}
.token.function-name { color: var(--code-color-fg);}
.token.function { color: var(--code-color-blue);}
This method seems more straightforward to me and gives me fine control over how exactly I want the code to appear on the website.
“Inject” PrismSyntaxHighlight
into MDX
This is the easiest part. Just provide <code>
and/or <pre>
components into the mdx document via the MDXProvider
. Here is a snippet from my src/templates/post.js
import { MDXProvider } from '@mdx-js/react';import PrismSyntaxHighlight from "../components/highlight"
const components = { pre: ({children, className}) => { return (<div className="code-container"><pre>{children}</pre></div>) }, code: ({children, className}) => { return className ? ( <PrismSyntaxHighlight className={className}>{children}</PrismSyntaxHighlight> ) : <code className="language-text">{children}</code> }}
// ... JSX:
<section itemProp="articleBody"> <MDXProvider components={components}>{children}</MDXProvider></section>
This takes case of both multiline code blocks and inline codes.
Languages support
Prism supports many languages, but we need to import them explicitly. This is what I’ve for in my gatsby-browser.js
:
import { Prism } from "prism-react-renderer";
(typeof global !== "undefined" ? global : window).Prism = Prism
// First import prism-markup-templating because otherwise it breaks php which depends on it.// https://github.com/PrismJS/prism/issues/2769await import('prismjs/components/prism-markup-templating')await import('prismjs/components/prism-go')await import('prismjs/components/prism-php')await import('prismjs/components/prism-python')// etc.
Highlighting specific lines
gatsby-remark-prismjs
has a support for highlighting specific lines of code with a special syntax that specifies those lines as a range which looks something like this python{1,2,20-22}
. This syntax means that lines number 1, 2, 20, 21, and 22 should be highlighted.
It doesn’t work with the above componenent, what gets rendered instead is something like this:
<code class="prism-code language-python{1,2,20-22}">
So, apparently I need to take the range out of the language name, interpret it and highlight those specific rows myself. Quite easy with parse-numeric-range which is a js package that is capable of interpreting that exact range format. The plan is simple now:
- take out the range definition from the classname
- use
parse-numeric-range
to parse it - add a
highlight-line
class to the highlighted lines of code
I’ll get straight into the soltuion and add comments to the code so that if you are interested, you can get a holistic view
import React from 'react';import rangeParser from 'parse-numeric-range';import Highlight, { defaultProps } from 'prism-react-renderer';
const calculateLinesToHighlight = (meta) => { const RE = /{([\d,-]+)}/ if (RE.test(meta)) { const strlineNumbers = RE.exec(meta)[1] const lineNumbers = rangeParser(strlineNumbers) return (index) => (lineNumbers.includes(index + 1)) } else { return () => false }}
const PrismSyntaxHighlight = ({ children, className }) => { var language = className.replace(/language-/gm, ''); const highlightedLinesMatch = language.match(/{[^}]+}/) let highlightLinesDefinition = '' if (highlightedLinesMatch !== null) { highlightLinesDefinition = highlightedLinesMatch[0] language = language.substr(0, highlightedLinesMatch.index) } const shouldHighlightLine = calculateLinesToHighlight(highlightLinesDefinition)
return ( <Highlight {...defaultProps} code={children} language={language}> {({ className, tokens, getLineProps, getTokenProps }) => { return (<code className={className}> {tokens.slice(0, -1).map((line, i) => { const lineProps = getLineProps({ line, key: i }) if (shouldHighlightLine(i)) { lineProps.className = `${lineProps.className} highlight-line` } delete lineProps.style return ( <div key={i} {...lineProps}> {line.map((token, key) => { const tokenProps = getTokenProps({ token, key }) delete tokenProps.style return ( <span {...tokenProps} /> ) })} </div> ) })} </code>) }} </Highlight> );};
The calculateLinesToHighlight
method has been taken from Prince Wilson’s Add line highlighting to prism-react-renderer blog post.
Now all we need is to add css styles for the highlighted lines. This is what I’ve got for it in src/styles.css
:
code.prism-code .token-line.highlight-line { background-color: var(--code-color-line); display: block; margin-right: -1em; margin-left: -1em; padding-right: 1em; padding-left: 0.75em; border-left: 0.25em solid var(--code-color-bg); border-right: 0.25em solid var(--code-color-bg);}
Conclusion
- MDX is great, I definitely enjoy how I can add anything into my markdown files, and I highly recommend moving away from just markdown on your gatsby sites
prism-react-renderer
gives a fine control over appearance of the code even though may look a little overwhelming at first (we all want “out-of-the-box” solutions, don’t we?)