Skip to content

Highlight code blocks with PostHTML and Shiki

License

Notifications You must be signed in to change notification settings

posthtml/posthtml-shiki

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PostHTML

Shiki Plugin

Highlight code with PostHTML and Shiki

Version Build License Downloads

Introduction

This is a PostHTML plugin that uses Shiki to highlight code blocks.

Features:

Input:

<shiki>
  <h1 class="text-xl">Hello</h1>
</shiki>

Output:

<pre class="shiki nord" style="background-color:#2e3440ff;color:#d8dee9ff" tabindex="0"><code><span class="line"></span>
<span class="line"><span style="color:#81A1C1">  &#x3C;h1</span><span style="color:#8FBCBB"> class</span><span style="color:#ECEFF4">=</span><span style="color:#ECEFF4">"</span><span style="color:#A3BE8C">text-xl</span><span style="color:#ECEFF4">"</span><span style="color:#81A1C1">></span><span style="color:#D8DEE9FF">Hello</span><span style="color:#81A1C1">&#x3C;/h1></span></span>
<span class="line"></span></code></pre>

Installation

npm i posthtml posthtml-shiki

Usage

Use the <shiki> tag to highlight all code inside it:

import posthtml from 'posthtml'
import shiki from 'posthtml-shiki'

posthtml([
  shiki()
])
  .process('<shiki><h1 class="text-xl">Hello</h1></shiki>')
  .then(result => result.html)

Attributes

You may use certain attributes to configure which themes or language to use.

lang

Alias: language

Use the lang attribute to specify the language of the code block.

<shiki lang="javascript">
  import { codeToHtml } from 'shiki'
</shiki>

theme

Use the theme attribute to specify the theme to use.

<shiki theme="github-light">
  <h1 class="text-xl">Hello</h1>
</shiki>

theme-*

Shiki's Dual Themes is supported through theme-* attributes:

<shiki theme-light="github-light" theme-dark="github-dark">
  <h1 class="text-xl">Hello</h1>
</shiki>

Note

If a theme attribute is present, it will override the theme-* attributes.

This uses CSS variables to switch between themes, so you'll need to define the CSS variables in your stylesheet.

With media queries:

@media (prefers-color-scheme: dark) {
  .shiki,
  .shiki span {
    color: var(--shiki-dark) !important;
    background-color: var(--shiki-dark-bg) !important;
    /* Optional, if you also want font styles */
    font-style: var(--shiki-dark-font-style) !important;
    font-weight: var(--shiki-dark-font-weight) !important;
    text-decoration: var(--shiki-dark-text-decoration) !important;
  }
}

Class-based:

html.dark .shiki,
html.dark .shiki span {
  color: var(--shiki-dark) !important;
  background-color: var(--shiki-dark-bg) !important;
  /* Optional, if you also want font styles */
  font-style: var(--shiki-dark-font-style) !important;
  font-weight: var(--shiki-dark-font-weight) !important;
  text-decoration: var(--shiki-dark-text-decoration) !important;
}

default-color

When using multiple themes, you may specify the default color theme for Shiki to use.

The value of the attribute must be the name of one of the theme-* attributes, so for example if you have theme-light and theme-dark attributes, the attribute value must be either light or dark.

<shiki 
  theme-light="github-light" 
  theme-dark="github-dark" 
  default-color="dark"
>
  <h1 class="text-xl">Hello</h1>
</shiki>

Shiki relies on CSS specificity and changes the order of the classes on the wrapping <pre> tag.

By default, the plugin does not set default-color.

wrap

By default, the <shiki> tag will be removed and the code block will be wrapped in a <pre> tag. Use the wrap attribute to define a custom tag to wrap the code block in.

<shiki lang="js" wrap="div">
  import { codeToHtml } from 'shiki'
</shiki>

Result:

<div><pre class="shiki nord" style="background-color:#2e3440ff;color:#d8dee9ff" tabindex="0"><code><span class="line"><span style="color:#81A1C1">import</span><span style="color:#ECEFF4"> {</span><span style="color:#8FBCBB"> codeToHtml</span><span style="color:#ECEFF4"> }</span><span style="color:#81A1C1"> from</span><span style="color:#ECEFF4"> '</span><span style="color:#A3BE8C">shiki</span><span style="color:#ECEFF4">'</span></span></code></pre></div>

Important

The value of the wrap attribute must be a valid tag name, CSS selectors are not supported.

Options

The plugin accepts an options object as the first argument, which can be used to configure things like the tag name or the options to pass to Shiki.

tag

Type: string
Default: shiki

Use the tag option to specify the tag name to use.

import posthtml from 'posthtml'
import shiki from 'posthtml-shiki'

