﻿---
title: Inline Code Highlighting with markdown-it & Shiki
date: 2025-11-21
tags:
  - Shiki
  - Markdown
  - Hexo
  - Blog
excerpt: I built markdown-it-inline-code on Nov 20–21, 2025 to highlight inline code. This post covers a preview, usage, and the development story.
updated: 2026-07-08 21:17:39
lang: en
i18n:
  cn: /inline_code_highlight
  translation: 2
---

<script type="module" src="/js/components/tab.js"></script>

> [!danger] This post is outdated
>
> The project has switched to [Efterklang/hexo-renderer-markdown-exit](https://github.com/Efterklang/hexo-renderer-markdown-exit) + markdown-exit-shiki

First, a preview of the result

<x-tabs>

<x-tab title="Markdown" active>

```md
- **Plain**: `printf("Hello, World")`
- **Python**: `{python} print("Hello, World")`
- **JavaScript**: `{javascript} console.log("Hello, World")`
- **HTML**: `{html} <h1>Hello, World!</h1>`
- **Rust** `{rust} fn main() { println!("Hello, World!"); }`
- **Shell**: `{shell} echo "Hello, World!"`
```

</x-tab>

<x-tab title="HTML">

- **Plain**: `printf("Hello, World")`
- **Python**: `{python} print("Hello, World")`
- **JavaScript**: `{javascript} console.log("Hello, World")`
- **HTML**: `{html} <h1>Hello, World!</h1>`
- **Rust** `{rust} fn main() { println!("Hello, World!"); }`
- **Shell**: `{shell} echo "Hello, World!"`

</x-tab>

</x-tabs>

> Tips: If you use Obsidian as your editor, install [obsidian-shiki-plugin](https://github.com/mProjectsCode/obsidian-shiki-plugin) and enable the `inline-highlight` feature

## Usage

### Install a markdown-it renderer

Take **Hexo** as an example. Its default renderer is marked[^1], which converts Markdown into HTML. For example:

- `{md} **Bold**` renders as `{html} <strong>Bold<strong/>`
- `{md} *Italic*` renders as `{html} <em>Italic<em/>`

This plugin is built on the `markdown-it` renderer, so you need to switch to `markdown-it` first. I use the hexo-renderer-markdown-it-plus plugin[^2]. Run these commands in the root of your Hexo blog:

```sh
bun uninstall hexo-renderer-marked --save
bun i hexo-renderer-markdown-it-plus --save
bun i markdown-it-inline-code@0.2.0
```

Update the Hexo config:

```yml _config.yml
markdown_it_plus:
  highlight: false
  html: true
  xhtmlOut: true
  breaks: true
  langPrefix: null
  linkify: true
  typographer: null
  quotes: “”‘’
  pre_class: highlight
  plugins:
    - plugin:
      name: markdown-it-inline-code
      enable: true
```

### Test the output, add custom styles

Run `{shell} hexo clean && hexo s`. Taking `{md} *Italic*` as an example, the expected rendered output is:

```html
<code>
  <span
    style="
      color: #d20f39;
      --shiki-light-font-style: italic;
      --shiki-dark: #f38ba8;
      --shiki-dark-font-style: italic;
      --shiki-tokyo: #c0caf5;
      --shiki-tokyo-font-style: italic;
    "
  >
    Italic
  </span>
</code>
```

You can refer to [[web_theme#代码高亮 —— Shiki|my earlier post]] to make it work with theme switching. Here is a CSS demo that switches via attribute selectors:

```css
code span {
  font-style: var(--shiki-light-font-style);
}

:where([data-theme="tokyo_night"]) {
  code span {
    font-style: var(--shiki-tokyo-font-style);
    color: var(--shiki-tokyo);
  }
}

:where([data-theme="mocha"], [data-theme="macchiato"]) {
  code span {
    font-style: var(--shiki-dark-font-style);
    color: var(--shiki-dark);
  }
}
```

## Development Story

At first I skimmed markdown-it's plugin docs and figured it should be easy, so I started coding. The plan was to finish before bed, but I ended up grinding until 4 a.m. and gave up. Partly because I'm no good at TS/JS, partly because coding before sleep is a bad idea.

The next day I cleaned up the code and vibe-coded a beta version. The code quality could be better, but

![Mr. Luo](https://assets.vluv.space/又不是不能用.avif)

### Using Shiki Synchronously

Initially I used Shiki's `codeToHtml` API, which is async and returns a Promise. Since markdown-it's `render.rules` must be synchronous, I followed [Synchronous Usage | Shiki docs](https://shiki.zhcndoc.com/guide/sync-usage#%E5%90%8C%E6%AD%A5%E4%BD%BF%E7%94%A8) and used `{js} createJavaScriptRegexEngine({lang, themes, engine})` to create a highlighter instance synchronously

```ts index.ts
import { createHighlighterCoreSync } from 'shiki/core'
import { createJavaScriptRegexEngine } from 'shiki/engine/javascript'
// allLangs: list of languages supported by shikijs
// see all-langs.ts in Efterklang/markdown-it-inline-code/src
import { allLangs } from './all-langs'
// theme
import latte from '@shikijs/themes/catppuccin-latte'
import mocha from '@shikijs/themes/catppuccin-mocha'
import tokyo_night from '@shikijs/themes/tokyo-night'
const themes = [latte, mocha, tokyo_night]

const SHIKI_KEY = Symbol.for('mdit-inline-code-shiki')

// global shiki singleton instance
function getShiki() { // [!code warning]
    const g = globalThis as any // [!code warning]
    if (g[SHIKI_KEY]) return g[SHIKI_KEY] // [!code warning]
 // [!code warning]
    // Handle potential default export wrapping when bundled/externalized // [!code warning]
    const themeList = themes.map(t => (t as any).default || t) // [!code warning]
 // [!code warning]
    const shiki = createHighlighterCoreSync({ // [!code warning]
        langs: allLangs, // [!code warning]
        themes: themeList, // [!code warning]
        engine: createJavaScriptRegexEngine() // [!code warning]
    }) // [!code warning]
    g[SHIKI_KEY] = shiki // [!code warning]
    return shiki // [!code warning]
} // [!code warning]
```

### Custom Render Rules

You can override the `renderer.rules.code_inline` method on the `MarkdownIt` instance to customize inline code rendering. The idea here:

1. Match the code content with a regex and split out the language and the code
2. Call `shiki.codeToHtml` to highlight the code synchronously

```ts
function inlineCodeHighlightPlugin( md: MarkdownIt, _options: null ) {
    // get the shiki singleton instance
    const shiki = getShiki() // [!code highlight]

    const defaultRender =
        md.renderer.rules.code_inline ||
        function (tokens, idx, _options, _env, self) {
            const token = tokens[idx]
            if (!token) return ''
            return (
                '<code' +
                self.renderAttrs(token) +
                '>' +
                md.utils.escapeHtml(token.content) +
                '</code>'
            )
        }

    const themeMap = {
        light: 'catppuccin-latte',
        dark: 'catppuccin-mocha',
        tokyo: 'tokyo-night'
    }

    md.renderer.rules.code_inline = function (tokens, idx, options, env, self) {
        const token = tokens[idx]
        if (!token) return ''

        const content = token.content.trim()
        // capture lang and code from `{lang} code`
        const match = content.match(/^\{(\w+)\}\s+(.+)$/)

        if (match === null) {
            return defaultRender(tokens, idx, options, env, self)
        }

        try {
            const highlighted = shiki.codeToHtml(match[2], { // [!code highlight]
                lang: match[1], // [!code highlight]
                themes: themeMap, // [!code highlight]
                structure: 'inline' // [!code highlight]
            }) // [!code highlight]

            return '<code' + self.renderAttrs(token) + '>' + highlighted + '</code>'
        } catch (e) {
            console.error('Highlighting failed', e)
            return defaultRender(tokens, idx, options, env, self)
        }
    }
}

export default inlineCodeHighlightPlugin
```

[^1]: A markdown parser and compiler. Built for speed. [markedjs/marked: A markdown parser and compiler. Built for speed.](https://github.com/markedjs/marked)
[^2]: Markdown-it is a markdown parser, done right. A faster and CommonMark compliant alternative for Hexo.  [CHENXCHEN/hexo-renderer-markdown-it-plus.](https://github.com/CHENXCHEN/hexo-renderer-markdown-it-plus) The plugin hasn't been maintained for a while, but it's simple and there isn't much to develop anyway. If needed later, I may fork it or look for an alternative
