Syntax highlighting with React & WordPress

Articles|HÃ¥kon Underbakke | over 3 years ago

THIS ARTICLE IS OUTDATED. I’m currently using a different, better setup. Please refer to this article instead.

The problem: WordPress’ REST API supplies the blogpost’s body as a string of HTML. This makes things a bit more complicated when trying to add most of the react syntax-highlighting packages, seeing as most of them are wrapper components.

The solution: Highlight.js gives us a function, highlightBlock(), which takes an HTML node, tries its best to automatically detect the language and then applies syntax highlighting.

How to set it up

The first step is to install highlight.js

npm i highlight.js

Then, in a callback after receiving the blogpost and putting it into state, initiate highlight.js using hljs.highlightBlock() on every instance of pre code

import React from "react";
import axios from "axios";
import hljs from "highlight.js";

class App extends React.Component {
  componentDidMount(){
    axios
      .get(
        `${siteURL}/wp-json/wp/v2/posts/?include[]=${this.state.postID}`
      )
      .then(res => res.data)
      .then(blogPost => {
        this.setState({
            blogPost: blogPost,
            loaded: true
        }, this.initiateSyntaxHighlighting);
      });
  }
  initiateSyntaxHighlighting = () => {
    document.querySelectorAll("pre code").forEach(block => {
      hljs.highlightBlock(block);
    }); 
  }
  // ...
}

I use axios in this example to retrieve my post, but the important part is just the initiateSyntaxHighlighting() method.

Choosing a theme

Go to highlightjs.org and pick a theme you like by clicking on the style-name under the demo. Once you find one you like (I use atom-one-dark), go ahead and download the relevant stylesheet from here. Once you’ve downloaded the stylesheet, include it into your project.

If you want to apply some extra styles to the code-box, you can easily do that using the following selector:

.wp-block-code code{
  font-size: 0.7em;
  padding: 13px;
}

Adding line-numbers

To add line-numbers, we are going to use CSS – but first we need to modify our code a bit so that we can target each line individually.

  initiateSyntaxHighlighting = () => {
        const codes = document.querySelectorAll("pre code");
        codes.forEach(block => {
           hljs.highlightBlock(block);
           const lines = block.innerHTML.split("\n");
           const newLines = [];
           lines.forEach(line => {
              newLines.push(`<div class="code-line">${line}</div>`);
           });
           block.innerHTML = newLines.join("\n");
        });
  }

What we do here is first make an array of each line by splitting the innerHTML of each block. Then we loop through this array, and wrap the line with a div called .code-line. Finally, we update the innerHTML of the code-block to newLines.join("\n").

In our CSS, we now need to create a counter.

.wp-block-code code {
  counter-reset: line;
  padding: 17px 40px; 
}
.wp-block-code code .code-line{
  display: inline-block; // important to prevent each line from becoming a paragraph.
  position: relative;
}
.wp-block-code code .code-line:before{
  counter-increment: line;
  content: counter(line);
  display: inline-block;
  position: absolute;
  top: 0;
  left: -30px;
  opacity: 0.5;
}

By resetting the counter on each code, and incrementing on each .code-line, we get an accurate line count positioned to the left of the line.

My thoughts

Highlight.js does a pretty subpar job at detecting the language, as I expected. The upside to this is that it usually does decent syntax-highlighting even though it gets the language wrong. I’m sure there are many better solutions to this problem, but it was a relatively straight forward and easy fix to my problem, and so I’m happy with it. If you want a quick fix for this specific scenario, then I’d definitely suggest this method.