序
由于非常喜欢 Zed 博客 中图片块支持说明的样式,我决定给我的 Blog 也加上这个功能。
一番搜寻,网上的解决方案基本都是在 MDX 中实现的。
如果你看过我的上一篇帖子 Astro 修改(1) — 增加 MarkDown Mermaid 支持
你应该可以发现我有一点赛博洁癖,不喜欢引入额外的 JS,当然放在这里,我也不会引入 MDX。
我目前的 Astro 渲染流程
由于我更改了 Astro 的渲染流程,我没法在 Shiki 渲染后再通过 Rehype 来给 Shiki 高亮过的代码块包裹一层 <figure>。
而在 Shiki 之前通过 Rehype 包裹一层 <figure> 的话,Shiki 就完全没法工作了,查看 rehype-shiki 的源代码可以看到:
export async function createShikiHighlighter({}): {
async function highlight() {}
code = code.replace(/(?:\r\n|\r|\n)$/, "");
return highlighter[to === "html" ? "codeToHtml" : "codeToHast"](code, {
transformers: [
{
pre(node) {
// 略
},
line(node) {
// 略
},
code(node) {
// 略
},
},
],
});
}
return {
codeToHast(code, lang, options = {}) {
return highlight(code, lang, options, "hast") as Promise<Root>;
},
codeToHtml(code, lang, options = {}) {
return highlight(code, lang, options, "html") as Promise<string>;
},
};
}Shiki 只会处理 code line pre 这三个 Node,如果换成其他标签那么 Shiki 就直接跳过不处理了。
但是非常幸运的是,Shiki 提供了 Transformers 来作为拓展 Shiki 处理流程的方法。
了解 Shiki 的渲染流程
PREprocess- 在代码标签化之前调用,你可以使用它在代码渲染为标签之前进行修改。tokens- 在代码标签化之后调用,你可以使用它来修改标签。span- 对每个<span>标签及每个标记都调用。line- 对每行<span>标签调用。code- 对每个<code>标签调用,这将会包裹所有的行。pre- 对每个<pre>标签调用,并将其包裹在<code>标签中。root- HAST 树的根,通常情况下只有<pre>一个子标签。POSTprocess- 在 HTML 生成后调用,用来修改最终输出,在 codeToHast 中不会被调用。
而 Transformers 可以在任意节点介入对 Shiki 代码做出相应处理,所以我的实现便是在 root 节点做出对 HTML 的改动,如果有简介,则包裹一层 <figure>。
而如何判断是否有简介呢?Shiki 提供了一个方法,this.options.meta.__raw 可以获取反引号之间除了语言类别之外的内容。
实现
shikiTransformerCodeBlockFigure
它已经在 GitHub 了,非常简单的实现。