August 06, 2020 . 8 min read
code block
pre
prism-react-renderer
As promised here I will try to explain how I have implemented syntax highlight using prism-react-renderer
together with theme-ui
.
We will do a couple of things:
code.js
component that will inject the style into the <pre>
taggatsby-plugin-theme-ui
title.js
component to get the title of the file into the code blockWe can start with prism-react-renderer
documentation to get a little bit more details on how to implement this.
To start with, let's create a code.js
component as follows:
import React from "react";import Highlight, { defaultProps } from "prism-react-renderer";const SyntaxHiglight = (props) => { return ( <Highlight {...defaultProps} theme={theme} code={props.children.props.children.trim()} language="jsx" > {({ className, style, tokens, getLineProps, getTokenProps }) => ( <pre className={className} style={style}> {tokens.map((line, i) => ( <div {...getLineProps({ line, key: i })}> {line.map((token, key) => ( <span {...getTokenProps({ token, key })} /> ))} </div> ))} </pre> )} </Highlight> );};const Code = (props) => <SyntaxHiglight {...props} />;export default Code;
To make sure that this was working I had to apply the component into theme-ui
import React from "react";import Code from "../components/code";const components = { pre: (props) => <Code {...props} />,};export default components;
This is great and if you and you can now see your code block with a different styling, however this might not be the styling to that you want and more importantly the language is hardcoded and currently perceiving all <pre>
tag's as jsx.
Let's change this.
import React from "react";import Highlight, { defaultProps } from "prism-react-renderer";import theme from "prism-react-renderer/themes/nightOwl";const getParams = (className = ``) => { const [lang = ``, params = ``] = className.split(`:`); return [lang.split(`language-`).pop().split(`{`).shift()].concat( params.split(`&`).reduce((merged, param) => { const [key, value] = param.split(`=`); merged[key] = value; return merged; }, {}) );};const SyntaxHiglight = (props) => { const className = props.children.props.className || ""; const [language, { title = `` }] = getParams(className); return ( <Highlight {...defaultProps} theme={theme} code={props.children.props.children.trim()} language={language} > {({ className, style, tokens, getLineProps, getTokenProps }) => ( <> <pre className={className} style={style}> {tokens.map((line, i) => { return ( <div key={i} {...getLineProps({ line, key: i })}> {line.map((token, key) => ( <span {...getTokenProps({ token, key })} /> ))} </div> ); })} </pre> </> )} </Highlight> );};const Code = (props) => <SyntaxHiglight {...props} />;export default Code;
We have added the theme and style that we wanted and now, with the getParams
we will be able to extract the language and the title of the code block. Now if we add the language and title as follows `jsx:title=src/components/code.js
, the function we created will capture the language as jsx and the tile of the code block as src/components/code.js.
Now we can create the title.js
component that will style and render the title and the language of the code block.
/** @jsx jsx */import { jsx } from "theme-ui";import styled from "@emotion/styled";const TitleContainer = styled.div` background-color: #d2c7ec; padding: 10px; display: flex; justify-content: space-between; align-items: center;`;const Text = styled.h2` margin: 0px; color: black;`;const LanguageTag = styled.div` padding: 5px; color: white; border-radius: 4px;`;const Title = (props) => { const { text, children, className } = props; return ( <TitleContainer className={className}> <Text sx={{ fontSize: [2, 3], fontFamily: `heading`, lineHeight: `heading`, }} > {text} </Text> <LanguageTag sx={{ bg: `highlight`, }} > {children} </LanguageTag> </TitleContainer> );};export default Title;
Please note that I am using styled components with emotion
on the title component.
Now we can use the title component in the code component that we have put together:
import React from "react";import Highlight, { defaultProps } from "prism-react-renderer";import theme from "prism-react-renderer/themes/nightOwl";import Title from "../components/title";const getParams = (className = ``) => { const [lang = ``, params = ``] = className.split(`:`); return [lang.split(`language-`).pop().split(`{`).shift()].concat( params.split(`&`).reduce((merged, param) => { const [key, value] = param.split(`=`); merged[key] = value; return merged; }, {}) );};const SyntaxHiglight = (props) => { const className = props.children.props.className || ""; const [language, { title = `` }] = getParams(className); const ifTitle = (title || language) && { marginTop: `0px` }; return ( <Highlight {...defaultProps} theme={theme} code={props.children.props.children.trim()} language={language} > {({ className, style, tokens, getLineProps, getTokenProps }) => ( <> <Title className="code-title" text={title}> {language} </Title> <pre className={className} style={{ ...style, ...ifTitle }}> {tokens.map((line, i) => { return ( <div key={i} {...getLineProps({ line, key: i })}> {line.map((token, key) => ( <span {...getTokenProps({ token, key })} /> ))} </div> ); })} </pre> </> )} </Highlight> );};const Code = (props) => <SyntaxHiglight {...props} />;export default Code;
We should be able to see the code block with the theme that we injected in the <pre>
tag, however we are not done yet as we still want to get the feature for highlighting certain code lines.
I reckon this super important piece to help anyone that is reading your digital garden to follow through when you are trying to explain anything with code.
We will be doing two very important and distinct things. In the first step (a) we will need to find a way to identify the lines of code to highlight and in the second step (b) we will need to determine and ingest the style on the line of code previously identified.
import React from "react";import Highlight, { defaultProps } from "prism-react-renderer";import theme from "prism-react-renderer/themes/nightOwl";import Title from "../components/title";const getParams = (className = ``) => { const [lang = ``, params = ``] = className.split(`:`); return [lang.split(`language-`).pop().split(`{`).shift()].concat( params.split(`&`).reduce((merged, param) => { const [key, value] = param.split(`=`); merged[key] = value; return merged; }, {}) );};const calculateLinesToHighlight = (meta) => { const RE = /{([\d,-]+)}/; if (RE.test(meta)) { const strlineNumbers = RE.exec(meta)[1]; const lineNumbers = rangeParser(strlineNumbers); return (i) => lineNumbers.includes(i + 1); } else { return () => false; }};const SyntaxHiglight = (props) => { const className = props.children.props.className || ""; const [language, { title = `` }] = getParams(className); const ifTitle = (title || language) && { marginTop: `0px` }; const metastring = props.children.props.metastring || ""; const shouldHighlightLine = calculateLinesToHighlight(metastring); return ( <Highlight {...defaultProps} theme={theme} code={props.children.props.children.trim()} language={language} > {({ className, style, tokens, getLineProps, getTokenProps }) => ( <> <Title className="code-title" text={title}> {language} </Title> <pre className={className} style={{ ...style, ...ifTitle }}> {tokens.map((line, i) => { const lineProps = getLineProps({ line, key: i }); if (shouldHighlightLine(i)) { lineProps.className = `${lineProps.className} highlight-line`; } return ( <div key={i} {...lineProps}> {line.map((token, key) => ( <span {...getTokenProps({ token, key })} /> ))} </div> ); })} </pre> </> )} </Highlight> );};const Code = (props) => <SyntaxHiglight {...props} />;export default Code;
We manage to get create a way to identify the lines of code that we would like to highlight if before the code block we input the following: ```jsx:title=src/components/code.js {17-26,32,33,49-52}
. We are now passing a metastring
prop with {17-26,32,33,49-52}
that results in the injection of a new class into the <div>
tag.
The only thing that we now need to do is to style that newly created class.
.highlight-line { background-color: rgb(53, 59, 69); display: block; margin-right: -1em; margin-left: -1em; padding-right: 1em; padding-left: 0.75em; border-left: 0.1em solid #d23669;}
Here we used a normal css file so that we can check the difference in styling when using a different language.
Please note that we still need to import the newly created style to the code.js
component.
import React from "react";import Highlight, { defaultProps } from "prism-react-renderer";import theme from "prism-react-renderer/themes/nightOwl";import Title from "../components/title";import '../css/code.css'...
That is it! Now we have a way to style our code block <pre>
tag as we want as well as an effective implementation to highlight the code lines that we want whenever needed.
Last but not the least I would like you to leave you with 2 great resources that really helped me out to achieve this:
Thanks for reading.
If this was useful for you just hit the like button bellow.
1 minutes read
1 minutes read
3 minutes read
4 minutes read
3 minutes read
2 minutes read
No spam! Only good stuff!