What's this?
This package is a Rehype plugin that provides beautiful code blocks for your MD/MDX docs. It has advantages over other solutions such as Prism. View on GitHub.
VS Code's highlighting
Leverage the accuracy of VS Code's syntax highlighting engine and the popularity of its themes ecosystem, powered by Shiki. No missing tokens or unexpected broken syntax highlighting.
import Document, { Html, Head, Main, NextScript } from "next/document";
// 🔥 Super granular and accurate highlighting
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
}
render() {
return (
<Html>
<Head />
<body className="bg-zinc-800 text-zinc-200">
<Main />
<NextScript />
</body>
</Html>
);
}
}
The theme is Moonlight II with a custom background color.
There's no runtime or bundle size cost
Shiki does its highlighting at build-time — there's no perf hit caused by runtime syntax highlighting or kilobytes of library code sent over the wire on the client. Your docs will be lightning fast.
Line numbers and line highlighting are supported
import { useFloating } from "@floating-ui/react";
function MyComponent() {
const { x, y, refs } = useFloating();
return (
<>
<div ref={refs.setReference} />
<div ref={refs.setFloating} />
</>
);
}
Word highlighting too
import { useFloating } from "@floating-ui/react";
function MyComponent() {
const { x, y, refs } = useFloating();
return (
<>
<div ref={refs.setReference} />
<div ref={refs.setFloating} />
</>
);
}
Even inline code!
The result of [1, 2, 3].join('-')
is '1-2-3'
.
Context-aware inline code
For instance, if you had the following code block:
function getStringLength(str) {
return str.length;
}
When we refer to getStringLength
as a plain variable,
we can color it as a function. Same with function
, or
str
vs. str
, etc. This allows
you to semantically tie inline code with the nearest code block it's referring
to.
ANSI highlighting
vite v2.8.6 dev server running at:
> Local: http://localhost:3000/
> Network: use `--host` to expose
ready in 125ms.
8:38:02 PM [vite] hmr update /src/App.jsx
Inline ANSI: > Local: http://localhost:3000/
Code blocks are unstyled
No need to fight CSS rules, build the look from scratch. We recommend the following styles as a base though:
pre > code {
display: grid;
}
This allows line highlighting to span the width of a horizontally-scrollable code block on small viewport widths.
Installation
Install via your terminal:
npm install rehype-pretty-code shiki
Usage
The following example shows how to use this package with Next.js and MDX (this website's stack).
const rehypePrettyCode = require("rehype-pretty-code");
const fs = require("fs");
const options = {
// Use one of Shiki's packaged themes
theme: "one-dark-pro",
// Or your own JSON theme
theme: JSON.parse(
fs.readFileSync(require.resolve("./themes/dark.json"), "utf-8")
),
// Keep the background or use a custom background color?
keepBackground: true,
// Callback hooks to add custom logic to nodes when visiting
// them.
onVisitLine(node) {
// Prevent lines from collapsing in `display: grid` mode, and
// allow empty lines to be copy/pasted
if (node.children.length === 0) {
node.children = [{ type: "text", value: " " }];
}
},
onVisitHighlightedLine(node) {
// Each line node by default has `class="line"`.
node.properties.className.push("highlighted");
},
onVisitHighlightedWord(node) {
// Each word node has no className by default.
node.properties.className = ["word"];
},
};
const withMDX = require("@next/mdx")({
extension: /\.mdx?$/,
options: {
remarkPlugins: [],
rehypePlugins: [[rehypePrettyCode, options]],
},
});
Meta strings
Code blocks are configured via the meta string on the top codeblock fence.
Highlight lines
Place a numeric range inside {}
.
```js {1-3,4}
```
Highlight words
Literal text, like a regex. Note regex itself is not yet supported.
```js /carrot/
```
```js /carrot/ /apple/
```
Highlight only the third to fifth instances of carrot
. Any numeric range can
be placed after the last /
.
```js /carrot/3-5
```
Highlight only the third to fifth instances of carrot
and any instances of
apple
.
```js /carrot/3-5 /apple/
```
Group highlighted words by id
Place an id after #
after the literal text. This allows you to color words
differently based on their id.
```js /age/#v /name/#v /setAge/#s /setName/#s /50/#i /'Taylor'/#i
const [age, setAge] = useState(50);
const [name, setName] = useState("Taylor");
```
const [age, setAge] = useState(50);
const [name, setName] = useState("Taylor");
onVisitHighlightedWord(node, id) {
node.properties.className = ['word'];
// `id` will be either 'v', 's', or 'i'.
// State (v)alue, (s)etter, and (i)nitial value
if (id) {
const backgroundColor = {
v: 'rgb(196 42 94 / 59%)',
s: 'rgb(0 103 163 / 56%)',
i: 'rgb(100 50 255 / 35%)',
}[id];
const color = {
v: 'rgb(255 225 225 / 100%)',
s: 'rgb(175 255 255 / 100%)',
i: 'rgb(225 200 255 / 100%)',
}[id];
// If the word spans across syntax boundaries (e.g. punctuation), remove
// colors from the child nodes.
if (node.properties['data-rehype-pretty-code-wrapper']) {
node.children.forEach((childNode) => {
childNode.properties.style = '';
});
}
node.properties.style =
`background-color: ${backgroundColor}; color: ${color};`;
}
}
Highlight inline code
Append {:lang}
(e.g. {:js}
) to the end of inline code to highlight it like
a regular code block.
This is an array `[1, 2, 3]{:js}` of numbers 1 through 3.
Highlight plain text
Append {:.token}
to the end of the inline code to highlight it based on a
token specified in your VS Code theme. Tokens start with a .
to differentiate
them from a language.
The name of the function is `getStringLength{:.entity.name.function}`.
You can create a map of tokens to shorten this usage throughout your docs:
const options = {
tokensMap: {
fn: "entity.name.function",
},
};
The name of the function is `getStringLength{:.fn}`.
Titles
Add a file title to your code block, with text inside double quotes (""
):
```js title="..."
```
Line numbers
CSS counters can be used to add line numbers.
code {
counter-reset: line;
}
code > .line::before {
counter-increment: line;
content: counter(line);
/* Other styling */
display: inline-block;
width: 1rem;
margin-right: 2rem;
text-align: right;
color: gray;
}
code[data-line-numbers-max-digits="2"] > .line::before {
width: 2rem;
}
code[data-line-numbers-max-digits="3"] > .line::before {
width: 3rem;
}
If you want to conditionally show them, use showLineNumbers
:
```js showLineNumbers
// <code> will have attributes `data-line-numbers` and
// `data-line-numbers-max-digits="n"`
```
If you want to start line number at specific number, use
showLineNumbers{number}
:
```js showLineNumbers{number}
// the first line of this code block will start at {number}
```
Multiple themes (dark/light mode)
Because Shiki generates themes at build time, client-side theme switching support is not built in. There are two popular options for supporting something like Dark Mode with Shiki. See the Shiki docs for more info.
Pass your themes to theme
, where the keys represent
the color mode:
const options = {
theme: {
dark: JSON.parse(
fs.readFileSync(require.resolve("./themes/dark.json"), "utf-8")
),
light: JSON.parse(
fs.readFileSync(require.resolve("./themes/light.json"), "utf-8")
),
},
};
The <code>
and <pre>
element will have a data attribute
data-theme="[key]"
, e.g data-theme="light"
.
Now, you can use CSS to display the desired theme:
@media (prefers-color-scheme: dark) {
pre[data-theme="light"],
code[data-theme="light"] {
display: none;
}
}
@media (prefers-color-scheme: light), (prefers-color-scheme: no-preference) {
pre[data-theme="dark"],
code[data-theme="dark"] {
display: none;
}
}
Custom highlighter
To completely configure the highlighter, use the
getHighlighter
option to provide a Shiki highlighter
instance.
In order to support light and dark modes, Rehype Pretty Code provides an
options
object for you to extend. The
theme
property is preconfigured with the light or
dark theme based on your theme options.
This is helpful if you'd like to configure other Shiki options, such as
langs
.
const { getHighlighter, BUNDLED_LANGUAGES } = require("shiki");
const options = {
theme: {
dark: JSON.parse(
fs.readFileSync(require.resolve("./themes/dark.json"), "utf-8")
),
light: JSON.parse(
fs.readFileSync(require.resolve("./themes/light.json"), "utf-8")
),
},
getHighlighter: (options) =>
getHighlighter({
...options,
langs: [
...BUNDLED_LANGUAGES,
{
id: "groq",
scopeName: "source.groq",
path: "./langs/vscode-sanity/grammars/groq.json",
},
],
}),
};
Filter meta string
If your library also parses code blocks' meta strings, it may
cause conflicts with
rehype-pretty-code
. This option allows you to filter out some part of the meta
string before the library starts parsing it.
const options = {
filterMetaString: (string) => string.replace(/filename="[^"]*"/, ""),
};
License
MIT • View on GitHub