Add line highlighting to prism-react-renderer
I’ve been recently migrating away from gatsby-remark-prismjs
to prism-react-renderer
, which if you’re
doing the same you can read about here! To keep the same feel for my readers (like yourself),
I needed to also make sure I could support one of my favorite features: line highlighting!
Why is Line Highlighting Important?
Now you might be wondering, “Why bother?” Well, have you ever read a blog post but felt super lost in what would be important to get out of the code blocks they shown? What about seeing big blocks of code and not quite what changed across the two? This is where I think line highlighting really helps!
It makes you, the author, pay more attention to what you need to be communicating, visually and written.
Adding Line Highlight to prism-react-renderer
When using MDX, the code
shortcode passes 3 props className
, children
, metastring
by default.
In my migration post, we used className
to display the correct styling by language and the
children
prop was the actual code we want to display. The newest prop is metastring
which is a string representation of everything following the backticks and language (THE SPACE IS IMPORTANT):
There are some limitations such as trying to render
markdown code blocks in a codeblock 😅
But ignoring the leading #, we would use this to denote a codeblock
# ```js {1,2,3-4}
This metastring can be used for all sorts of things, but for now we’ll focus on
getting out the lines! I used a package, parse-numeric-range
, that will help with parsing
ranges which you’ll want to add through either npm
or yarn
. Then in my CodeBlock
component,
I added a bit of code:
// src/components/CodeBlock.jsx
import React from 'react'
import rangeParser from 'parse-numeric-range';
import Highlight, { defaultProps } from 'prism-react-renderer'
// Create a closure that determines if we have
// to highlight the given index
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
}
}
// ...
This calculateLinesToHighlight
will actually return a function that can be executed to determine
if it is a line to highlight. This will make it so we don’t need to recompute what the range is every
time we check for a line. The only acceptable characters inside of the {}
are numbers, -
, and ,
.
Then we can go into our actual render for the codeblock and add:
// src/components/CodeBlock.jsx
// ...
const shouldHighlightLine = calculateLinesToHighlight(metastring)
return (
<Highlight {...defaultProps} code={children} language={language}>
{({ className, style, tokens, getLineProps, getTokenProps }) => (
<pre className={className} style={{ ...style }}>
{tokens.map((line, index) => {
const lineProps = getLineProps({ line, key: index })
if (shouldHighlightLine(index)) { lineProps.className = `${lineProps.className} highlight-line` }
return (
<div key={index} {...lineProps}>
{line.map((token, key) => (
<span key={key}{...getTokenProps({ token, key })} />
))}
</div>
)
})}
</pre>
)}
</Highlight>
)
If we need to highlight our line through shouldHighlightLine
, we will need to add highlight-line
to the className
string for the line. This is finally then missing what the value it should receive:
/* inside of my custom theme */
/**
* Handles the actual highlighting process.
* Feel free to customize this.
*/
.highlight-line {
background-color: rgb(53, 59, 69);
display: block;
margin-right: -1em;
margin-left: -1em;
padding-right: 1em;
padding-left: 0.75em;
border-left: 0.3em solid #f99;
}
And from there, this should now allow you to go and write your own line highlights in your code blocks! Feel free to customize your line highlighting to your needs. You will now be able to write the same things you were writing before in your markdown files in your MDX files. As a final reminder, the code blocks will need a space after you declare the language in order for the meta string to be picked up! I spent a good amount of time frustrated wondering, why the heck isn’t it working! Don’t be like past me, learn from my mistakes!
Whenever you get your line highlighting setup, feel free to share it with me on Twitter! I want to give a massive shoutout to Carlos for their solution on an issue for the package. I also want to thank Benjamin Lannon, Ryan Warner, and Chris Biscardi for helping me with this blog post and giving feedback.
My solution to this was to have my Highlight
component be wrapped with a component that has
the correcting styles on it. For me, that is the pre
component found in my MDXProvider components
.
I added to my pre
component className="gatsby-highlight"
:
const components = {
a: (props) => <Link {...props} />,
pre: (props) => <div className="gatsby-highlight" {...props} />,
code: CodeBlock,
inlineCode: InlineCode
}
Then in my CSS, I added:
.gatsby-highlight {
background-color: #011627; /* my previous background color */
overflow: auto;
}
.gatsby-highlight pre[class*='language-'] {
background-color: transparent;
float: left;
min-width: 100%;
}
The first rule makes sure I have my background color set to the same thing on my theme, the rule also makes sure that if it is clipped to add a scrollbar.
Then in the next rule, it specifically goes and finds my pre
tags with a class that
has the language-
prefix and removes its background, and makes the content take up
as much space as its container. Now everything should be scrollbar and useable!