使用Shiki高亮行内代码
于25/11/20和25/11/21期间开发了 markdown-it-inline-code 项目,用于高亮行内代码。本文包括效果预览,使用方法,以及部分开发历程。
先展示一下预览效果
- **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!"`- Plain:
printf("Hello, World") - Python:
print("Hello, World") - JavaScript:
console.log("Hello, World") - HTML:
<h1>Hello, World!</h1> - Rust
fn main() { println!("Hello, World!"); } - Shell:
echo "Hello, World!"
Tip
写作过程中,可能觉得 {lang} 前缀看起来比较多余,如果使用Obsidian作为编辑器,可以下载 obsidian-shiki-plugin 并开启 inline-highlight 特性,效果如下

使用方法
下线 markdown-it 渲染引擎
以Hexo为例,默认的渲染引擎(renderer)是 marked[1],其作用是将Markdown转换为HTML。例如:
- 将
**Bold**渲染为<strong>Bold<strong/> - 将
*Italic*渲染为<em>Italic<em/>
本插件基于 markdown-it 渲染引擎开发,因此需要先将渲染引擎切换为 markdown-it。这里个人使用 hexo-renderer-markdown-it-plus插件[2],在Hexo博客的根目录运行如下命令:
bun uninstall hexo-renderer-marked --savebun i hexo-renderer-markdown-it-plus --savebun i markdown-it-inline-code@0.2.0修改 Hexo配置:
markdown_it_plus: highlight: false html: true xhtmlOut: true breaks: true langPrefix: null linkify: true typographer: null quotes: “”‘’ pre_class: highlight plugins: - plugin: name: markdonwn-it-inline-code enable: true测试效果,设置自定义样式
运行 hexo clean && hexo s ,以 *Italic* 为例,预期渲染结果如下
<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>可以参考我的这篇文章为其适配主题切换,这里提供一个基于属性选择器进行切换的CSS Demo
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); }}开发历程
起初看了下markdown-it的插件开发文档,觉得应该很简单,于是便动手开发了。打算睡前完成,结果折腾到4点后放弃了,一是因为笨人是ts, js低手,二是因为睡前不宜写代码
今天又整理了一下代码,Vibe Coding出了一个Beta版本,代码质量有待提高,但是

同步使用 Shiki
起初使用Shiki的 codeToHtml 接口,是个异步接口,返回一个Promise对象。由于markdown-it的 render.rules 只能是同步的,于是参考了同步使用 | Shiki 中文文档,使用 createJavaScriptRegexEngine({lang, themes, engine}) 同步创建高亮器实例,有一点不方便的就是不能由用户传入配置,动态加载语言和主题了,不过用于自娱自乐足矣👍
import { createHighlighterCoreSync } from 'shiki/core'import { createJavaScriptRegexEngine } from 'shiki/engine/javascript'// allLangs: list, shikijs所支持的语言// 见 all-langs.ts in Efterklang/markdown-it-inline-code/srcimport { allLangs } from './all-langs'// themeimport 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')// shiki全局singleton实例function getShiki() { const g = globalThis as any if (g[SHIKI_KEY]) return g[SHIKI_KEY] // Handle potential default export wrapping when bundled/externalized const themeList = themes.map(t => (t as any).default || t) const shiki = createHighlighterCoreSync({ langs: allLangs, themes: themeList, engine: createJavaScriptRegexEngine() }) g[SHIKI_KEY] = shiki return shiki}自定义渲染规则
可以修改 MarkdownIt 实例的 renderer.rules.code_inline 方法,来自定义行内代码的渲染规则,这里的思路是:
- 使用正则表达式匹配代码内容,提取出语言和代码两部分
- 调用
shiki.codeToHtml方法,同步完成代码高亮
function inlineCodeHighlightPlugin( md: MarkdownIt, _options: null ) { // 获取shiki singleton实例 const shiki = getShiki() 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() // 于`{lang} code`中捕获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], { lang: match[1], themes: themeMap, structure: 'inline' }) return '<code' + self.renderAttrs(token) + '>' + highlighted + '</code>' } catch (e) { console.error('Highlighting failed', e) return defaultRender(tokens, idx, options, env, self) } }}export default inlineCodeHighlightPluginMarkdown-it 异步渲染
发现Antfu的 markdown-it-async 项目和Hexo Renderer文档[3],重写了 hexo-renderer-markdown-it-plus 的渲染器,改为异步渲染。伪代码如下:
// file: lib/renderer.jsconst { MarkdownItAsync } = require('markdown-it-async');module.exports = async function (data, options) { let md = new MarkdownItAsync(parseConfig); // init plugin and options md.use(plugin, options); return await md.renderAsync(data.text);}// file: index.jsvar mdit_renderer = require('./lib/renderer');// Promise-based async renderer https://hexo.io/api/renderingfunction asyncRenderer(data, options) { return mdit_renderer.call(this, data, options);}hexo.extend.renderer.register('md', 'html', asyncRenderer);发现用了之后,hexo.extend.tag.register() 注册的标签内的markdown无法被正确渲染,搞不懂,弃坑了
A markdown parser and compiler. Built for speed. markedjs/marked: A markdown parser and compiler. Built for speed. ↩︎
Markdown-it is a markdown parser, done right. A faster and CommonMark compliant alternative for Hexo. CHENXCHEN/hexo-renderer-markdown-it-plus. 插件很久没维护了,不过插件功能简单,本身也无太多开发需求。后续有需要则可能会考虑fork二开,或者找下其他替代 ↩︎
There are two methods for rendering files or strings in Hexo: the asynchronous
hexo.render.rendermethod and the synchronoushexo.render.renderSyncmethod. Rendering | Hexo ↩︎
使用Shiki高亮行内代码