Statamic Code Highlighting with Highlight.js

Published on Mar 15, 2021
Topics: Meta, Coding, Statamic

I wanted to add syntax highlighting to content entries. Tailwind's typography includes basic formatting for code blocks, but I wanted it to look a little nicer.

There were two main goals:

  • Don't include the CSS/JS for code highlighting unless absolutely necessary
  • Make sure it has dark/light mode support

The two most frequently recommended libraries I found were Prism and Highlight.js. I set up Prism first, it was quick and it worked but I didn't like the way the themes looked on the site. Highlight.js has a lot more themes baked in, and it was just as easy to integrate as Prism (after setting it up I learned that is in use by Stack Overflow)

This is how I set it up on the site.

Get it Working

Before we can make it work the way we want it to we have to make it work. Download the files from the Highlight.js website with your languages of choice.

I always keep assets in the resources folder, and use Laravel Mix to copy them to the public folder so I can version them.

I put the entire highlights folder I downloaded in to resources. That's a lot stylesheets, but we're only using two of them - one for light theme and one for dark.

Set up webpack.mix.js to copy the files to public directory (this is my entire webpack.mix.js file).

mix.js('resources/js/site.js', 'public/js')
    // start the highlightjs part
   .copy('resources/highlight/styles/atom-one-dark.css', 'public/css/highlight-dark.css')
   .copy('resources/highlight/styles/atom-one-light.css', 'public/css/highlight-light.css')
   .copy('resources/highlight/highlight.pack.js', 'public/js/highlight.js')
   // end of highlightjs part
   .postCss('resources/css/tailwind.css', 'public/css', [
       require('tailwindcss'),
   ]);

if(process.env.NODE_ENV === 'production') {
    mix.version();
}

In the <head> of resources/views/layout.antlers.html I added:

<link rel="stylesheet" href="{{ mix src='css/higlight-dark.css' }}">

Then, after all the content in the <body> I added:

<script src="{{ mix src='js/highlight.js' }}"></script>
<script>hljs.highlightAll();</script>

Double check and yay, I have code highlighting.

Only on Code Pages

To include the Prism files only on pages with code blocks I found a simple enough method. Just add a piece of information to the article/page entry named include_code if is set to true then include the CSS/JS.

Here's an example on an article entry which include the code files:

title: 'Statamic Code Highlighting'
updated_at: 1615660665
include_code: true
excerpt: 'How I added code highlighting to the blog, with dark and light mode support use Prism and Alpine JS.'

And one that doesn't (no include_code at all);

title: 'It is back.'
topics:
  - macos
  - meta
excerpt: 'Starting it up again.'
updated_at: 1614590657

In my default layout file I added a check for include_code set to true.

This is what the <head> sections looks like now:

{{ if include_code }}
    <link rel="stylesheet" href="{{ mix src='css/higlight-dark.css' }}">
{{ /if }}

This is what the JS include looks like now:

{{ if include_code }}
    <script src="{{ mix src='js/highlight.js' }}"></script>
    <script>hljs.highlightAll();</script>
{{ /if }}

Dark and Light Mode

I added in dark/light mode support with Javascript, but ultimately removed it. The dark code theme looked better in both scenarios (dark on dark and dark on light).

Here is the code I originally wrote to make it work.

In the layout page...

{{ if include_code }}
<script src="{{ mix src='js/highlight.js' }}"></script>
<script>
    // Run highlighter
    hljs.highlightAll();
    
    // Load the styles
    function loadUp(css) {
        let cssNode = document.createElement('link');
        cssNode.setAttribute('rel', 'stylesheet');
        cssNode.setAttribute('href', css);
        document.querySelector('head').appendChild(cssNode);
    }

    // https://stackoverflow.com/questions/50730640/how-can-i-detect-if-dark-mode-is-enabled-on-my-website
    if(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
        loadUp("{{ mix src='css/highlight-dark.css' }}");
    } else {
        loadUp("{{ mix src='css/highlight-light.css' }}");
    }
</script>
{{ /if }}

I also had to make some updates to the Tailwind Typography plugin config to stop it from styling the pre code elements

theme: {
    extend: {
        typography(theme) {
          return {
              DEFAULT: {
                    css: {
                        code: {
                            backgroundColor: '',
                        },
                        'pre code': {
                            backgroundColor: '',
                        },
                    },
              },
          };
        },
    },
  },