Article Logo Go Back

Building React Code Blocks with PrismJS

AvatarJun 13, 201913 minsTim Ellenberger

Code snippets without syntax highlighting are unsightly. No one wants to view these code snippets. They are extremely hard to read and deeply unstylistic.

Aiming to bridge the gap for the unsightly snippets, VS Code and its' default theme Dark+ have gained a lot of familiarity in the development community.

A project called PrismJS for syntax highlighting

Another project succinctly named prism-react-renderer enables us to manipulate the PrismJS syntax highlighted output as well as add extraneous react components into the code blocks. Subsequently, a theme object can be passed as a prop to further control the syntax highlighting.

componentsBlogBlogCodeBlock.jsjsx
1const BlogCodeBlock = ({ code, language, theme }) => (2  <Highlight3    {...defaultProps}4    theme={theme}5    code={code.trim()}6    language={language}7  >8    {({ className, style, tokens, getLineProps, getTokenProps }) => (9      <CodeBlock className={className} style={style}>10        {tokens.map((line, i) => (11          <BlockLine {...getLineProps({ line, key: i })}>12            <LineNumber>{i + 1}</LineNumber>13            {line.map((token, key) => {14              const props = getTokenProps({ token, key });15
16              return <span {...props} />;17            })}18          </BlockLine>19        ))}20      </CodeBlock>21    )}22  </Highlight>23);
dataGraphQLGetBlogCodeBlock.graphqlgraphql
1query {2  superCoolGraphQLQuery({ number: 5 }) {3    id4    name5    date6  }7}
componentsBlogBlogCodeBlock.jsjsx
1import React from 'react';2import PropTypes from 'prop-types';3import styled from 'styled-components';4import Highlight, { defaultProps } from 'prism-react-renderer'; 5import theme from 'prism-react-renderer/themes/vsDark';6
7/**8 * Splits the first token of a code line at any leading whitespace.9 *10 * The purpose of the split is to easily highlight code on hover11 * without including the indenting whitespace.12 *13 * So if the beginning of a line looks like this exists:14 *    <span>{`      <div />`}</span>15 * break into two parts:16 *    <span>{`      `}</span>17 *    <span>{`<div />`}</span>18 *19 * @param {array} line Tokenized line of code20 */21const splitLineIndent = line => {22  const { content } = line[0];23  const hasIndent = content.charAt(0) === ' ';24
25  // Does the first token of the line have an indent?26  if (hasIndent) {27    // Separate leading whitespace and code portion of token28    const [, lineIndent, codeStart] = content.split(/^(\s+)/);29
30    // If token isn't only whitespace, 31    // insert split tokens back into line32    if (codeStart !== '') {33      const newIndent = { ...line[0], content: lineIndent };34      const newCodeStart = { ...line[0], content: codeStart };35
36      // Delete first token37      line.shift();38
39      // Replace with two tokens40      line.unshift(newIndent, newCodeStart);41    }42  }43};44
45const BlogCodeBlock = ({ code, language }) => (46  <Highlight47    {...defaultProps}48    theme={theme}49    code={code.trim()}50    language={language}51  >52    {({ className, style, tokens, getLineProps, getTokenProps }) => (53      <CodeBlock className={className} style={style}>54        {tokens.map((line, i) => {55          // Split left-most tokens with leading whitespace56          splitLineIndent(line);57
58          return (59            <BlockLine {...getLineProps({ line, key: i })}>60              <LineNumber>{i + 1}</LineNumber>61              {line.map((token, key) => {62                const props = getTokenProps({ token, key });63
64                // if first span is empty, add empty classname65                // eslint-disable-next-line react/prop-types66                if (key === 0 && !/\S/.test(props.children)) {67                  props.className += ' whitespace';68                }69
70                return <span {...props} />;71              })}72            </BlockLine>73          );74        })}75      </CodeBlock>76    )}77  </Highlight>78);79
80BlogCodeBlock.propTypes = {81  code: PropTypes.string.isRequired,82  language: PropTypes.string83};84
85BlogCodeBlock.defaultProps = {86  language: 'jsx'87};88
89export default BlogCodeBlock;

Edit Post

Bug?Edit Post