シンタックスハイライト
zfb は syntect によるサーバーサイドのシンタックスハイライトを搭載しています。このページでは組み込みテーマの設定方法、カスタム .tmTheme ファイルの使い方、補足パターンを解説します。
zfb は syntect による サーバーサイドのシンタックスハイライト を搭載しています。syntect は crates/zfb-content の内部でビルド時に動作する Rust ライブラリです。パイプラインがフェンス付きコードブロックに遭遇すると、SyntectPlugin が言語タグを調べ、設定されたテーマでソースをハイライトし、<pre><code> 要素を出力に焼き込まれた <pre class="syntect-…"><code>…</code></pre> の HTML 断片に置き換えます。ハイライトのために JavaScript がブラウザへ送られることはありません。
組み込みの挙動
| 項目 | 値 |
|---|---|
| エンジン | syntect(Sublime Text 互換の文法) |
| 実行 | ビルド時(crates/zfb-content の hast ビジターフェーズ) |
| 出力 | class 属性付きのインライン HTML — ランタイム JS はゼロ |
| 未知の言語 | テーマ適用のフォールバック: <pre class="syntect-…"> でラップされ、コードは保持される |
mermaid ブロック | スキップ — 代わりに MermaidPlugin にルーティングされる |
テーマのカスタマイズ(組み込みテーマ)
配色を変更するもっとも単純な方法は、zfb.config.ts で syntect に同梱されたテーマのひとつを選ぶことです。
// zfb.config.ts
export default {
codeHighlight: {
theme: "Solarized (light)",
},
};組み込みテーマ名: "base16-ocean.dark"(デフォルト)、"base16-ocean.light"、"InspiredGitHub"、"Solarized (dark)"、"Solarized (light)"。
これらは Shiki のテーマ名では ありません。"dracula" のような名前を themesDir 経由で読み込まずに使うと、ビルド時に unknown theme エラーが発生します。
カスタム .tmTheme ファイルの使い方
syntect は Sublime Text の .tmTheme 形式と互換性があります。任意の .tmTheme ファイル(Dracula、One Dark、Catppuccin、…)をディレクトリに置き、codeHighlight.themesDir をそこに向けることで読み込めます。
// zfb.config.ts
export default {
codeHighlight: {
themesDir: "./themes", // relative to the project root
theme: "Dracula", // the `name` declared inside the .tmTheme file
},
};ディレクトリ構成:
my- project/
├── themes/
│ └── dracula. tmTheme ← drop your . tmTheme files here
├── pages/
├── content/
└── zfb. config. ts.tmTheme のファイル名は重要ではありません — theme に渡す名前は、plist 内の name キーの <string> 値と一致しなければなりません。Dracula の場合、宣言されている名前は "Dracula" です。
Dracula のダウンロード: 公式の Dracula .tmTheme は https:
エラー報告: themesDir が存在しないディレクトリを指している場合や、いずれかの .tmTheme ファイルが不正な形式の場合、zfb はビルド開始時(ページの描画前)に、ファイルパスとパースエラーを含む明確なエラーを表示します。
ライト / ダークのデュアルテーマ
上記の theme オプションは各トークンをインラインの style="color:…" で着色するため、テーマは 1 つだけ焼き込まれます。1 回のビルドでライトとダークの両方のサイトテーマに対応させるには、代わりに themeLight と themeDark を設定します。各ブロックは 2 回ハイライトされ、2 つの色が CSS カスタムプロパティとして出力されます。ブラウザは light-dark() ルールでアクティブな色を選びます。ランタイム JS は依然としてゼロです。
// zfb.config.ts
export default {
codeHighlight: {
themeLight: "InspiredGitHub",
themeDark: "base16-ocean.dark",
},
};ルール
両方を必ずセットで指定する。
themeLightだけ、またはthemeDarkだけを設定するとビルドエラーになります。themeとは排他。 デュアルのペアと同時にthemeを設定するとビルドエラーになります。シングルテーマモード(theme)かデュアルテーマモード(themeLight+themeDark)のどちらか一方を選び、両方は使えません。themesDirは両方のモードに適用される。themesDirで読み込んだカスタム.tmThemeファイルは、themeと同様に、宣言されたnameによってthemeLight/themeDarkから利用できます。これらは Shiki ではなく syntect のテーマ名です —
"base16-ocean.light"/"base16-ocean.dark"、"InspiredGitHub"、"Solarized (light)"/"Solarized (dark)"、または読み込んだ任意のカスタム.tmTheme。themesDir経由で読み込んでいない"dracula"のような名前は、やはりビルド時にエラーになります。
出力される markup
デュアルモードでは <pre> 要素に class="syntect-dual" が付与され、2 つの背景色が --shiki-light-bg / --shiki-dark-bg として style に入ります。各トークンの <span> は、インラインの color: の代わりに --shiki-light / --shiki-dark を持ちます。
<pre class="syntect-dual" style="--shiki-light-bg:#fff;--shiki-dark-bg:#2b303b">
<code><span class="line"><span style="--shiki-light:#998;--shiki-dark:#65737e">token</span>…</span></code>
</pre><span class="line"> のラッパー構造はシングルテーマモードと同一で、異なるのはトークンごとの色の指定方法だけです。
Note
変数名 --shiki-light / --shiki-dark は、利用側の CSS をなじみやすくするため、意図的に Shiki のデュアルテーマ CSS 規約に合わせています。ただし、指定するテーマ名は Shiki ではなく syntect のものです。
色の解決(利用側の CSS)
ライト / ダーク対応のサイトは、出力された変数を light-dark() を通してマッピングする 1 つの CSS ルールを追加します。light-dark() はページの color-scheme に従います。
pre[class^="syntect-"] span {
color: light-dark(var(--shiki-light), var(--shiki-dark));
background-color: light-dark(var(--shiki-light-bg), var(--shiki-dark-bg));
}light-dark() がアクティブなモードに解決されるよう、周囲のコンテキスト(:root や <pre> など)で color-scheme: light dark をオプトインしておいてください。
補足パターン 1 — クライアントサイドの追加ハイライト
インタラクティブなテーマ切り替えやユーザーごとの設定が必要な場合は、サーバーで描画された出力の上に、クライアントサイドのハイライターを クライアントアイランド として重ねます。
"use client";
import { useEffect, useRef } from "preact/hooks";
import Prism from "prismjs";
import "prismjs/components/prism-typescript";
export default function PrismRoot({ children }) {
const ref = useRef(null);
useEffect(() => { Prism.highlightAllUnder(ref.current); }, []);
return <div ref={ref}>{children}</div>;
}記事の本文を <PrismRoot> でラップすると、アイランドが事前描画済みのブロックをその場で再ハイライトします。これはメディアクエリやユーザー設定に依存するテーマに有用ですが、JavaScript が追加され、ハイドレーション後に短い再描画が発生します。
補足パターン 2 — カスタム文法のためのポストビルドスクリプト
syntect は Sublime Text 互換の文法を使います。syntect が同梱していない文法(社内 DSL やニッチな言語)が必要な場合は、zfb build の後に特定のブロックを置き換えるポストビルドの Node スクリプトを実行できます。
// post-build/highlight-custom.ts — runs after `zfb build`
import { glob } from "glob";
import { readFile, writeFile } from "node:fs/promises";
import { codeToHtml } from "shiki";
for (const file of await glob("dist/**/*.html")) {
const html = await readFile(file, "utf8");
const next = await highlightCustomBlocks(html, codeToHtml);
if (next !== html) await writeFile(file, next);
}これは syntect の同梱セットに存在しない文法の場合にのみ意味があります。標準的な言語(Rust、TypeScript、Python、Go など)はすべて、組み込みのパイプラインが追加の手順なしで処理します。
関連項目
Markdown パイプラインの拡張 —
SyntectPluginが hast フェーズのパイプラインにどう組み込まれるか、それをどう差し替え・拡張するか。カスタムディレクティブ —
mermaidブロックは syntect ではなくこの経路を使います。crates/— プラグインのソース。zfb- content/ src/ plugins/ syntect_ plugin. rs