rehype plugin to highlight code with Starry Night
- What’s this?
- When should I use this?
- Install
- Use
- API
- Theming
- Examples
- Example: Codeblock with single line
- Example: Codeblock with multiple lines
- Example: Codeblock with title
- Example: Codeblock with prompts
- Example: Codeblock with command and its output
- Example: Codeblock with highlighted lines
- Example: Codeblock with added and removed lines
- Example: Codeblock with unknown language
- Example: Codeblock with aliased language
- Example: Codeblock rendered using custom plugin
- Example: Codeblock rendered using default and custom plugins
- Example: Codeblock rendered without plugins
- Example: Using custom classname prefix
- Example: Using custom marker for inline code
- Example: Code without language info
- Related
- License
This package is a unified (rehype) plugin to highlight code with Starry Night in a markdown document. It mimics GitHub's syntax highlighting.
This project is useful if you want to use the syntax highlighting powered by VS Code's syntax highlighter engine, and themes similar to GitHub. It is also useful if you want to build your own syntax highlighting themes based on CSS custom properties.
The following additonal features are also available out of box:
- line numbers
- line highlights
- annotations for added and removed lines
- prompt character
- title and language information
- highlighting inline
code
elements
This package is ESM only.
In Node.js (version 16.0+), install with npm:
npm install @microflash/rehype-starry-night
In Deno, with esm.sh:
import rehypeStarryNight from "https://esm.sh/@microflash/rehype-starry-night";
In browsers, with esm.sh:
<script type="module">
import rehypeStarryNight from "https://esm.sh/@microflash/rehype-starry-night?bundle";
</script>
Say we have the following file example.md
:
```css
html {
box-sizing: border-box;
text-size-adjust: 100%;
/* allow percentage based heights for the children */
height: 100%;
}
```
And our module example.js
looks as follows:
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify";
import rehypeStarryNight from "@microflash/rehype-starry-night";
main();
async function main() {
const file = await unified()
.use(remarkParse)
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeStarryNight)
.use(rehypeStringify, { allowDangerousHtml: true })
.process(markdown);
console.log(String(file));
}
Running that with node example.js
yields:
<div class="hl hl-css">
<div class="hl-header">
<div class="hl-language"><span>css</span></div>
</div>
<pre id="MC4wNTYxMTQ4" style="--hl-line-number-gutter-factor: 1"><code tabindex="0"><span class="line"><span class="line-number" aria-hidden="true">1</span><span class="pl-ent">html</span> {</span>
<span class="line"><span class="line-number" aria-hidden="true">2</span> <span class="pl-c1">box-sizing</span>: <span class="pl-c1">border-box</span>;</span>
<span class="line"><span class="line-number" aria-hidden="true">3</span> <span class="pl-c1">text-size-adjust</span>: <span class="pl-c1">100</span><span class="pl-k">%</span>;</span>
<span class="line"><span class="line-number" aria-hidden="true">4</span> <span class="pl-c">/* allow percentage based heights for the children */</span></span>
<span class="line"><span class="line-number" aria-hidden="true">5</span> <span class="pl-c1">height</span>: <span class="pl-c1">100</span><span class="pl-k">%</span>;</span>
<span class="line"><span class="line-number" aria-hidden="true">6</span>}</span>
</code></pre>
</div>
To highlight inline code
elements, import rehype-starry-night-inline
plugin. This plugin relies on the language information injected by the remark-inline-code-lang
plugin.
Say we have the following file example.md
:
To print a greeting, use `js> console.log("Hello, world!");`. This code prints `Hello, world!` on the console window.
And our module example.js
looks as follows:
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkInlineCodeLang from "@microflash/rehype-starry-night/remark-inline-code-lang";
import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify";
import rehypeStarryNightInline from "@microflash/rehype-starry-night/rehype-starry-night-inline";
main();
async function main() {
const file = await unified()
.use(remarkParse)
.use(remarkInlineCodeLang)
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeStarryNightInline)
.use(rehypeStringify, { allowDangerousHtml: true })
.process(markdown);
console.log(String(file));
}
Running that with node example.js
yields:
<p>To print a greeting, use <code class="hl-inline hl-js"><span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>Hello, world!<span class="pl-pds">"</span></span>);</code>. This code prints <code>Hello, world!</code> on the console window.</p>
The default export is rehypeStarryNight
. The following options are available. All of them are optional.
aliases
(type:Object
) - used to alias languages to force syntax highlighting. By default, unknown languages are highlighted as plain text.grammars
(type:Array<Grammar>
) - array of Starry Night compatible grammar definitions. By default, all grammars provided by Starry Night are used.classNamePrefix
(type:string
, default:hl
) - prefix of the classNames for different elements of HTML generated by the plugin.plugins
(type:Array<Plugin>
, default:defaultPluginPack
) - a list of plugins to customize the header and lines.
rehype-starry-night
supports two types of plugins: header plugins that modify the header items, and line plugins that modify the lines of code.
// structure of a plugin
export default {
type: "header", // or "line"
plugin: function (globalOptions, nodes) {
// do something with `globalOptions` and `nodes`
}
}
type
(type:string
) - declares whether the plugin is a header or line plugin usingheader
orline
value.globalOptions
(type:Object
) - contains the configuration available to a plugin, such asid
(type:string
) - unique id attached to thepre
elementmetadata
(type:Object
) - configuration specified on the codeblock, parsed withfenceparser
language
(type:string
) - language specified on the codeblock after backticksclassNamePrefix
(type:string
, default:hl
) - prefix of the classNames for different elements of HTML generated by the plugin
nodes
(type:Array<Node>
) - array of hast nodes. For header plugins, it is the array of children of the header. For line plugins, it is the array of the lines of code in the codeblock.
rehype-starry-night
ships with the following plugins out of box.
headerLanguagePlugin
- attaches the language in the headerheaderTitlePlugin
- attaches a title in the header if specified on the codeblocklinePromptPlugin
- used to add a prompt symbol before the start of a linelineOutputPlugin
- used to mark a line as command-line outputlineMarkPlugin
- used to highlight a linelineInsPlugin
- used to annotate an added linelineDelPlugin
- used to annotate a removed line
You can import these plugins individually.
import headerLanguagePlugin from "@microflash/rehype-starry-night/plugins/header-language-plugin";
import headerTitlePlugin from "@microflash/rehype-starry-night/plugins/header-title-plugin";
import linePromptPlugin from "@microflash/rehype-starry-night/plugins/line-prompt-plugin";
import lineOutputPlugin from "@microflash/rehype-starry-night/plugins/line-output-plugin";
import lineMarkPlugin from "@microflash/rehype-starry-night/plugins/line-mark-plugin";
import lineInsPlugin from "@microflash/rehype-starry-night/plugins/line-ins-plugin";
import lineDelPlugin from "@microflash/rehype-starry-night/plugins/line-del-plugin";
Alternatively, you can import them all at once.
import { defaultPluginPack } from "@microflash/rehype-starry-night";
The following options are available for the rehype-starry-night-inline
plugin. All of them are optional.
aliases
(type:Object
) - used to alias languages to force syntax highlighting. By default, unknown languages are highlighted as plain text.grammars
(type:Array<Grammar>
): array of Starry Night compatible grammar definitions. By default, all grammars provided by Starry Night are used.classNamePrefix
(type:string
, default:hl
): prefix of the classNames for different elements of HTML generated by the plugin.
The following options are available for the remark-inline-code-lang
plugin. All of them are optional.
marker
(type:string
, default:>
) - the marker for inlinecode
element before which the language information is specified.
Import props.css
and index.css
files in your project, or use them as a base for your own custom theme. For different color schemes for syntax highlighting, check the available themes on Starry Night repository.
Here's one way to support light and dark themes; the appropriate theme will get activated based on system preferences.
:root {
/* light theme variables specific to rehype-starry-night plugin */
--hl-background-color: hsl(220, 23%, 97%);
--hl-background-color-inline: var(--hl-background-color);
--hl-border-color: hsl(215, 15%, 85%);
--hl-outline-color: hsl(215, 15%, 70%, 0.5);
--hl-line-highlight-background-color: hsl(220, 23%, 92%);
--hl-line-added-background-color: hsla(103, 96%, 73%, 0.5);
--hl-line-removed-background-color: hsla(4, 75%, 83%, 0.5);
--hl-line-active-background-color: hsl(220, 23%, 89%);
--hl-line-number-added-color: hsl(106, 59%, 27%);
--hl-line-number-removed-color: hsl(355, 67%, 41%);
}
@media (prefers-color-scheme: dark) {
:root {
/* dark theme variables specific to rehype-starry-night plugin */
--hl-background-color: hsl(216, 18%, 11%);
--hl-border-color: hsl(215, 11%, 22%);
--hl-outline-color: hsl(215, 11%, 37%, 0.5);
--hl-line-highlight-background-color: hsl(218, 14%, 17%);
--hl-line-added-background-color: hsla(105, 62%, 20%, 0.5);
--hl-line-removed-background-color: hsla(356, 69%, 31%, 0.5);
--hl-line-active-background-color: hsl(218, 14%, 20%);
--hl-line-number-added-color: hsl(105, 51%, 51%);
--hl-line-number-removed-color: hsl(3, 77%, 74%);
}
}
/* import a Starry Night theme that supports both dark and light themes */
@import "https://raw.githubusercontent.com/wooorm/starry-night/main/style/both.css";
/* import CSS specific to rehype-starry-night plugin */
@import "https://raw.githubusercontent.com/Microflash/rehype-starry-night/main/src/index.css";
Warning
URL imports for external styles is not recommended. You should either self-host them, bundle them, or copy-paste the entire CSS in one single file.
```sh
docker ps -a
```
The above codeblock gets rendered as:
<div class="hl hl-sh">
<div class="hl-header">
<div class="hl-language"><span>sh</span></div>
</div>
<pre id="MC4wNjE2ODk0"><code tabindex="0"><span class="line">docker ps -a</span>
</code></pre>
</div>
The plugin does not add line numbers when the codeblock contains a single line.
```css
* {
display: revert;
}
```
The above codeblock gets rendered as:
<div class="hl hl-css">
<div class="hl-header">
<div class="hl-language"><span>css</span></div>
</div>
<pre id="MC4xNzU3MDU0" style="--hl-line-number-gutter-factor: 1"><code tabindex="0"><span class="line"><span class="line-number" aria-hidden="true">1</span><span class="pl-ent">*</span> {</span>
<span class="line"><span class="line-number" aria-hidden="true">2</span> <span class="pl-c1">display</span>: <span class="pl-c1">revert</span>;</span>
<span class="line"><span class="line-number" aria-hidden="true">3</span>}</span>
</code></pre>
</div>
The plugin attaches --hl-line-number-gutter-factor
CSS property on the pre
element when the codeblock contains multiple lines. You can use this property to pad the line numbers and align them. See index.css
.
```zsh title="Switching off homebrew telemetry"
# turns off homebrew telemetry
export HOMEBREW_NO_ANALYTICS=1
# turns off homebrew auto-update
export HOMEBREW_NO_AUTO_UPDATE=1
```
The above codeblock gets rendered as:
<div class="hl hl-zsh">
<div class="hl-header">
<div class="hl-language"><span>zsh</span></div>
<div class="hl-title">Switching off homebrew telemetry</div>
</div>
<pre id="MC4xOTE1OTM1" style="--hl-line-number-gutter-factor: 1"><code tabindex="0"><span class="line"><span class="line-number" aria-hidden="true">1</span><span class="pl-c"># turns off homebrew telemetry</span></span>
<span class="line"><span class="line-number" aria-hidden="true">2</span><span class="pl-k">export</span> HOMEBREW_NO_ANALYTICS=1</span>
<span class="line"><span class="line-number" aria-hidden="true">3</span><span class="pl-c"># turns off homebrew auto-update</span></span>
<span class="line"><span class="line-number" aria-hidden="true">4</span><span class="pl-k">export</span> HOMEBREW_NO_AUTO_UPDATE=1</span>
</code></pre>
</div>
Sometimes you may want to show a prompt character while displaying a command-line instruction. rehype-starry-night
supports this out of box.
```sh prompt{1,3}
curl localhost:8080/actuator/health
{"status":"UP"}
curl localhost:8080/greeter?name=Anya
Hello, Anya!
```
The above codeblock gets rendered as:
<div class="hl hl-sh">
<div class="hl-header">
<div class="hl-language"><span>sh</span></div>
</div>
<pre id="MC43MTQzMTQx" style="--hl-line-number-gutter-factor: 1"><code tabindex="0"><span class="line"><span class="line-number" aria-hidden="true">1</span><span class="line-prompt" aria-hidden="true"></span>curl localhost:8080/actuator/health</span>
<span class="line"><span class="line-number" aria-hidden="true">2</span>{<span class="pl-s"><span class="pl-pds">"</span>status<span class="pl-pds">"</span></span>:<span class="pl-s"><span class="pl-pds">"</span>UP<span class="pl-pds">"</span></span>}</span>
<span class="line"><span class="line-number" aria-hidden="true">3</span><span class="line-prompt" aria-hidden="true"></span>curl localhost:8080/greeter<span class="pl-k">?</span>name=Anya</span>
<span class="line"><span class="line-number" aria-hidden="true">4</span>Hello, Anya<span class="pl-k">!</span></span>
</code></pre>
</div>
You should disable the selection of prompt character so that when people copy the command, the prompt is not copied. See index.css
.
Sometime you may want to display a command and its output, but you want people to just copy the command and not the output. You can mark unselectable lines with output
property.
```sh prompt{1} output{2..6}
mvn -version
Apache Maven 3.9.8
Maven home: ~/maven/3.9.8/libexec
Java version: 22.0.1, vendor: Azul Systems, Inc., runtime: ~/zulu-22.jdk/Contents/Home
Default locale: en_US, platform encoding: UTF-8
OS name: "mac os x", version: "14.5", arch: "aarch64", family: "mac"
```
The above codeblock gets rendered as:
<div class="hl hl-sh">
<div class="hl-header">
<div class="hl-language"><span>sh</span></div>
</div>
<pre id="MC45NTIyNzEx" style="--hl-line-number-gutter-factor: 1"><code tabindex="0"><span class="line"><span class="line-number" aria-hidden="true">1</span><span class="line-prompt" aria-hidden="true"></span>mvn -version</span>
<span class="line" data-line-output="" data-unselectable=""><span class="line-number" aria-hidden="true">2</span>Apache Maven 3.9.8</span>
<span class="line" data-line-output="" data-unselectable=""><span class="line-number" aria-hidden="true">3</span>Maven home: <span class="pl-k">~</span>/maven/3.9.8/libexec</span>
<span class="line" data-line-output="" data-unselectable=""><span class="line-number" aria-hidden="true">4</span>Java version: 22.0.1, vendor: Azul Systems, Inc., runtime: <span class="pl-k">~</span>/zulu-22.jdk/Contents/Home</span>
<span class="line" data-line-output="" data-unselectable=""><span class="line-number" aria-hidden="true">5</span>Default locale: en_US, platform encoding: UTF-8</span>
<span class="line" data-line-output="" data-unselectable=""><span class="line-number" aria-hidden="true">6</span>OS name: <span class="pl-s"><span class="pl-pds">"</span>mac os x<span class="pl-pds">"</span></span>, version: <span class="pl-s"><span class="pl-pds">"</span>14.5<span class="pl-pds">"</span></span>, arch: <span class="pl-s"><span class="pl-pds">"</span>aarch64<span class="pl-pds">"</span></span>, family: <span class="pl-s"><span class="pl-pds">"</span>mac<span class="pl-pds">"</span></span></span>
</code></pre>
</div>
The plugin marks the output lines with [data-unselectable]
attribute. You can set user-select: none
for such elements using CSS. See index.css
.
You can highlight lines by specifying the line numbers (or even, range of line numbers) between curly braces in the codeblock metadata.
```sh {4..7} prompt{1}
aws --endpoint-url http://localhost:4566 s3api list-buckets
{
"Buckets": [
{
"Name": "my-bucket",
"CreationDate": "2022-07-12T13:44:44+00:00"
}
],
"Owner": {
"DisplayName": "webfile",
"ID": "bcaf1ffd86f41161ca5fb16fd081034f"
}
}
```
The above codeblock gets rendered as:
<div class="hl hl-sh">
<div class="hl-header">
<div class="hl-language"><span>sh</span></div>
</div>
<pre id="MC4wNTg1MTA5" style="--hl-line-number-gutter-factor: 2"><code tabindex="0"><span class="line"><span class="line-number" aria-hidden="true">1</span><span class="line-prompt" aria-hidden="true"></span>aws --endpoint-url http://localhost:4566 s3api list-buckets</span>
<span class="line"><span class="line-number" aria-hidden="true">2</span>{</span>
<span class="line"><span class="line-number" aria-hidden="true">3</span> <span class="pl-s"><span class="pl-pds">"</span>Buckets<span class="pl-pds">"</span></span>: [</span>
<span class="line" data-highlighted=""><span class="line-number" aria-hidden="true">4</span> {</span>
<span class="line" data-highlighted=""><span class="line-number" aria-hidden="true">5</span> <span class="pl-s"><span class="pl-pds">"</span>Name<span class="pl-pds">"</span></span>: <span class="pl-s"><span class="pl-pds">"</span>my-bucket<span class="pl-pds">"</span></span>,</span>
<span class="line" data-highlighted=""><span class="line-number" aria-hidden="true">6</span> <span class="pl-s"><span class="pl-pds">"</span>CreationDate<span class="pl-pds">"</span></span>: <span class="pl-s"><span class="pl-pds">"</span>2022-07-12T13:44:44+00:00<span class="pl-pds">"</span></span></span>
<span class="line" data-highlighted=""><span class="line-number" aria-hidden="true">7</span> }</span>
<span class="line"><span class="line-number" aria-hidden="true">8</span> ],</span>
<span class="line"><span class="line-number" aria-hidden="true">9</span> <span class="pl-s"><span class="pl-pds">"</span>Owner<span class="pl-pds">"</span></span>: {</span>
<span class="line"><span class="line-number" aria-hidden="true">10</span> <span class="pl-s"><span class="pl-pds">"</span>DisplayName<span class="pl-pds">"</span></span>: <span class="pl-s"><span class="pl-pds">"</span>webfile<span class="pl-pds">"</span></span>,</span>
<span class="line"><span class="line-number" aria-hidden="true">11</span> <span class="pl-s"><span class="pl-pds">"</span>ID<span class="pl-pds">"</span></span>: <span class="pl-s"><span class="pl-pds">"</span>bcaf1ffd86f41161ca5fb16fd081034f<span class="pl-pds">"</span></span></span>
<span class="line"><span class="line-number" aria-hidden="true">12</span> }</span>
<span class="line"><span class="line-number" aria-hidden="true">13</span>}</span>
</code></pre>
</div>
See the documentation of fenceparser
to learn about the ways in which you can specify the line range for highlighted lines.
You can render code diffs using ins
and del
properties on the codeblock followed by a range of line numbers.
```js title="Pool options in Vitest 2.0" del{4..6} ins{7..9}
export default defineConfig({
test: {
poolOptions: {
threads: {
singleThread: true,
},
forks: {
singleFork: true,
},
}
}
});
```
The above codeblock gets rendered as:
<div class="hl hl-js">
<div class="hl-header">
<div class="hl-language"><span>js</span></div>
<div class="hl-title">Pool options in Vitest 2.0</div>
</div>
<pre id="MC45ODE0NDE0" style="--hl-line-number-gutter-factor: 2; --hl-line-marker-gutter-factor: 1"><code tabindex="0"><span class="line"><span class="line-number" aria-hidden="true">1</span><span class="pl-k">export</span> <span class="pl-c1">default</span> <span class="pl-smi">defineConfig</span>({</span>
<span class="line"><span class="line-number" aria-hidden="true">2</span> test<span class="pl-k">:</span> {</span>
<span class="line"><span class="line-number" aria-hidden="true">3</span> poolOptions<span class="pl-k">:</span> {</span>
<span class="line" data-line-removed=""><span class="line-number" aria-hidden="true">4</span> threads<span class="pl-k">:</span> {</span>
<span class="line" data-line-removed=""><span class="line-number" aria-hidden="true">5</span> singleThread<span class="pl-k">:</span> <span class="pl-c1">true</span>,</span>
<span class="line" data-line-removed=""><span class="line-number" aria-hidden="true">6</span> },</span>
<span class="line" data-line-added=""><span class="line-number" aria-hidden="true">7</span> forks<span class="pl-k">:</span> {</span>
<span class="line" data-line-added=""><span class="line-number" aria-hidden="true">8</span> singleFork<span class="pl-k">:</span> <span class="pl-c1">true</span>,</span>
<span class="line" data-line-added=""><span class="line-number" aria-hidden="true">9</span> },</span>
<span class="line"><span class="line-number" aria-hidden="true">10</span> }</span>
<span class="line"><span class="line-number" aria-hidden="true">11</span> }</span>
<span class="line"><span class="line-number" aria-hidden="true">12</span>});</span>
</code></pre>
</div>
The plugin attaches --hl-line-marker-gutter-factor
CSS property on the pre
element when you specify the codeblock line addition or removal annotations. You can use this property to pad the line numbers and align the icons. See index.css#L94
and index.css#L120
.
See the documentation of fenceparser
to learn about the ways in which you can specify the line range for ins
and del
properties.
If a language is not supported by Starry Night, it will be rendered as plain text.
```nux
let-env NU_LIB_DIRS = [
($nu.config-path | path dirname | path join 'scripts')
]
```
The above codeblock gets rendered as:
<div class="hl hl-nux">
<div class="hl-header">
<div class="hl-language"><span>nux</span></div>
</div>
<pre id="MC42MzYyNTcw" style="--hl-line-number-gutter-factor: 1"><code tabindex="0"><span class="line"><span class="line-number" aria-hidden="true">1</span>let-env NU_LIB_DIRS = [</span>
<span class="line"><span class="line-number" aria-hidden="true">2</span> ($nu.config-path | path dirname | path join 'scripts')</span>
<span class="line"><span class="line-number" aria-hidden="true">3</span>]</span>
</code></pre>
</div>
To prevent a codeblock with an unsupported language to be rendered as plain text, you can force the syntax highlighting with aliases.
Say we have the following file example.md
:
```xjm
language = "en"
customization = false
features = [ "io", "graphics", "compute" ]
```
You can alias xjm
to toml
as follows with example.js
:
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify";
import rehypeStarryNight from "https://esm.sh/@microflash/rehype-starry-night";
main()
async function main() {
const file = await unified()
.use(remarkParse)
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeStarryNight, {
aliases: {
xjm: "toml"
}
})
.use(rehypeStringify, { allowDangerousHtml: true })
.process(markdown);
console.log(String(file));
}
Running that with node example.js
yields:
<div class="hl hl-toml">
<div class="hl-header">
<div class="hl-language"><span>xjm</span></div>
</div>
<pre id="MC40NDMwMTAw" style="--hl-line-number-gutter-factor: 1"><code tabindex="0"><span class="line"><span class="line-number" aria-hidden="true">1</span><span class="pl-smi">language</span> = <span class="pl-s"><span class="pl-pds">"</span>en<span class="pl-pds">"</span></span></span>
<span class="line"><span class="line-number" aria-hidden="true">2</span><span class="pl-smi">customization</span> = <span class="pl-c1">false</span></span>
<span class="line"><span class="line-number" aria-hidden="true">3</span><span class="pl-smi">features</span> = [ <span class="pl-s"><span class="pl-pds">"</span>io<span class="pl-pds">"</span></span>, <span class="pl-s"><span class="pl-pds">"</span>graphics<span class="pl-pds">"</span></span>, <span class="pl-s"><span class="pl-pds">"</span>compute<span class="pl-pds">"</span></span> ]</span>
</code></pre>
</div>
Suppose you want to add a copy to clipboard button in the header. You can do so by adding a custom header plugin.
Say we have the following file example.md
:
```html
<mark>highlighted</mark>
```
You can pass a custom header plugin as follows with example.js
:
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify";
import rehypeStarryNight from "@microflash/rehype-starry-night";
main()
async function main() {
const file = await unified()
.use(remarkParse)
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeStarryNight, {
plugins: [
{
type: "header",
plugin: (globalOptions, nodes) => {
nodes.push({
type: "element",
tagName: "button",
properties: {
className: [`${globalOptions.classNamePrefix}-copy`],
style: "margin-left: auto"
},
children: [
{ type: "text", value: "Copy to clipboard" }
]
});
}
}
]
})
.use(rehypeStringify, { allowDangerousHtml: true })
.process(markdown);
console.log(String(file));
}
Running that with node example.js
yields:
<div class="hl hl-html">
<div class="hl-header">
<button class="hl-copy" style="margin-left: auto">Copy to clipboard</button>
</div>
<pre id="MC44MjkyNjY4"><code tabindex="0"><span class="line"><<span class="pl-ent">mark</span>>highlighted</<span class="pl-ent">mark</span>></span>
</code></pre>
</div>
You can also use the default plugins alongside your custom plugins.
Say we have the following file example.md
:
```rust title="hello.rs"
fn main() {
println!("Hello, world!");
}
```
You can pass a custom plugin alongwith default plugins as follows with example.js
:
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify";
import rehypeStarryNight, { defaultPluginPack } from "@microflash/rehype-starry-night";
main()
async function main() {
const file = await unified()
.use(remarkParse)
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeStarryNight, {
plugins: [
...defaultPluginPack,
{
type: "header",
plugin: (globalOptions, nodes) => {
nodes.push({
type: "element",
tagName: "button",
properties: {
className: [`${globalOptions.classNamePrefix}-copy`],
style: "margin-left: auto"
},
children: [
{ type: "text", value: "Copy to clipboard" }
]
});
}
}
]
})
.use(rehypeStringify, { allowDangerousHtml: true })
.process(markdown);
console.log(String(file));
}
Running that with node example.js
yields:
<div class="hl hl-rust">
<div class="hl-header">
<div class="hl-language"><span>rust</span></div>
<div class="hl-title">hello.rs</div>
<button class="hl-copy" style="margin-left: auto">Copy to clipboard</button>
</div>
<pre id="MC41ODEyMDY2" style="--hl-line-number-gutter-factor: 1"><code tabindex="0"><span class="line"><span class="line-number" aria-hidden="true">1</span><span class="pl-k">fn</span> <span class="pl-en">main</span>() {</span>
<span class="line"><span class="line-number" aria-hidden="true">2</span> <span class="pl-en">println!</span>(<span class="pl-s"><span class="pl-pds">"</span>Hello, world!<span class="pl-pds">"</span></span>);</span>
<span class="line"><span class="line-number" aria-hidden="true">3</span>}</span>
</code></pre>
</div>
If you want to disable all plugins, you can do so by setting plugins: false
while configuring rehype-starry-night
.
Say we have the following file example.md
:
```yml
- name: Job summary
run: |
echo "# Deployment result" >> $GITHUB_STEP_SUMMARY
echo "**Preview URL** = $PREVIEW_URL" >> $GITHUB_STEP_SUMMARY
```
You can disable the plugins as follows with example.js
:
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify";
import rehypeStarryNight from "@microflash/rehype-starry-night";
main()
async function main() {
const file = await unified()
.use(remarkParse)
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeStarryNight, {
plugins: false
})
.use(rehypeStringify, { allowDangerousHtml: true })
.process(markdown);
console.log(String(file));
}
Running that with node example.js
yields:
<div class="hl hl-yml">
<pre id="MC40MTIxNzg1" style="--hl-line-number-gutter-factor: 1"><code tabindex="0"><span class="line"><span class="line-number" aria-hidden="true">1</span>- <span class="pl-ent">name</span>: <span class="pl-s">Job summary</span></span>
<span class="line"><span class="line-number" aria-hidden="true">2</span> <span class="pl-ent">run</span>: <span class="pl-s">|</span></span>
<span class="line"><span class="line-number" aria-hidden="true">3</span><span class="pl-s"> echo "# Deployment result" >> $GITHUB_STEP_SUMMARY</span></span>
<span class="line"><span class="line-number" aria-hidden="true">4</span><span class="pl-s"> echo "**Preview URL** = $PREVIEW_URL" >> $GITHUB_STEP_SUMMARY</span></span>
</code></pre>
</div>
You can attach your own prefix on the classes of HTML elements generated by the rehype-starry-night
and rehype-starry-night-inline
plugins.
Say we have the following file example.md
:
```java
System.out.println("Hello, world!");
```
You can customize the className prefix as follows with example.js
:
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify";
import rehypeStarryNight from "@microflash/rehype-starry-night";
main()
async function main() {
const file = await unified()
.use(remarkParse)
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeStarryNight, {
classNamePrefix: "highlight"
})
.use(rehypeStringify, { allowDangerousHtml: true })
.process(markdown);
console.log(String(file));
}
Running that with node example.js
yields:
<div class="highlight highlight-java">
<div class="highlight-header">
<div class="highlight-language">java</div>
</div>
<pre id="MC42NjM4OTE0"><code tabindex="0"><span class="line"><span class="pl-smi">System</span><span class="pl-k">.</span>out<span class="pl-k">.</span>println(<span class="pl-s"><span class="pl-pds">"</span>Hello, world!<span class="pl-pds">"</span></span>);</span>
</code></pre>
</div>
Similarly for inline code
element, say we have the following file example.md
:
To remove the whitespace around a string, try `java> str.strip()`.
You can customize the className prefix as follows with example.js
:
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkInlineCodeLang from "@microflash/rehype-starry-night/remark-inline-code-lang";
import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify";
import rehypeStarryNightInline from "@microflash/rehype-starry-night/rehype-starry-night-inline";
main();
async function main() {
const file = await unified()
.use(remarkParse)
.use(remarkInlineCodeLang)
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeStarryNightInline, {
classNamePrefix: "highlight"
})
.use(rehypeStringify, { allowDangerousHtml: true })
.process(markdown);
console.log(String(file));
}
Running that with node example.js
yields:
<p>To remove the whitespace around a string, try <code class="highlight-inline highlight-java">str<span class="pl-k">.</span>strip()</code>.</p>
You can configure a custom marker for inline code
element to inject the language information. For example, say you want to annotate your inline code
element with :
instead of the default >
marker, as shown in the following file example.md
:
To specify the language direction, use `html: <span dir="rtl">مرحبا</span>`.
You can customize the marker as follows with example.js
:
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkInlineCodeLang from "@microflash/rehype-starry-night/remark-inline-code-lang";
import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify";
import rehypeStarryNightInline from "@microflash/rehype-starry-night/rehype-starry-night-inline";
main();
async function main() {
const file = await unified()
.use(remarkParse)
.use(remarkInlineCodeLang, {
marker: ": "
})
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeStarryNightInline)
.use(rehypeStringify, { allowDangerousHtml: true })
.process(markdown);
console.log(String(file));
}
Running that with node example.js
yields:
<p>To specify the language direction, use <code class="hl-inline hl-html"><<span class="pl-ent">span</span> <span class="pl-e">dir</span>=<span class="pl-s"><span class="pl-pds">"</span>rtl<span class="pl-pds">"</span></span>>مرحبا</<span class="pl-ent">span</span>></code>.</p>
If you don't provide the language info in the codeblock, rehype-starry-night
will render it as plain text without header.
```
import gleam/io
pub fn main() {
io.println("hello, friend!")
}
```
The above codeblock gets rendered as:
<div class="hl hl-txt">
<pre id="MC44MzU2OTkz" style="--hl-line-number-gutter-factor: 1"><code tabindex="0"><span class="line"><span class="line-number" aria-hidden="true">1</span>import gleam/io</span>
<span class="line"><span class="line-number" aria-hidden="true">2</span></span>
<span class="line"><span class="line-number" aria-hidden="true">3</span>pub fn main() {</span>
<span class="line"><span class="line-number" aria-hidden="true">4</span> io.println("hello, friend!")</span>
<span class="line"><span class="line-number" aria-hidden="true">5</span>}</span>
</code></pre>
</div>
Similarly for inline code
element without language information:
`gleam new` command will generate a new Gleam project.
It gets rendered as:
<p><code>gleam new</code> command will generate a new Gleam project.</p>
rehype-starry-night
— alternative plugin to apply syntax highlighting to code withstarry-night
rehype-highlight
— highlight code with highlight.js (through lowlight)rehype-prism-plus
— highlight code with Prism (via refractor) with additional line highlighting and line numbers functionalities@shikijs/rehype
— highlight code with shiki