React

Add code highlighting to MDX

How to add code highlighting to Gatsby and MDX

I've recently dropped markdownRemark and started using MDX to write my posts. MDX is great since it allows you to import components and even write code that will be converted as expected. You can also use graphql queries on your posts which is very useful.

I have been using the gatsby prism plugin to get my code highlighting work, but after moving to MDX this plugin stopped working, so I went ahead in search for a way to get the code highlighting working again. This article did that.

Dependencies

To get the code highlighting work with MDX, we need to install the pristm react renderer package.

shell
1npm install prism-react-renderer

The Code Block Component

Following the tutorial, I wrote a code block component. I've done some changes to my code block component since I wanted to use my code highlighting theme, I've also added line numbers to each line.

javascript
1import React from "react"
2import Highlight, { defaultProps } from "prism-react-renderer"
3
4export default (props) => {
5
6 const className = props.children.props.className || 'language-text' //needed for styling``
7
8 const matches = className.match(/language-(?<lang>.*)/)
9
10 return (
11 <div className="gatsby-highlight">
12 <Highlight {...defaultProps}
13 code={props.children.props.children.trim()}
14 language={matches && matches.groups
15 && matches.groups.lang
16 ? matches.groups.lang : ''}
17 theme=''
18 >
19 {({ className, tokens, getLineProps, getTokenProps}) => (
20 <pre className={className}>
21 <code className={className}>
22 {tokens.map((line, i) => (
23 <div className="code-block" key={i} {...getLineProps({line, key:i})}>
24 <span className="line-number">{i + 1}</span>
25 {line.map((token, key) => (
26 <span key={key} {...getTokenProps({token, key})} />
27 ))}
28 </div>
29 ))}
30 </code>
31 </pre>
32 )}
33 </Highlight>
34 </div>
35 )
36}

Notice that I am not passing a theme since I am using my own code highlight theme - you could download one theme from the official PrismJS repository and use it instead.

Or if you want, you can just copy my theme and use it.

Expanding the code block

I've seen Chris Biscardi digital garden and really liked the "header" of his code blocks, so I thought that it could be a nice addition to my code block.

To allow users to copy the code inside a code block, I had to create a function for the copy button and handle the event when someone clicks on it.

javascript
1import React, { useState } from "react"
2import Highlight, { defaultProps } from "prism-react-renderer"
3
4
5export default (props) => {
6 const className = props.children.props.className || 'language-text'
7
8 const language = className.replace("language-", "")
9 const matches = className.match(/language-(?<lang>.*)/)
10
11 const CopyButton = props => {
12 const [text, setText] = useState("Copy")
13
14 return (
15 <button className="code-copy-button" onClick={() => {
16 navigator.clipboard.writeText(props.content)
17 setText("Copied!")
18 setTimeout(() => { setText("Copy")}, 1000)
19 }}
20 >
21 {text}
22 </button>
23 )
24 }
25
26 return (
27 <div className="gatsby-highlight">
28 <Highlight {...defaultProps}
29 code={props.children.props.children.trim()}
30 language={matches && matches.groups && matches.groups.lang ? matches.groups.lang : ''}
31 theme=''
32 >
33 {({ className, tokens, getLineProps, getTokenProps}) => (
34 <pre className={className}>
35 <div className="code-header">
36 <span className="language-name">{language}</span>
37 <CopyButton content={props.children.props.children} />
38 </div>
39 <code className={className}>
40 {tokens.map((line, i) => (
41 <div className="code-block" key={i} {...getLineProps({line, key:i})}>
42 <span className="line-number">{i + 1}</span>
43 {line.map((token, key) => (
44 <span key={key} {...getTokenProps({token, key})} />
45 ))}
46 </div>
47 ))}
48 </code>
49 </pre>
50 )}
51 </Highlight>
52 </div>
53 )
54}

Adding Wrapper Component to MDXRenderer

The step 3 of the adding syntax highlighting to Gatsby Mdx with Prism, says that we need to add the code block as a component to the MDXProvider and also points to the MDXRenderer documentation.

I have to admit that I couldn't understand fully how to do this. Everything that I tried was failing, luckily Will Harris was kind enough to share his repository with me and explained that I should add the component to the gatsby-browser.js.

