はてなブログを「Shiki | 式」でシンタックスハイライトする

本ブログでも使っている、Syntax Highlighter Shikiはてなブログに導入する方法をここに記します。

Shiki | 式

Shiki とは TypeScript 製の Syntax Highlighter で、対応言語やテーマの数が豊富なことからとても人気があります。(筆者の主観)

markdown-itVitePress などの Integration が提供されていたり Astro に組み込まれていたりと、触れたことがある人も多いのではないでしょうか。

shiki.style

はてなブログ

さて、はてなブログの Syntax Highlighter を変更するといっても既存のものを止める訳ではありません。

今のところ、HTML のレンダリングに介入することは出来ないので...

置き換える

はてなブログではヘッダに任意の HTML を挿入することができるので、HTML 読み込み時に既存のコードブロック要素を Shikiレンダリングしたものに後から置き換えることにします。

(ここでブラウザをリロードしてみると、コードブロックが置き換わる様子が見えるでしょう)

Installation | Shiki

ドキュメントにあるブラウザ向けの CDN を利用して、はてなブログの HTML 上にあるコードブロックを置き換える処理を実装します。

<script type="module">
import { codeToHtml } from "https://esm.sh/shiki@1.12.0"
const pres = document.querySelectorAll("pre.code")
pres.forEach((pre) => {
    const lang = pre.dataset.lang
    const rawCode = pre.textContent
    ;(async () => {
        const code = await codeToHtml(rawCode, {
            theme: "github-dark",
            lang: lang,
        })
        const dummy = document.createElement("div")
        dummy.innerHTML = code
        pre.replaceWith(dummy.firstChild)
    })()
})
</script>

あとは、ブログの 設定 > 詳細設定 > head内タグ > <head>要素にメタデータを追加 から上記のスクリプトを張り付ければ完成です。

ブログのテーマによっては、CLS(Cumulative Layout Shift)の原因となるので元のコードブロックのフォントサイズなどを揃えておきましょう。

HTML 編集モード

HTML 編集モードでは、はてなブログで生成されるコードブロックの HTML に合わせて <pre> タグに code classと、data-lang attribute に言語を指定してください。

<pre class="code" data-lang="javascript">
console.log("Hello, World!")
</pre>

設定

Themes

Shiki には次のテーマが指定可能です。

Themes | Shiki

prefers-color-scheme

Light/Dark Dual Themes | Shiki

端末のダークモードに追従させることもできます。

-   theme: "github-dark",
+  themes: { 
+    light: 'github-light',
+    dark: 'github-dark',
+  },

上記のようにテーマを設定した後、以下のスタイルをブログの 設定から <head>要素 に追加してください。

<style>
@media (prefers-color-scheme: dark) {
  .shiki,
  .shiki span {
    color: var(--shiki-dark) !important;
    background-color: var(--shiki-dark-bg) !important;
    /* Optional, if you also want font styles */
    font-style: var(--shiki-dark-font-style) !important;
    font-weight: var(--shiki-dark-font-weight) !important;
    text-decoration: var(--shiki-dark-text-decoration) !important;
  }
}
</style>

おまけ

Shiki は v1 から ESM となりました。 つまり、JavaScript モジュールで使う必要があります。

もしも、あなたが過去を生きるユーザーも救いたいということであれば、以下に古いバージョンのコードを用意したのでお使いください。

<script defer src="https://unpkg.com/shiki@0.14.6"></script>
<script>
window.addEventListener("DOMContentLoaded", function () {
    const pres = document.querySelectorAll("pre.code")
    const langs = []
    for (const pre of pres) {
        const lang = pre.dataset.lang
        langs.push(lang)
    }
    shiki.getHighlighter({
    theme: 'github-dark',
    langs: langs,
  }).then(highlighter => {
    pres.forEach(pre => {
      const lang = pre.dataset.lang
      const rawCode = pre.textContent
      const code = highlighter.codeToHtml(rawCode, {
        lang: lang
      })
      const dummy = document.createElement('div')
      dummy.innerHTML = code
      pre.replaceWith(dummy.firstChild)
    })
  })
})
</script>

inline スクリプトに defer 使いたいナ