#author("2026-02-13T00:15:57+09:00","default:kanateko","kanateko")
#author("2026-02-13T22:48:50+09:00","default:kanateko","kanateko")
&tag(プラグイン,自作,追加);

#ref(shiki.webp,center,wrap,500x)

#contentsx

*Shikiによる構文ハイライトプラグイン [#summary]

#infobox(plugin){{
name = shiki
ver = 1.0.1
pukiwiki = 1.5.4
update = 2026-02-13
}}

''Shiki'' ([[https://shiki.style]]) を使ってコードブロックを読みやすく装飾するプラグイン。

Shiki は VSCode でも使われている TextMate の文法をベースにしており、高いカスタマイズ性や豊富な対応言語・テーマが魅力。

これまでは他の方が作成してくれていた Prism を用いたプラグイン ([[https://storagebox.xxxxxxxx.jp]]) を使用していたのだが、細かいところを自分で調整したくなり、せっかくならまだ使われていないライブラリを使って新しく作ろうと思い、 Shiki を選ぶに至った。

*プラグインの特徴 [#features]
- Shiki による高品質なシンタックスハイライト
- 言語・テーマの指定に対応
- 行番号表示 (開始行指定可)
- コードブロックタイトル表示
- 追加 CSS クラス指定可能
- 単一ファイル構成で導入が簡単

*動作環境 [#requirements]
- PukiWiki 1.5.4 以降
- PHP 8.x 推奨
- PHP 8.x
- JavaScript 有効環境

#clear

*ダウンロード [#download]
最新: [[https://github.com/kanateko/pukiwiki-shiki]]

|~日付|~バージョン|~備考|h
|CENTER:120|CENTER:60|590|c
|2026-02-13|1.0.1|#ul(HTML構造を修正)|
|2026-02-12|1.0.0|#ul(初版作成)|

*セットアップ [#setup]
+ GitHub の Release から最新のファイルをダウンロードする。
+ ダウンロードした「`shiki.inc.php`」を `plugin/` ディレクトリに配置する。
+ `pukiwiki.ini.php` を編集してマルチラインプラグインを許可する。

#shiki(php,diff,title=pukiwiki.ini.php){{
- define('PKWKEXP_DISABLE_MULTILINE_PLUGIN_HACK', 1); // 1 = Disabled
+ define('PKWKEXP_DISABLE_MULTILINE_PLUGIN_HACK', 0); // 1 = Disabled
}}

*プラグイン設定 [#setting]
#style(word-break:break-all;,class=table_compact){{{
#tableif(^(int|\d+)$=COLOR(#62bf4b),^string|'$|DIR \.=COLOR(#cb643d),^(bool|true|false)$=COLOR(#463dcb),^(array|\[.+\])$=COLOR(#ec91ed)){{
|>|>|>|BGCOLOR(--color-key):COLOR(white):~Shikiクラス|
|DEFAULT_LANG|string|'text'|デフォルトの言語指定|
|DEFAULT_THEME|string|'github-dark'|デフォルトのテーマ|
|DEFAULT_LINENUMBERS|string|''|デフォルトの開始行番号|
|DEFAULT_TITLE|string|''|デフォルトのタイトル|
|DEFAULT_CLASS|string|''|デフォルトのクラス名|
}}
}}}

*使用方法 [#usage]

 #shiki([オプション]){{
 表示したいコード
 }}

** 引数一覧 [#s3434bb9]

|~引数名|~説明|~例|h
|150|250|150|c
|`lang`|言語指定 (Shiki 対応言語)|`lang=js`|
|`theme`|テーマ名|`theme=github-dark`|
|`title`|コードブロック上部タイトル|`title=sample.js`|
|`linenumbers`|行番号表示 (開始行指定可)|`linenumbers`&br;`linenumbers=12`|
|`start`|linenumbers の別名|`start=10`|
|`class`|追加 CSS クラス|`class=my-code my-code-2`|
|`diff`|差分ハイライト (+ / - 行)|`diff`|

※上記に当てはまらない引数は''言語指定''として扱う。

*使用例 [#example]
#tab{{{{
#:表示
#shiki(php,theme=dark-plus,title=shiki.inc.php,start=60){{
    public function convert(): string
    {
        $code = htmlsc($this->code);
        $lang = ' data-lang="' . $this->lang . '"';
        $hasLinenumbers = $this->linenumbers !== '';
        $theme = ' data-theme="' . $this->theme . '"';
        $class = $this->class !== '' ? $this->class : '';
        $title = $this->title !== '' ? ' data-title="' . $this->title . '"' : '';
        $linenumbers = $hasLinenumbers ? 'data-linenumbers' : '';
        $start = $hasLinenumbers ? '--start-index:' . $this->linenumbers . ';' : '';
        $script = $this->script();

        return <<<EOD
        <div class="plugin-shiki$class" style="visibility:hidden;$start"$start$lang$theme$title$linenumbers>
            <pre class="shiki-target">$code</pre>
        </div>
        $script
        EOD;
    }
}}

#:ソース

 #shiki(php,theme=dark-plus,title=shiki.inc.php,start=60){{
     public function convert(): string
     {
         $code = htmlsc($this->code);
         $lang = ' data-lang="' . $this->lang . '"';
         $hasLinenumbers = $this->linenumbers !== '';
         $theme = ' data-theme="' . $this->theme . '"';
         $class = $this->class !== '' ? $this->class : '';
         $title = $this->title !== '' ? ' data-title="' . $this->title . '"' : '';
         $linenumbers = $hasLinenumbers ? 'data-linenumbers' : '';
         $start = $hasLinenumbers ? '--start-index:' . $this->linenumbers . ';' : '';
         $script = $this->script();
 
         return <<<EOD
         <div class="plugin-shiki$class" style="visibility:hidden;$start"$lang$theme$title$linenumbers>
             <pre class="shiki-target">$code</pre>
         </div>
         $script
         EOD;
     }
 }}
}}}}

