Nathan Smith
  • Projects
  • About
  • Contact

usePrismjs: A prismjs hook for CRA & React

May 20, 2020, 9:14 PMReact

What is it?

Prismjs is a powerful little javascript package that adds syntax highlighting to <pre> and <code> blocks, but it hasn't kept up with a JavaScript framework-using, module-bundling world. There aren't many docs around how to use it with the npm package, and most want you to use a babel plugin, and I'm not about to eject my CRA app just for syntax highlighting. Additionally, the html I'm highlighting comes from a CMS, so it has to work with any incoming HTML. After struggling through it, I came up with this hook, which is being used on this page. How meta!

Using the hook

1. Install prismjs

The easy part. Just install prismjs and its types (if you're using TypeScript):

npm install --save prismjs
npm install --save-dev @types/prismjs

2. Create the usePrismjs hook

import React from 'react';
import Prismjs from 'prismjs';

import 'prismjs/themes/prism.css'; /* or your own custom theme */
import 'prismjs/plugins/line-numbers/prism-line-numbers.css' /* add plugin css */

// Require all needed languages
require('prismjs/components/prism-typescript');
require('prismjs/components/prism-jsx');
require('prismjs/components/prism-tsx');

// Require all needed plugins
require('prismjs/plugins/line-numbers/prism-line-numbers');

export function usePrismjs<T extends HTMLElement>(
  target: React.RefObject<T>,
  plugins: string[] = []
) {
  React.useLayoutEffect(() => {
    if (target.current) {
      if (plugins.length > 0) {
        target.current.classList.add(...plugins);
      }
      // Highlight all <pre><code>...</code></pre> blocks contained by this element
      Prismjs.highlightAllUnder(target.current);
    }
  }, [target, plugins]);
}

Customize to your plugin/language needs.

3. Use the hook

import React from 'react';
import { BlogPost } from '../../types/cms';
import { usePrismjs } from '../../hooks/usePrismjs';

interface PostProps {
  post: BlogPost;
}

export const Post: React.FC<PostProps> = props => {
  // body is string with blog post html, including code
  const { body, title, published } = props.post;
  const date = new Date(published);
  const bodyRef = React.useRef<HTMLDivElement>(null);
  usePrismjs(bodyRef, ['line-numbers']);

  return (
    <div>
      <h1>{title}</h1>
      <h4>{published}</h4>
      <div ref={bodyRef} dangerouslySetInnerHTML={{ __html: body }} />
    </div>
  );
};

Explanations

There are a few obvious questions I see:

Why add the class directly to the dom?

Because prismjs works directly on the dom; the highlightAllUnder method expects a real DOM element, so we have to give it one.

Why not conditionally require plugins and languages like the babel plugin?

This might work, but it would also mean conditionally requiring plugin css.

Previous: Publishing a Dev and Production CRA App to GH Pages

Next: Playing Spotify playlists in Discord voice channels