WordPressでShikiを導入!CDNと遅延読み込みで高速化する実装術
「Prism.js」というシンタックスハイライターを使っていましたが、VS Codeに採用されている「Shiki」を使ってみたいと思いました。このブログはWordPressを利用して作成しているので、プラグインを使えば簡単にできそうですが、プラグインを使わない方法を探していました。
「Prism.js」や「highlight.js」のように分かりやすい方法が見つけられず、何度か挑んだのですが上手く行きませんでした。今回、CDNにアクセスする方法を使い、思ったより短いコードで「Shiki」が使えたので備忘録を残します。
Shikiの導入方法
ページの表示レスポンスを考えてCDNへのアクセスは避けてきたのですが、「Shiki」は手間をかけてサーバーに設置する方法、CDNへアクセスする方法のどちらでも負荷は大きいので、導入の簡単なCDN方式を選びます。また、ファーストビュー領域にコードを書かなければ、シンタックスハイライターはページを読み込むと同時に動作する必要がありません。
加えて、人が読みやすくなるためだけの機能なので、シンタックスハイライターがなくても表示に異常がある訳ではないのでSEOに影響しません。そこで、遅延読込みさせて初期のページ読み込みの負荷を減らします。少しスクロール(50px)したタイミングで「Shiki」を動かします。
- 使用言語は「js, css, html, php, json, bash」。
- 使用テーマは「Antigravity」と同じにしたい。
- 50pxスクロールするまでCDNにアクセスしない。
言語は「js, css, html, php, json」とそれ以外のために「bash」を使います。Linuxを使っているので、たまに「bash」の記述があるのと、それ以外のコードにも「bash」を指定すると色がつくので、その他の言語用にも使います。テーマの配色は慣れているAntigravityのデフォルト色を探します。
JavaScriptコード
「Prism.js」ではCSSで色を決めていたので、好みに変えられましたが、「Shiki」はJavaScriptでHTMLにスタイル属性を加えて色指定をするので、CSSファイルは不要です。テーマは「slack-dark」が見慣れたテーマでした。「Prism.js」はCSSでは白色が多かったのですが、こちらはすべてに色がついています。
/**
* シンタックスハイライトライブラリ「Shiki」を遅延読み込みし、
* ページ内のコードブロックをハイライトするスクリプト。
*/
// 初期化フラグ(2回目以降のスクロール実行を防止するため)
let isHighlighterLoaded = !1;
// スクロールイベントを監視して、必要になったタイミングでShikiを読み込む
window.addEventListener("scroll", async function () {
// まだ読み込まれておらず、かつスクロール量が50pxを超えた場合に実行
if (!isHighlighterLoaded && window.scrollY > 50) {
isHighlighterLoaded = !0; // フラグを立てて重複実行を防止
try {
// ShikiライブラリをESモジュールとして動的にインポート
const shiki = await import("https://esm.sh/shiki");
// ライブラリのバージョンによって異なる関数名を考慮
const getHighlighter = shiki.createHighlighter || shiki.getHighlighter;
// ハイライターを初期化(テーマと使用する言語を指定)
const highlighter = await getHighlighter({
themes: ["slack-dark"],
langs: ["js", "css", "html", "php", "json", "bash"],
});
// ページ内のすべての <pre><code> 要素を対象に処理を行う
document.querySelectorAll("pre code").forEach((codeElement) => {
// classリストから、サポートしている言語名を探す
const detectedLang = Array.from(codeElement.classList).find((className) =>
["js", "css", "html", "php", "json", "bash"].includes(className),
);
// 言語クラスが見つからない場合は処理をスキップ
if (!detectedLang) return;
// コードの中身(テキスト)を取得
const rawCode = codeElement.textContent;
// 指定した言語とテーマでHTMLを生成
const highlightedHtml = highlighter.codeToHtml(rawCode, {
lang: detectedLang,
theme: "slack-dark",
});
// 親要素(通常は <pre>)を取得
const parentElement = codeElement.parentElement;
// 一時的なdiv要素を作成して、生成されたHTMLをパースする
const tempDiv = document.createElement("div");
tempDiv.innerHTML = highlightedHtml;
// Shikiが生成した新しい <pre> 要素を取得
const newPreElement = tempDiv.firstElementChild;
// 言語名をクラスとして追加(スタイル調整用など)
newPreElement.classList.add(detectedLang);
// 元の <pre> 要素を、新しく生成されたハイライト済みの要素に置き換える
parentElement.replaceWith(newPreElement);
});
} catch (error) {
// 読み込みエラー時の処理(必要に応じてログ出力など)
console.error("Shiki initialization failed:", error);
}
}
});
WordPressでの使い方
WordPressで使うにはコードブロックにコードをペーストし「HTMLとして編集」を選びます。すると以下のようにHTMLが表示されるので
<pre class="wp-block-code"><code>
Codeタグに「class=html」の形で表示言語を指定します。この時にclass="wp-block-code"は「Shiki」が読み込まれると消えるので、残しても消してもどちらでも構いません。言語指定のクラスを加えると「このブロックには、想定されていないか無効なコンテツが含まれています。」と表示されるので右の詳細設定から「HTMLに変換」を選択します。
<pre><code class="html">
Codeタグにクラス名として使用言語を設定しても「Shiki」が読み込まれると消えるので、疑似要素を使い言語名を表示するためにPreタグに言語名をクラス名として付与しています。シンタックスハイライトが有効な状態では以下のようになります。
<pre class="shiki slack-dark html"><code>
Shikiを使った各言語の見た目
JavaScriptとHTML以外の言語の見た目は以下のように表示されます。
CSS
:where(:root[dm] dialog *),
[dn]:where(:root[dm] *) {
outline: none
}
PHP
// 画像
$imageWidth = $item['images']['primary']['large']['width'];
$imageHeight = $item['images']['primary']['large']['height'];
$imageUrl = $item['images']['primary']['large']['url'];
if ($imageWidth >= $imageHeight) {
$displayWidthMobile = 250;
$displayHeightMobile = round(($imageHeight / $imageWidth) * 250);
$displayWidth = 200;
$displayHeight = round(($imageHeight / $imageWidth) * 200);
} else {
$displayHeightMobile = 250;
$displayWidthMobile = round(($imageWidth / $imageHeight) * 250);
$displayHeight = 200;
$displayWidth = round(($imageWidth / $imageHeight) * 200);
}
JSON
{
"url": "\/water-permeability-of-filter-media\/",
"page_views": "25"
},
{
"url": "\/amplifier-to-the-speakers\/",
"page_views": "23"
},
Bash
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://us-central1-apt.pkg.dev/doc/repo-signing-key.gpg | \
sudo gpg --dearmor --yes -o /etc/apt/keyrings/antigravity-repo-key.gpg
echo "deb [signed-by=/etc/apt/keyrings/antigravity-repo-key.gpg] https://us-central1-apt.pkg.dev/projects/antigravity-auto-updater-dev/ antigravity-debian main" | \
sudo tee /etc/apt/sources.list.d/antigravity.list > /dev/null
最後に
処理が重いのでコードはファーストビューに表示させないように気をつければ、今までと同じように使えそうです。生成AIが発達しコードを紹介する機会は減ると思いますが、現状では「Shiki」が最も正確できれいなシンタックスハイライターだと思うので使えるようになって良かったです。
最後までお読みいただき、誠にありがとうございます。