* 技術的メモ [#tech]

- Shiki は CDN (https://esm.run/shiki@3) から ESM として読み込み
- `import { createHighlighter } from 'shiki'` を利用し、ブラウザ上でハイライトを実行
- PHP 側では以下のみを担当
-- コード内容のエスケープ
-- 引数(言語・テーマ・行番号など)の解析
-- 必要な HTML 構造と data 属性の出力
- 実際のシンタックスハイライト処理はすべてクライアント側 JavaScript で実行
- ページ内に複数 `#shiki` があっても JS/CSS は1回のみロード
- 初期表示時は `visibility:hidden` → ハイライト完了後に表示

** 処理フロー概要 [#flow]

+ PukiWiki が `#shiki(...)` を解釈し、`shiki.inc.php` が実行される
+ PHP 側で以下を生成
-- `<pre><code>` を含むプレースホルダー HTML
-- 言語・テーマ・行番号などを `data-*` 属性として付与
+ JavaScript で `<head>` にプラグイン用の `<style>` 要素を挿入
+ ページ読み込み後、 `createHighlighter` で `highlighterInstance` を一度のみ作成
-- この時点では初期テーマのみをロードする
-- この際言語はロードせず、各コードブロックの処理時に必要な言語やテーマをロードする
#shiki(javascript){{
const highlighterInstance = await createHighlighter({
	themes: ['github-dark'],
	langs: [],
});
}}
+ 各コードブロックごとに `highlighterInstance.codeToHtml()` を実行し、 ハイライト済み HTML に差し替え
-- `data` 属性から言語とテーマ、タイトルや行番号指定などを取得
--- 言語やテーマがまだロードされていない場合は、ここでロードする
-- Transformer を使ってコードブロックの上にタイトル、言語ラベル、コピーボタンなどのヘッダー要素を追加
#shiki(javascript){{
const html = await highlighterInstance.codeToHtml(text, {
	lang: lang,
	theme: theme,
	transformers: [
		insertHeaderTransformer(lang, title)
	],
});
}}
+ 処理完了後、各コードブロックを表示する

* 備考 [#notes]
- Prism から変えたらページの表示速度が下がるかと思ったが、むしろ早くなった。
- 今回からリポジトリをプラグインごとに分けるようにした。
- CSS や JS を PHP にまとめて単一ファイルで配布できるようにしつつ、開発中はそれぞれ別のファイルに分けて編集したかったので、 Docker で PHP 8 + PukiWiki 1.5.4 + Node.js という開発環境を構築し、最終的なプラグインファイルをビルドするという形を取った。
-- ビルド用のスクリプトはローカル LLM (Qwen3 Coder 30B) を使って生成してみた。流石にそのままじゃダメだったので手直ししたが、それでも作業時間は短縮してくれたように思う。まぁ最初から Claude やら ChatGPT 頼ればもっと早いじゃんねっていうのはそうなんだけれども。


* コメント [#comment]
#mcomment