Skip to content

@shikijs/rehype

NPM versionNPM downloadsGitHub

rehype plugin for Shiki.

Install

sh
npm i -D @shikijs/rehype
sh
yarn add -D @shikijs/rehype
sh
pnpm add -D @shikijs/rehype
sh
bun add -D @shikijs/rehype
sh
deno add npm:@shikijs/rehype

Usage

ts
import 
rehypeShiki
from '@shikijs/rehype'
import
rehypeStringify
from 'rehype-stringify'
import
remarkParse
from 'remark-parse'
import
remarkRehype
from 'remark-rehype'
import {
unified
} from 'unified'
const
file
= await
unified
()
.
use
(
remarkParse
)
.
use
(
remarkRehype
)
.
use
(
rehypeShiki
, {
// or `theme` for a single theme
themes
: {
light
: 'vitesse-light',
dark
: 'vitesse-dark',
} }) .
use
(
rehypeStringify
)
.
process
(await fs.readFile('./input.md'))

The default export of @shikijs/rehype uses a shared instance of shiki from getSingletonHighlighter, which will persist across processes. If you want full control over the highlighter lifecycle, use Fine-grained Bundle @shikijs/rehype/core instead.

Fine-grained Bundle

By default, the full bundle of shiki will be imported. If you are using a fine-grained bundle, you can import rehypeShikiFromHighlighter from @shikijs/rehype/core and pass your own highlighter:

ts
import 
rehypeShikiFromHighlighter
from '@shikijs/rehype/core'
import
rehypeStringify
from 'rehype-stringify'
import
remarkParse
from 'remark-parse'
import
remarkRehype
from 'remark-rehype'
import {
createHighlighterCore
} from 'shiki/core'
import {
createOnigurumaEngine
} from 'shiki/engine/oniguruma'
import {
unified
} from 'unified'
const
highlighter
= await
createHighlighterCore
({
themes
: [
import('@shikijs/themes/vitesse-light') ],
langs
: [
import('@shikijs/langs/javascript'), ],
engine
:
createOnigurumaEngine
(() => import('shiki/wasm'))
}) const
raw
= await fs.readFile('./input.md')
const
file
= await
unified
()
.
use
(
remarkParse
)
.
use
(
remarkRehype
)
.
use
(
rehypeShikiFromHighlighter
,
highlighter
, {
// or `theme` for a single theme
themes
: {
light
: 'vitesse-light',
dark
: 'vitesse-dark',
} }) .
use
(
rehypeStringify
)
.
processSync
(
raw
) // it's also possible to process synchronously

Features

Inline Code

You can also highlight inline codes with the inline option.

OptionExampleDescription
false-Disable inline code highlighting (default)
'tailing-curly-colon'let a = 1{:js}Highlight with a {:language} marker inside the code block

Enable inline on the Rehype plugin:

ts
import 
rehypeShiki
from '@shikijs/rehype'
import
rehypeStringify
from 'rehype-stringify'
import
remarkParse
from 'remark-parse'
import
remarkRehype
from 'remark-rehype'
import {
unified
} from 'unified'
const
file
= await
unified
()
.
use
(
remarkParse
)
.
use
(
remarkRehype
)
.
use
(
rehypeShiki
, {
inline
: 'tailing-curly-colon', // or other options
// ... }) .
use
(
rehypeStringify
)
.
process
(await fs.readFile('./input.md'))

Then you can use inline code in markdown:

md
This code `console.log("Hello World"){:js}` will be highlighted.

Postprocess Transformers

INFO

The postprocess transformer hook is only invoked when producing HTML strings (i.e., when using codeToHtml). Since @shikijs/rehype operates on HAST (Hypertext Abstract Syntax Tree) rather than HTML strings, postprocess transformers will not be executed.

This design is intentional: running postprocess hooks in rehype would require converting HAST → HTML → running postprocess → parsing back to HAST, which changes the semantics and could surprise users expecting HAST-only transformers.

Workaround for HTML-based Postprocessing

If you need to run HTML-based postprocessing with rehype, you can apply a root transformer that:

  1. Converts the HAST fragment to HTML using hast-util-to-html
  2. Runs your HTML transformers
  3. Converts back to HAST using hast-util-from-html

Example:

ts
import { fromHtml } from 'hast-util-from-html'
import { toHtml } from 'hast-util-to-html'

const file = await unified()
  .use(remarkParse)
  .use(remarkRehype)
  .use(rehypeShiki, {
    themes: {
      light: 'vitesse-light',
      dark: 'vitesse-dark',
    },
    transformers: [
      {
        name: 'custom-html-postprocessor',
        root(node) {
          // Convert HAST to HTML
          const html = toHtml(node)

          // Apply your HTML transformations
          const processedHtml = myCustomPostprocess(html)

          // Parse back to HAST
          const newNode = fromHtml(processedHtml, { fragment: true })

          // Replace the node
          return newNode
        }
      }
    ]
  })
  .use(rehypeStringify)
  .process(await fs.readFile('./input.md'))

Released under the MIT License.