@shikijs/rehype
rehype plugin for Shiki.
Install
npm i -D @shikijs/rehypeyarn add -D @shikijs/rehypepnpm add -D @shikijs/rehypebun add -D @shikijs/rehypedeno add npm:@shikijs/rehypeUsage
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:
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 synchronouslyFeatures
Inline Code
You can also highlight inline codes with the inline option.
| Option | Example | Description |
|---|---|---|
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:
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:
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:
- Converts the HAST fragment to HTML using
hast-util-to-html - Runs your HTML transformers
- Converts back to HAST using
hast-util-from-html
Example:
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'))