javascript
1import React from "react"
2import { MDXProvider } from "@mdx-js/react"
3
4import CodeBlock from "./src/components/highlight/CodeBlock"
5
6const component = {
7 pre: CodeBlock
8}
9
10export const wrapRootElement = ({ element }) => {
11 return <MDXProvider components={component}>{element}</MDXProvider>
12}

By adding this bit of code to the gatsby-browser.js made the whole code highlighting work.

If you chose a theme, you should have everything working, if you prefer to build your own, then you can keep on reading and use my theme.

Code Highlighting Theme

I am using scss on this website so the theme that I am going to share with you, will be in scss.

scss
1.gatsby-highlight {
2 margin-top: 2.5rem;
3 margin-bottom: 2.5rem;
4}
5
6.code-header {
7 display: flex;
8 justify-content: flex-end;
9 border-bottom: 1px solid #6089bd;
10 padding: 1rem;
11}
12
13.language-name {
14 padding-right: 1em;
15 color: #ff85cb;
16}
17
18.code-copy-button {
19 color: #79b3fd;
20 background: none;
21 border: none;
22 cursor: pointer;
23}
24
25code {
26 padding: 2px 6px;
27 color: #78b2fd;
28 border: 1px solid #1b2530;
29 background-color: #1b2530;
30 border-radius: 5px;
31}
32
33
34code[class*="language-"],
35pre[class*="language-"] {
36 padding: 0;
37 color: #f8f8f2;
38 background: none;
39 text-shadow: 0 1px rgba(0, 0, 0, 0.3);
40 text-align: left;
41 white-space: pre;
42 word-spacing: normal;
43 word-break: normal;
44 word-wrap: normal;
45 line-height: 1.5;
46 -moz-tab-size: 4;
47 -o-tab-size: 4;
48 tab-size: 4;
49 -webkit-hyphens: none;
50 -moz-hyphens: none;
51 -ms-hyphens: none;
52 hyphens: none;
53 font-size: large;
54}
55
56/* Code blocks */
57pre[class*="language-"] {
58 overflow: auto;
59 border-radius: 0.3em;
60 border: 1px solid rgb(97, 137, 189);
61 scrollbar-width: none;
62
63 &::-webkit-scrollbar {
64 display: none; /* Chrome Safari */
65 }
66}
67
68:not(pre) > code[class*="language-"],
69pre[class*="language-"] {
70 background: #1A2430;
71}
72
73/* Inline code */
74:not(pre) > code[class*="language-"] {
75 padding: .1em;
76 border-radius: .3em;
77 white-space: normal;
78}
79
80.line-number {
81 color: #6272a4;
82 padding-right: 2rem;
83}
84
85.token-line {
86 padding: 0 1em;
87}
88
89.token.comment,
90.token.prolog,
91.token.doctype,
92.token.cdata {
93 color: #6272a4;
94}
95
96.token.punctuation {
97 color: #f8f8f2;
98}
99
100.namespace {
101 opacity: .7;
102}
103
104.token.property,
105.token.tag,
106.token.constant,
107.token.symbol,
108.token.deleted {
109 color: #ff79c6;
110}
111
112.token.boolean,
113.token.number {
114 color: #bd93f9;
115}
116
117.token.selector,
118.token.attr-name,
119.token.string,
120.token.char,
121.token.builtin,
122.token.inserted {
123 color: #50fa7b;
124}
125
126.token.operator,
127.token.entity,
128.token.url,
129.language-css .token.string,
130.style .token.string,
131.token.variable {
132 color: #f8f8f2;
133}
134
135.token.atrule,
136.token.attr-value,
137.token.function,
138.token.class-name {
139 color: #f1fa8c;
140}
141
142.token.keyword {
143 color: #8be9fd;
144}
145
146.token.regex,
147.token.important {
148 color: #ffb86c;
149}
150
151.token.important,
152.token.bold {
153 font-weight: bold;
154}
155
156.token.italic {
157 font-style: italic;
158}
159
160.token.entity {
161 cursor: help;
162}

Share your theme!

If you have used my template to get your own code highlighting and have changed it, I would love to see what you have created!

You can add your own theme to my code highlighting theme repository.

References

Webmentions

0 Like 0 Comment

You might also like these