posthtml([
  shiki({
    tag: 'highlight'
  })
])
  .process('<highlight>... your code</highlight>')
  .then(result => result.html)

langs

Type: string[]
Default: ['html']

Use the langs option to specify the languages for Shiki to load.

It's recommended to load only the languages that you need.

import posthtml from 'posthtml'
import shiki from 'posthtml-shiki'

posthtml([
  shiki({
    langs: ['html', 'javascript']
  })
])
  .process(`
    <shiki lang="html">... some html</shiki>
    <shiki lang="javascript">... some js</shiki>
  `)
    .then(result => result.html)

See the list of supported languages in Shiki.

Custom Languages

You may also load custom languages by passing a TextMate grammar object to the langs option.

const customDiffLang = JSON.parse(readFileSync('./custom-diff.json', 'utf8'))

posthtml([
  shiki({
    langs: [customDiffLang]
  })
])
  .process(`
    <shiki lang="custom-diff">
      - FOO
      + BAR
    </shiki>
  `)
    .then(result => result.html)

You must specify the lang attribute with the name of the language, and the value must match the name property of the TextMate grammar object.

See tm-grammars for examples.

themes

Type: Array<string> | Array<object>
Default: ['nord']

Use the themes option to specify the themes for Shiki to load.

It's recommended to load only the themes that you need.

import posthtml from 'posthtml'
import shiki from 'posthtml-shiki'

posthtml([
  shiki({
    themes: ['github-light', 'github-dark']
  })
])
  .process(`
    <shiki theme="github-light">[code]</shiki>
    <shiki theme="github-dark">[code]</shiki>
  `)
    .then(result => result.html)

See the list of available themes in Shiki.

Note

If you don't specify a theme="" attribute, the first theme in the themes option will be used.

Custom Themes

You may also load custom themes by passing a TextMate theme object to the themes option:

// Define textmate theme
const myTheme = {
  name: 'my-theme',
  settings: [
    {
      scope: ['string'],
      settings: {
        foreground: '#888'
      }
    },
  ]
}

posthtml([
  shiki({
    themes: [myTheme],
  })
])
  .process(`<shiki theme="my-theme">[code]</shiki>`)
  .then(result => result.html)

If you're loading multiple themes, you will need to specify which theme to use with the theme="" attribute. For custom themes, the attribute value must match the name of the theme - in the example above, that would be my-theme.

wrapTag

Type: string|boolean
Default: false

Use the wrapTag option to specify a custom tag to wrap the highlighted code block in.

By default, the plugin does not wrap the code block in any tag.

import posthtml from 'posthtml'
import shiki from 'posthtml-shiki'

posthtml([
  shiki({
    wrapTag: 'div'
  })
])
  .process('<shiki>... your code</shiki>')
  .then(result => result.html)

Result:

<div>
  [highlighted code]
</div>

defaultColor

Type: string
Default: undefined

Use the defaultColor option to specify the default color theme for Shiki to use.

The value must be the key name of one of the themes in the themes option.

import posthtml from 'posthtml'
import shiki from 'posthtml-shiki'

posthtml([
  shiki({
    themes: {
      light: 'github-light',
      dark: 'github-dark',
    },
    defaultColor: 'dark'
  })
])
  .process(`
    <shiki>
      [code]
    </shiki>
  `)
  .then(result => result.html)

decorations

Type: array
Default: []

Shiki's Decorations are supported through the decorations option.

You can use this to wrap custom classes and attributes around character ranges in your code.

import posthtml from 'posthtml'
import shiki from 'posthtml-shiki'

posthtml([
  shiki({
    decorations: [
      {
        // line and character are 0-indexed
        start: { line: 0, character: 0 },
        end: { line: 0, character: 5 },
        properties: { class: 'highlighted-word' }
      }
    ]
  })
])
  .process(`
    <shiki>
      const foo = 'bar'
    </shiki>
  `)
  .then(result => result.html)

The word const will be wrapped in a <span class="highlighted-word"> tag.

transformers

Type: array
Default: []

Use this option to transform the highlighted code block with Shiki's Transformers.

import posthtml from 'posthtml'
import shiki from 'posthtml-shiki'
import { transformerNotationHighlight } from '@shikijs/transformers'

posthtml([
  shiki({
    transformers: [
      transformerNotationHighlight(),
      {
        code(node) {
          this.addClassToHast(node, 'custom-class')
        },
      },
    ]
  })
])
  .process(`
    <shiki>
      const foo = 'bar'
      let baz = 'biz' // [!code highlight]
    </shiki>
  `)
  .then(result => result.html)

See the docs for Shiki Transformers and a list of common Shiki Transformers.