ということで前回記事のPrismでシンタックスハイライトブロックの進化版、Shikiでシンタックスハイライトブロックにゃー
ところでShikiってあまり聞いたことがないのですがなんでしょうか?
そうなんですね
らしいばかりで怪しいのですけど
VS Codeも同じ仕組みで動いているみたいだしNode.jsのWebサイトでも採用されているみたいなので品質や将来性は問題ないだろう
もう作ってしまったようですし
まあいいでしょう
コードのポイント解説
<div useBlockProps
class="pt-shiki"
data-lang="{{language}}"
data-theme="{{theme}}"
data-line-numbers="{{line_numbers}}"
aria-label="Source code ({{language}})"
role="region"
>
<!--
NOTE:
- JS 無効時でも表示される安全なプレーン構造
- Shiki はこの <pre> を丸ごと置き換える
-->
<pre><code class="language-{{language}}">{{code}}</code></pre>
</div>
<style>
/* ======================================================
Shiki Code Block — Complete Stable CSS
LazyBlocks + Gutenberg + Frontend 対応
====================================================== */
/* ---------- ルート変数 ---------- */
.pt-shiki {
/* ========= フォント管理(ここだけ変更すれば全体反映)========= */
--pt-shiki-font-size: 16px;
/* 行高はここだけを変更すれば全体が同期 */
--pt-shiki-line-height: calc(var(--pt-shiki-font-size) * 1.6);
/* フォールバック配色(JS失敗時) */
--pt-shiki-bg: #0d1117;
--pt-shiki-fg: #c9d1d9;
/* =========================================================
ブロック間余白(重要)
========================================================= */
margin-block: var(--wp--style--block-gap, 1.5em);
}
/* ======================================================
初期状態(JS未実行・Shiki未適用)
- 可読性確保
- CLS抑制
====================================================== */
.pt-shiki pre {
/*margin: 0;*/
padding: 14px;
border-radius: 6px;
overflow: auto;
font-size: var(--pt-shiki-font-size);
line-height: var(--pt-shiki-line-height);
font-family:
ui-monospace,
SFMono-Regular,
Menlo,
Consolas,
"Liberation Mono",
monospace;
white-space: pre;
background: var(--pt-shiki-bg);
color: var(--pt-shiki-fg);
content-visibility: auto;
contain-intrinsic-size: 1px 240px;
}
/* Gutenberg / theme の code リセット打ち消し */
.pt-shiki pre code {
font-family: inherit;
font-size: inherit;
line-height: inherit;
display: block;
margin: 0;
padding: 0;
}
/* ======================================================
Shiki適用後
====================================================== */
.pt-shiki pre.shiki {
/*margin: 0;*/
line-height: normal; /* ← 親のline-heightを無効化 */
content-visibility: visible;
}
/* ❗重要:行ボックス二重化防止 */
.pt-shiki pre.shiki code {
display: block !important;
line-height: 0 !important;
}
/* ======================================================
各行(Shiki生成)
====================================================== */
.pt-shiki pre.shiki .line {
display: block;
position: relative;
white-space: pre;
/* 行番号用余白 */
padding-left: 3.5em;
/* 行高をここだけに集中 */
line-height: var(--pt-shiki-line-height) !important;
min-height: var(--pt-shiki-line-height);
margin: 0;
}
/* 空行でも高さを持たせる */
.pt-shiki pre.shiki .line:empty::before {
content: "\200B";
}
/* ======================================================
行番号
====================================================== */
.pt-shiki .line-number {
position: absolute;
left: 0;
width: 3em;
text-align: right;
opacity: .45;
user-select: none;
pointer-events: none;
font-variant-numeric: tabular-nums;
line-height: var(--pt-shiki-line-height);
}
/* ======================================================
Gutenberg Editor 専用(LazyBlocks previewのみ)
====================================================== */
/*
- エディタ専用
- フロントには一切影響しない
*/
.editor-styles-wrapper
.lzb-preview-server
.pt-shiki
pre {
height: 10em;
overflow-y: auto;
font-size: 13px;
}
/* Gutenberg の余計なマージンを殺す */
.wp-block-lazyblock-shiki-code pre {
/*margin-top: 0 !important;*/
/*margin-bottom: 0 !important;*/
}
</style>
<script type="module">
/*
NOTE:
- Frontend のみで Shiki を初期化
- Editor / 再初期化は行わない(安全優先)
*/
if (
!window.__LB_SHIKI_INIT__ &&
!document.body.classList.contains('block-editor-page')
) {
window.__LB_SHIKI_INIT__ = true;
// NOTE: CDN バージョンを単一管理
const SHIKI_VER = '3.21.0';
async function initShiki() {
const blocks = Array.from(document.querySelectorAll('.pt-shiki'));
if (!blocks.length) return;
// NOTE: ESM import(失敗時は catch される)
const shiki = await import(
`https://cdn.jsdelivr.net/npm/shiki@${SHIKI_VER}/+esm`
);
// 使用言語・テーマを事前に収集
const langs = new Set();
const themes = new Set();
blocks.forEach(b => {
langs.add(b.dataset.lang || 'plaintext');
themes.add(b.dataset.theme || 'nord');
});
const highlighter = await shiki.createHighlighter({
langs: [...langs],
themes: [...themes],
});
blocks.forEach(block => {
if (block.dataset.shikiDone) return;
block.dataset.shikiDone = '1';
const pre = block.querySelector('pre');
const codeEl = block.querySelector('code');
if (!pre || !codeEl) return;
const code = codeEl.textContent;
const lang = block.dataset.lang || 'plaintext';
const theme = block.dataset.theme || 'nord';
const showLines =
block.dataset.lineNumbers === '1' ||
block.dataset.lineNumbers === 'true';
let html;
try {
html = highlighter.codeToHtml(code, {
lang,
theme,
transformers: showLines ? [lineNumberTransformer()] : []
});
} catch {
// NOTE: 言語未対応時はプレーン表示にフォールバック
return;
}
pre.outerHTML = html;
});
}
function lineNumberTransformer() {
let line = 0;
return {
line(node) {
line++;
node.children.unshift({
type: 'element',
tagName: 'span',
properties: {
className: ['line-number'],
'aria-hidden': 'true'
},
children: [{ type: 'text', value: String(line) }]
});
}
};
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initShiki);
} else {
initShiki();
}
}
</script>
今回はチャッピーさんことChatGPTでヴァイブコーディングにゃー
ヴァイブコーディングという名の開発丸投げだろう
基本方針はLazyBlocks内完結、二重ローディング防止、Shikiが動かない場合のフォールバック処理、その他SEO対策にゃー
自分で開発しないからって要求を詰め込むな
ポイントのひとつめ
二重ローディング防止はwp_enqueue_scriptおよびwp_enqueue_style関数を使ってやるのが定番らしいのだけどLazyBlocks内で使えないらしい
PHPのグローバル変数やdefineとかで判定しようとしたけど動作状況によってうまくいかなかったにゃー
JSで判定するのが今のところ一番安定しているにゃー
Pro版ならそこは苦労はしなくて済むのですけどね
CSSはそんなにサイズもないしこのブロックを大量に使うようなこともないだろうから重複OKにしているにゃー
テーマも含んでいるのでShiki本体のサイズのほうがおそらく大きいでしょうね
あとから気づいたけどFine-grained Bundleというのがあるらしい
これをやると必要な分だけローディングされるからJS本体のサイズを減らせそう
調べておけよ!
最初から!
Fullだと6.4 MBか
まあそれほどこのブロックを使わなさそうなので今後のアップデート目標ということにしておくか
めちゃくちゃでかいじゃあないか!
一応ブラウザで確認したけど6.4 MBダウンロードはしていなかったにゃー
元々サーバサイトレンダリング向けに開発されているぽいのでクライアント側で動かすのはできないことはないけどあまりよくないのかも
だから<pre><code>のouterHTMLに注入する動作になっているにゃー
無理やり感はありますが公式もCDN経由の紹介をしているので想定範囲ないですかね
minifyされているのでよくわからんが多分必要なものだけダウンロードするようになっているぽい
まあいいでしょう
まずは動くのが重要ですからね
ポイントのふたつめ
JS実行できなかった場合、とりあえずハイライトしないけどそれらしく出力するようにした点にゃー
実行前が<pre>、実行後は<pre class=”shiki”>になる点がポイントにゃー
CLS対策も含んでいるのですね
うむ
やたら面倒だったにゃー
テーマCSSとかの競合とかもあるのでめんどい
font-familyを統一しておくといい感じになったにゃー
なるほど
aria-labelとrole=”region”とかやるとアクセシビリティがあがるらしい
この辺りはよくわかってないにゃー
行番号はカスタマイズしてますよね
うむ
これはチャッピーさんにお願いしたら一発で出来上がったにゃー
流石にゃー
この辺りはShikiの高度なカスタマイズの恩恵もありますね
実はHighlight APIとShikiを組み合わせたshiki-highlight-apiというヤツも見つけていたのだけど
こういうやつを使って前回失敗したので今回は見送ったのにゃー
もしかしたらこっちは言語によらずちゃんと動くのかも
書かれていない制限に引っかかると厄介ですからね
まあ妥当な判断か
そんなところかにゃー
Shikiを使ってテーマも対応言語も増えて満足にゃー
ほとんどなにもしてないけどな
何を言っている!
ShikiみたいなヤツはWordPressの思想と合わない、だとか言ってくるチャッピーさんをなだめたり
二重ローディングは最初PHPバージョンだったけど全然動かなくてJSバージョンもなかなか安定しなくて
それでも自信満々なコードを提示してくるチャッピーさんにダメ出ししたり
なかなか大変だったのにゃー
なんというか
チャッピーさん、お疲れ様です。
しかし成果は良かったので過去のコードとかチャッピーさんを使って改善してみるかな
まだ酷使する気か!?
それでは今回はこの辺で
Aloha
何の脈絡もなくハワイ語!?
