シンタックスハイライト
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 にルーティングされる |
Tip
zfb には、色を焼き込む代わりに各トークンへ再テーマ可能な CSS デザイントークンを付与する クラスモード もあります。テーマを切り替えられるサイトにはこちらを推奨します。詳しくは下記の「クラスモード(推奨)」の節を参照してください。
認識される言語
フェンス付きコードブロックの言語タグは、zfb のエイリアス表を経由して、syntect に同梱された Sublime Text 互換の文法か、zfb 自身が追加している 1 つの文法(TOML — syntect には同梱されていません)のいずれかに解決されます。マッピングされているタグは次のとおりです。
| タグ | 文法 |
|---|---|
ts、typescript、tsx | JavaScript(下記の注意を参照) |
js、javascript、jsx | JavaScript |
rs、rust | Rust |
py、python | Python |
sh、bash、zsh | Bash(Bourne Again Shell) |
md、markdown、mdx | Markdown |
yaml、yml | YAML |
json | JSON |
c | C |
cpp、c++ | C++ |
go | Go |
toml | TOML |
html | HTML |
css | CSS |
Note
ts / tsx は JavaScript の文法でハイライトされます。 syntect に同梱された syntax set には、読み込んで使える TypeScript / TSX の文法がありません。zfb が検討した再配布可能なソースはいずれも、syntect が対応していないファイル形式で提供されているか、syntect が実装していない extends: の継承機構に依存しています(調査の全容は crates/ にあります)。そのため、どちらのタグもハイライトなしのフォールバックではなく、JavaScript のハイライトに流れます。
この表にないタグは、上で説明したテーマ適用のハイライトなし経路にフォールバックします。コードは保持・エスケープされますが、トークンの着色は行われません。
テーマのカスタマイズ(組み込みテーマ)
配色を変更するもっとも単純な方法は、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 をオプトインしておいてください。
クラスモード(推奨)
ここまではすべて インラインモード です。zfb は各トークンの色を style="color:#…"(またはデュアルの --shiki-* カスタムプロパティ)として HTML に直接焼き込みます。一方 クラスモード は、各トークンに意味的な ロールクラス(hi-kw、hi-str、hi-com、…)を付与し、それらのクラスを CSS カスタムプロパティ経由で着色するスタイルシートを同梱します。色は再テーマ可能なデザイントークンとなり、再ビルドなしで自分の CSS から上書きできます。
zfb.config.ts で有効化します。
// zfb.config.ts
export default {
codeHighlight: {
mode: "class",
},
};Note
このリリースでは inline が引き続きデフォルトです。 クラスモードはオプトインで、デフォルトは切り替わっていません。class をデフォルトにするかどうかは将来の判断です。今すぐ使うには mode: "class" を明示的に設定してください。
クラスモードは すべてのテーマ関連オプション(theme、themeLight、themeDark、themesDir)と排他 です。テーマはインラインの色にしか影響しないため、mode: "class" と同時に指定すると、暗黙的に無視されるのではなくビルドエラーになります。クラスモードでの色の選択は、完全に CSS 側で行います。
18 個のロールクラス
ハイライトされた各トークンは、固定された 18 個の意味的ロールのちょうど 1 つに分類されます。この分類体系は凍結されており、ロールが追加・削除されることはありません。各ロールは、デフォルトのクラス {classPrefix}{suffix}(デフォルトの classPrefix は hi-)と、対応する CSS カスタムプロパティ --zfb-hi-{suffix} にマッピングされます。
| ロール(設定キー) | デフォルトクラス | カスタムプロパティ |
|---|---|---|
escape | hi-esc | --zfb-hi-esc |
operator | hi-op | --zfb-hi-op |
comment | hi-com | --zfb-hi-com |
string | hi-str | --zfb-hi-str |
number | hi-num | --zfb-hi-num |
constant | hi-const | --zfb-hi-const |
keyword | hi-kw | --zfb-hi-kw |
function | hi-fn | --zfb-hi-fn |
type | hi-ty | --zfb-hi-ty |
namespace | hi-ns | --zfb-hi-ns |
property | hi-prop | --zfb-hi-prop |
variable | hi-var | --zfb-hi-var |
tag | hi-tag | --zfb-hi-tag |
attribute | hi-attr | --zfb-hi-attr |
punctuation | hi-punct | --zfb-hi-punct |
inserted | hi-ins | --zfb-hi-ins |
deleted | hi-del | --zfb-hi-del |
heading | hi-hd | --zfb-hi-hd |
設定キーは ロールのフルネーム(keyword)であるのに対し、出力されるクラスは短い サフィックス(kw)を使います。18 個のロールプロパティに加えて、スタイルシートは --zfb-hi-fg / --zfb-hi-bg(<pre> 要素である .hi-root の基本の前景色と背景色)と、--zfb-hi-ins-bg / --zfb-hi-del-bg(差分行の淡い背景色)も定義します。
出力される markup
<pre> 要素には {classPrefix}root(デフォルトは hi-root)が付与され、各トークンは自身のロールクラスを持つ <span> になります。インラインの色は付きません。
<pre class="hi-root"><code><span class="line"><span class="hi-kw">fn</span> <span class="hi-fn">main</span>…</span></code></pre><span class="line"> のラッパーはインラインモードやデュアルモードと同一で、異なるのはトークンごとの色の指定方法だけです。
デフォルトのスタイルシート
mode: "class" を指定すると、zfb は組み込みのトークンスタイルシート(zfb-hi.css)を、まとめられた styles.css に注入します。このスタイルシートは --zfb-hi-* カスタムプロパティと、それらを参照する .hi-* ルールを定義し、@layer zfb-hi カスケードレイヤーで包みます。そのため、レイヤーに属さないあなた自身のルールが常に優先されます。デフォルトのパレットは syntect の base16-ocean.light / base16-ocean.dark テーマから抽出されており、これはインラインモードのデフォルトテーマ base16-ocean.dark が属するのと同じ系統です。
オプトアウトして完全に自前のスタイルシートを使うには、defaultStylesheet: false を設定します。
// zfb.config.ts
export default {
codeHighlight: {
mode: "class",
defaultStylesheet: false,
},
};再ビルドなしの再テーマ
色は CSS カスタムプロパティ経由で解決されるため、自分の CSS から --zfb-hi-* を上書きするだけでハイライトを再テーマできます。zfb build は不要です。tight-token / 3 層(three-tier)パターンに従いましょう。生のパレットトークンが意味的なロールの上書きを供給し、それを同梱の .hi-* ルールが参照します。
/* Tier 1 — your raw palette tokens */
:root {
--palette-purple: #8250df;
--palette-green: #1a7f37;
--palette-slate: #6e7781;
}
/* Tier 2 — map the palette onto the semantic --zfb-hi-* roles */
:root {
--zfb-hi-kw: var(--palette-purple);
--zfb-hi-str: var(--palette-green);
--zfb-hi-com: var(--palette-slate);
}
/* Tier 3 is shipped by zfb: .hi-kw { color: var(--zfb-hi-kw) } … */あなたの上書きは @layer zfb-hi の外側に置かれるため、!important なしでレイヤー内のデフォルトに勝ちます。
ダークモード
同梱スタイルシートの値は light-dark() を使い、さらに @media (prefers-color-scheme: dark) フォールバックも備えています。そのため、color-scheme をオプトインしていないホストでも、設定なし でダークモードが機能します。
明示的なテーマトグル(prefers-color-scheme ではなく、<html> の class や data- 属性)を持つホストは、トグルのセレクタ配下でカスタムプロパティを上書きします。
:root[data-theme="dark"] {
--zfb-hi-fg: #c0c5ce;
--zfb-hi-bg: #2b303b;
--zfb-hi-kw: #b48ead;
--zfb-hi-str: #a3be8c;
--zfb-hi-com: #65737e;
/* …override the roles your theme changes… */
}Tip
デフォルト値は light-dark() で書かれているため、明示的なトグルを持つホストは、トグルされたルート要素で color-scheme を切り替える(:root[data-theme="dark"] { color-scheme: dark; })だけでも構いません。こうすると同梱の light-dark() の値がダーク側に解決され、デフォルトから外したいロールだけを個別に上書きできます。
ロールを Tailwind ユーティリティにマッピングする
デフォルトの hi-* クラスの代わりに、roleClasses で任意のロールを自分のユーティリティクラス(もっとも有用なのは Tailwind)にマッピングできます。
// zfb.config.ts
export default {
codeHighlight: {
mode: "class",
roleClasses: {
keyword: "text-violet-600 dark:text-violet-400",
string: "text-emerald-600 dark:text-emerald-400",
},
},
};キーは上記 18 個のロール名のいずれかである必要があります。値はスペース区切りで複数のクラスを持てますが、素の line トークンを含めることはできません(code-enrichment の行ラッパークラスと衝突します)。マッピングしなかったロールは、デフォルトの {classPrefix}{suffix} クラスのままです。
マッピングされたユーティリティは 自動でセーフリスト化 されます。ハイライトされた markup は Rust パイプラインが生成し、Tailwind がスキャンしないレンダリング済みの dist/*.html にのみ出力されます。そのため、何もしなければユーティリティは tree-shaking で除去されてしまいます(ビルドは成功するのにトークンは無スタイル)。zfb は各 roleClasses の値を Tailwind の @source inline("…") エントリとして出力するため、これらのクラスは常に生成されます。
Warning
オーサリング CSS 経路(tailwind: { enabled: false })。 Tailwind を無効にした状態で roleClasses を設定することは許可されますが、ビルド警告が出ます。この経路では Tailwind のセーフリストを生成できないため、マッピング先のユーティリティは、自分で手書きした CSS の中に既に存在している必要があります。
カスタムクラスプレフィックス
classPrefix(デフォルトは hi-)は、<pre> のルートクラスとすべてのロールクラスの両方でプレフィックスを変更します。classPrefix: "syn-" にすると syn-root、syn-kw、… になります。/ に一致する必要があります。
カスタムプレフィックスは 同梱スタイルシートでも尊重されます。zfb はビルド時にデフォルトの .hi-* セレクタをあなたのプレフィックスへ書き換えます。--zfb-hi-* カスタムプロパティは独立して名前空間化されており、classPrefix に関わらず --zfb-hi-* のままです。そのため、再テーマ用の上書きは変更不要です。
インライン / デュアルモードを使うべき場合
クラスモードは外部スタイルシートに依存するため、HTML が自己完結している必要がある場合は インライン(または デュアル)モードを選んでください。RSS / Atom フィードの本文、HTML メール、サイトの CSS を伴わずに配信されるあらゆる埋め込みなどです。そうした場面では、焼き込まれた style="color:…" はむしろ利点であり、スタイルシートがなくても色が保たれます。
スコープに関する注意
md言語フェンス内の markup ロール。 見出しと差分の追加 / 削除行を除き、Markdown の markup 構文は設計上、控えめにしかスタイル付けされません。構文ごとにロールを作るのではなく、分類体系が意図的にそれらをまとめているためです。「フロントエンドライブラリ不要」という主張は、静的 HTML のレンダリング経路についてのものです。 クラスモードは、静的にレンダリングされた HTML に対してハイライト用の JavaScript を一切出力しません。MDX の React コンポーネントレンダリング経路は、従来どおり実行時に
pre/codeをあなたの_componentsに委譲します。クラスモードはこの経路を変更しません。
補足パターン 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 など)はすべて、組み込みのパイプラインが追加の手順なしで処理します。
ブラウザでコードをハイライトする
このページのすべては、zfb の syntect パイプラインを通じて ビルド時 に実行されます。 代わりに 実行時にブラウザで コードをハイライトする必要がある場合 — ライブの Markdown/MDX エディタ、貼り付けてプレビューするボックス、あるいはページ読み込み後まで ソースが分からないあらゆる場面 — では、@takazudo/zfb-md-wasm の highlightCode() を 使ってください。手順については 直接的なセマンティックコードハイライト を参照してください。
Tip
highlightCode しか呼ばないなら、パッケージのルートではなく@takazudo/ サブパスからインポートしてください — 完全な MDX/Markdown パイプラインなしでビルドされた、スリムでハイライト専用のアーティファクトで、 デフォルトのエントリポイントのおよそ半分のダウンロードサイズです。
完全な API リファレンスについては zfb-md-wasm を参照してください。
関連項目
Markdown パイプラインの拡張 —
SyntectPluginが hast フェーズのパイプラインにどう組み込まれるか、それをどう差し替え・拡張するか。カスタムディレクティブ —
mermaidブロックは syntect ではなくこの経路を使います。crates/— プラグインのソース。zfb- content/ src/ plugins/ syntect_ plugin. rs