はじめに
このブログのコードブロックのシンタックスハイライトをPrism.jsからShikiに移行しました。
本記事では、移行の背景、具体的な変更内容、そしてShikiに変更したことで得られたメリットについて解説します。
なぜ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への移行を検討してみてください。