Astro + rehype-shikijiでMarkdownからシンタックスハイライト付きHTMLを生成する

Dec 22, 2023

はじめに

前回の記事でMarkedとShikiを使って外部APIから取得したMarkdown情報をシンタックスハイライト付きでHTMLに変換する方法をご紹介しました。

今回はShikiをESM向けにリライトしたShikijiを使った方法でコードハイライトを実現したいと思います。

Shikijiとは?

ShikijiはShikiをESM向けに書き直したシンタックスハイライターです。

さらにゼロランタイムで動くので、ビルドして配信後にはJavaScriptなしでシンタックスハイライトを実現できます。

Shikijiにはmarkdown-itやrehypeプラグインなどが提供されているので、今回はremarkを使ってみます。

環境

  • Node.js 20.10.0
  • Astro v4
  • remark-rehype 11.0.0
  • remark-parse 11.0.0
  • remark-gfm 4.0.0
  • rehype-stringify 10.0.0
  • rehype-shikiji 0.9.10
  • unified 11.0.4

Astroプロジェクト作成

npm create astro@latest
cd your-project

MarkdownをHTMLに変換する

remarkを使ってMarkdownからHTMLへの変換処理を行うため必要なパッケージをインストールします。

npm install unified remark-gfm rehype-stringify remark-parse remark-rehype

次にsrc/pages/index.astroを以下のように変更してみます。

※ Astroのテンプレだと背景が黒くなっているので、Layout.astroのスタイルも削除しておいてください。

---
import { unified } from 'unified';
import Layout from '../layouts/Layout.astro';
import remarkParse from 'remark-parse';
import remarkGfm from 'remark-gfm';
import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';
const markdown = `
# Hello, world!

## This is a markdown file

- This is a list
- This is another list item
- This is the last list item

This is a paragraph

\`\`\`js
console.log('Hello, world!');
\`\`\`
`
const html = await unified()
  .use(remarkParse)
  .use(remarkGfm)
  .use(remarkRehype)
  .use(rehypeStringify)
	.process(markdown);

---

<Layout title="Welcome to Astro.">
	<main>
		<div set:html={html}></div>
	</main>
</Layout>

すると以下のような見た目になると思います。ここではそれぞれのremark pluginについて触れませんが、無事にHTMLに変換できました。

Remark Demo

ただ、シンタックスハイライトはまだ効いていないので、rehype-shikijiを使ってシンタックスハイライトを実現します。

rehype-shikijiを導入する

rehype-shikijiをインストールします。

npm install rehype-shikiji

そしてsrc/pages/index.astroへ変更を加えます。

---
import { unified } from 'unified';
import Layout from '../layouts/Layout.astro';
import remarkParse from 'remark-parse';
import remarkGfm from 'remark-gfm';
import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';
import rehypeShikiji from "rehype-shikiji"; // 追加

const markdown = `
# Hello, world!

## This is a markdown file

- This is a list
- This is another list item
- This is the last list item

This is a paragraph

\`\`\`js
console.log('Hello, world!');
\`\`\`
`
const html = await unified()
  .use(remarkParse)
  .use(remarkGfm)
  .use(remarkRehype)
  .use(rehypeStringify)
	// ↓追加
	.use(rehypeShikiji, {
    themes: {
      light: "nord",
      dark: "nord",
    },
  })
	.process(markdown);

---

<Layout title="Welcome to Astro.">
	<main>
		<div set:html={html}></div>
	</main>
</Layout>

<!--追加 -->
<style is:global>
	pre {
      padding: 1rem;
      border-radius: 4px;
      margin-bottom: 1rem;
      overflow: auto;
    }
</style>

スタイルを少し調整しましたが、基本的にrehype-shikijiをremarkに食わせてあげるだけで動きます。

Shikiji Demo

これでいい感じにシンタックスハイライトが動きました!

おわりに

今回はESMフォーカスなShikijiを使ってみました。ブラウザやNode.js、Cloudflare Workersなどで動きバンドルサイズも削減できるので、とても良さそうに思えました。

名前も日本語の「式辞」から来てるそうで親近感が湧くので使っていきたいですね。