AstroブログのシンタックスハイライトをPrismからShikiに移行した

はじめに

このブログのコードブロックのシンタックスハイライトをPrism.jsからShikiに移行しました。

本記事では、移行の背景、具体的な変更内容、そしてShikiに変更したことで得られたメリットについて解説します。

Image in a image block

なぜShikiに移行したのか

Prism.jsの課題

これまで使用していたPrism.jsは長年の実績があるシンタックスハイライターですが、いくつかの課題がありました:

  • 言語サポートの追加が手動: 新しい言語を追加するたびに個別にimportが必要
  • テーマのカスタマイズ性: CSSベースのテーマで、細かい調整が難しい
  • トークン精度: 正規表現ベースのため、複雑な構文の解析精度に限界がある
Shikiの魅力

ShikiはVS Codeと同じTextMate文法エンジンを使用したシンタックスハイライターです:

  • VS Codeと同じ品質: エディタで見ているのと同じ精度のハイライト
  • ゼロランタイム: ビルド時に処理され、クライアントにJavaScriptを送信しない
  • 豊富なテーマ: VS Codeのテーマがそのまま使える
  • 200+言語サポート: 追加importなしで多数の言語に対応

具体的な変更内容

1. パッケージのインストール
npm install shiki
npm uninstall prismjs @types/prismjs
2. Code.astroの変更

変更前(Prism.js)

import Prism from 'prismjs'
import 'prismjs/components/prism-css'
import 'prismjs/components/prism-diff'
import 'prismjs/components/prism-docker'
import 'prismjs/components/prism-go'
import 'prismjs/components/prism-java'
import 'prismjs/components/prism-json'
import 'prismjs/components/prism-python'
import 'prismjs/components/prism-typescript'
import 'prismjs/components/prism-yaml'
// ... 他の言語も個別にimport

const grammer = Prism.languages[language] || Prism.languages.javascript

// テンプレート内で
<pre><code set:html={Prism.highlight(code, grammer, language)} /></pre>

変更後(Shiki)

import { codeToHtml } from 'shiki'

// 言語名のマッピング(Notionの言語名 → Shikiの言語名)
const languageMap: Record<string, string> = {
  'plain text': 'text',
  'objective-c': 'objc',
  'c++': 'cpp',
  'c#': 'csharp',
  'f#': 'fsharp',
  'visual basic': 'vb',
}
const shikiLang = languageMap[language] || language

let highlightedCode = ''
try {
  highlightedCode = await codeToHtml(code, {
    lang: shikiLang,
    theme: 'dracula',
  })
} catch {
  // サポート外の言語はプレーンテキストにフォールバック
  highlightedCode = await codeToHtml(code, {
    lang: 'text',
    theme: 'dracula',
  })
}

// テンプレート内で
<Fragment set:html={highlightedCode} />
3. スタイルの調整

Shikiはインラインスタイルでハイライトを適用するため、CSSの調整が必要でした。draculaテーマのダーク背景(#282a36)に合わせてUIを統一:

.code > div > div {
  background: #282a36;  /* draculaの背景色 */
}

.code button.copy {
  background-color: rgba(255, 255, 255, 0.1);
  color: #f8f8f2;  /* draculaのテキスト色 */
}

/* Shikiが生成するpre/codeにスタイルを適用 */
.code :global(pre) {
  margin: 0;
  border-radius: 0;
}

.code :global(pre code) {
  background: transparent !important;
}

Shikiに変更して何が変わったか

1. コードの見た目が向上

VS Codeと同じTextMate文法を使用しているため、より正確で美しいシンタックスハイライトが実現しました。特に以下の点で改善が見られます:

  • JSX/TSXの精度向上: タグ、props、式の区別がより明確に
  • 文字列内の補間: テンプレートリテラル内の${}が適切にハイライト
  • 型アノテーション: TypeScriptの型が適切に色分け
2. メンテナンス性の向上
// Before: 言語ごとに手動import
import 'prismjs/components/prism-css'
import 'prismjs/components/prism-docker'
import 'prismjs/components/prism-go'
// ... 新しい言語を追加するたびに追記

// After: importなしで200+言語に対応
highlightedCode = await codeToHtml(code, {
  lang: shikiLang,  // どの言語でもOK
  theme: 'dracula',
})
3. テーマ変更が容易に

テーマを変更したい場合は、1行変えるだけ:

// dracula → github-light に変更
highlightedCode = await codeToHtml(code, {
  lang: shikiLang,
  theme: 'github-light',  // ここを変えるだけ
})

利用可能なテーマ例:

  • dracula - ダークテーマ(今回採用)
  • github-light / github-dark - GitHub風
  • one-dark-pro - Atom風
  • nord - Nord配色
  • vitesse-dark / vitesse-light - Vitesse
4. パフォーマンス

Shikiはビルド時にHTMLを生成するため、クライアントサイドでのJavaScript実行が不要です。Prism.jsもSSGで使えばビルド時処理ですが、Shikiの方がバンドルサイズの観点で有利です。

注意点

非同期処理

ShikiのcodeToHtmlは非同期関数のため、Astroコンポーネントのfrontmatterでawaitを使う必要があります。Astroは標準でトップレベルawaitをサポートしているので問題ありません。

言語名のマッピング

Notionの言語名とShikiの言語名が異なる場合があるため、マッピングテーブルを用意しました:

const languageMap: Record<string, string> = {
  'plain text': 'text',
  'objective-c': 'objc',
  'c++': 'cpp',
  'c#': 'csharp',
  'f#': 'fsharp',
  'visual basic': 'vb',
}
エラーハンドリング

サポートされていない言語が指定された場合のフォールバック処理も重要です:

try {
  highlightedCode = await codeToHtml(code, { lang: shikiLang, theme: 'dracula' })
} catch {
  highlightedCode = await codeToHtml(code, { lang: 'text', theme: 'dracula' })
}

まとめ

PrismからShikiへの移行により、以下のメリットを得られました:

項目 Prism.js Shiki
ハイライト精度 正規表現ベース TextMate文法(VS Code同等)
言語追加 手動import必要 追加作業不要
テーマ変更 CSS差し替え 1行変更
利用可能テーマ 限定的 VS Codeテーマ全て

移行作業自体も比較的シンプルで、主な変更はCode.astroの1ファイルのみでした。コードブロックの見た目を改善したい方は、ぜひShikiへの移行を検討してみてください。

参考リンク