Astro でコードブロックのシンタックスハイライトをしつつタイトルも付ける
Astro 標準のシンタックスハイライトには、タイトルを付ける仕組みが存在しない。remark-code-title
という Remark のプラグインを使うことで実現できるが、シンタックスハイライトと干渉することを防ぐために、code
要素の手前に div
要素を置く形になっており、AST や最終的な HTML の構造上、コードブロック本体とタイトルに関係性がない。
# 似たようなプラグインに rehype-code-titles
もあるが、Astro の場合はプラグインとシンタックスハイライトの処理順の兼ね合いで動作しない。
構造上コードとタイトルが結びつくようにすべく、標準のシンタックスハイライトを無効化して自前でシンタックスハイライトを実装した。
と言っても、シンタックスハイライト自体は Astro のコンポーネントとして提供されているので、そんなに難しいことはしていない。コードとタイトルをひとまとめで Astro のコンポーネントに置換する Remark プラグインを実装し、あとは Astro のコンポーネントの世界で Prism を呼び出しつつ HTML を出力すればよい。
今回は、figure
要素でラップして figcaption
要素でタイトルを付ける形の HTML を出力した。
yarn add --dev @types/mdast
import type { Code } from "mdast"
import { visit } from "unist-util-visit"
export default function remarkCodeBlock() {
return (tree: any) => {
visit(tree, "code", function (node) {
const { lang, meta, value } = node as Code
const title = meta
node.type = "paragraph"
node.data = {
hName: "code-block",
hProperties: {
code: value,
...(lang ? { lang } : {}),
...(title ? { title } : {}),
},
}
node.children = []
})
}
}
import mdx from "@astrojs/mdx"
import { defineConfig } from "astro/config"
import remarkCodeBlock from "./src/remark/remark-code-block"
export default defineConfig({
integrations: [
mdx(),
],
markdown: {
syntaxHighlight: false, // default false なので実際には書かなくてよい
remarkPlugins: [
remarkCodeBlock,
],
},
})
---
import { Prism } from "@astrojs/prism"
interface Props {
lang?: string
title?: string
code: string
}
const { lang, title, code } = Astro.props
---
<figure>
{title && <figcaption>{title}</figcaption>}
<Prism lang={lang} code={code} />
</figure>
---
import CodeBlock from "../components/CodeBlock.astro"
const { Content } await entry.render()
---
<Content
components={{
"code-block": CodeBlock,
}}
/>
あとは、Markdown を書くときに以下のように言語名の後ろにスペースを挟んでタイトルを書けばよい (シンタックスハイライトを使わずにタイトルだけ付けたい場合には言語を plaintext
とする)。
```astro hello.astro
---
---
<p>Hello, world!</p>
```
この例は以下のようにレンダリングされる。
---
---
<p>Hello, world!</p>
ちなみに、Markdown の AST 仕様である mdast では、言語名の後ろにスペースを挟んで書かれた文字列は meta
として保持されることになっている。今回は meta
全体をそのままタイトルとして使ったものの、他にも情報を含めておいてレンダリングに活用することも考えられる。