Code Highlighting In This Blog

2023-10-03

Categoriestechtypescript

I decided to rewrite this entire website, migrating from WordPress to a static site generated in NextJS, partly because I wanted to have something more customisable and partly because I wanted to get away from WordPress.

One of the things I knew I had to get working was code highlighting, I wanted to be able to write code in my posts and have it look nice and pretty. The source files for these posts are written in Markdown, and I was already using react-markdown to parse the markdown into HTML, so I needed to find a way to get code highlighting working with react-markdown.

Their docs suggest using react-syntax-highlighter and then using Prism.js (apparently it has better JSX highlighting support over highlight.js) as the highlighter, so after installing the packages I then went looking at the docs to see how to integrate it. The example code in their docs, reads pretty horrifically:

1ReactDom.render(
2  <Markdown
3    children={markdown}
4    components={{
5      code(props) {
6        const {children, className, node, ...rest} = props
7        const match = /language-(\w+)/.exec(className || '')
8        return match ? (
9          <SyntaxHighlighter
10            {...rest}
11            children={String(children).replace(/\n$/, '')}
12            style={dark}
13            language={match[1]}
14            PreTag="div"
15          />
16        ) : (
17          <code {...rest} className={className}>
18            {children}
19          </code>
20        )
21      }
22    }}
23  />,
24  document.body
25)

Now, firstly I find the lack of types disturbing. Darth Vader GIF

Secondly, I don't like the way it's written, it's not very readable, and it's not very maintainable, I don't like the obscure ternary operator, I don't like the shadowed variable names, I don't like the prop spreading. It didn't fill me with hope or happiness. But, I pasted the code in, and it worked, so I decided to try and refactor it to something I was happier with.

I have split the code generator out into a separate function, and then had to do the same with the Pre tags, as it would insist on putting a pre tag around the code block, which I didn't want, I also added a max width to the code blocks, as they were stretching the page out too much especially on mobile.

1'use client';
2
3import Markdown from 'react-markdown';
4import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
5import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
6
7function generateCodeBlock(
8  props: React.DetailedHTMLProps<
9    React.HTMLAttributes<HTMLElement>,
10    HTMLElement
11  >,
12) {
13  const match = /language-(\w+)/.exec(props.className || '');
14  return match ? (
15    <SyntaxHighlighter
16      style={vscDarkPlus}
17      language={match[1]}
18      showLineNumbers
19      customStyle={{
20        maxWidth: 'calc(100vw - 50px)',
21      }}
22    >
23      {String(props.children).replace(/\n$/, '')}
24    </SyntaxHighlighter>
25  ) : (
26    <code className={props.className}>{props.children}</code>
27  );
28}
29
30function noPreWrap(
31  props: React.DetailedHTMLProps<
32    React.HTMLAttributes<HTMLPreElement>,
33    HTMLPreElement
34  >,
35) {
36  // eslint-disable-next-line react/jsx-no-useless-fragment
37  return <>{props.children}</>;
38}
39
40export default function ReactMarkdown({ children }: { children: string }) {
41  return (
42    <div className='container'>
43      <Markdown
44        className='prose prose-invert break-words text-gray-100 prose-p:break-words prose-p:text-justify prose-a:break-all prose-img:h-1/6'
45        components={{
46          code: generateCodeBlock,
47          pre: noPreWrap,
48        }}
49      >
50        {children}
51      </Markdown>
52    </div>
53  );
54}

I'm much happier with this, it's more readable, it's more maintainable, it's more type safe. I'm sure there are still improvements to be made, and I don't like the linting override, but I'm happy with it for now.

If I get the time, I might clean it up a bit and submit a PR to their docs to see if they want to use this instead. I think it's better to have a more readable example in the docs, and it's definitely clearer to me what is going on in this code.

But mainly this post is for me to remember how I did it, and to check that the code highlighting works as I write this. Also, a friendly reminder to myself that when you are struggling to type something Ctrl + Click or Highlight and F12 is your friend.

This has also made me realise that KBD tags don't work1, so I guess I'll have to fix that next.

Footnotes

  1. They work now!