テーマを作成してみよう
MkDocsでは、他の人が作ったテーマを使ったり、部分的にカスタマイズして使うことができますが、Jinja というテンプレートエンジンを使って、自分の作りたいように一から作ることもできます。ここでは、テーマを自作する方法を見ていきます。
自作テーマを使う設定
テーマに最低限必要なものは、main.html ファイルだけです。docs と同じ階層に別のフォルダを作って、その下にテーマ用のファイルを保存していきます。例えば、次のようにファイルを作ったとしましょう。
mkdocs.yml
docs/
    index.md
    about.md
custom_theme/
    main.html
    ...
custom_theme というフォルダを作ってその下にファイルを作っています。このフォルダ名は自分の好きなものにできます。このテーマを使うには、mkdocs.yml ファイルに次のように設定します。
theme:
    name: null
    custom_dir: 'custom_theme/'
テーマを null にし、custom_dir に自作テーマを置いている場所を指定します。これで、自作テーマの内容がサイトに反映されます。
custom_dir は、既存テーマの一部を上書きするためにも使われます。例えば、name: mkdocs と設定すると、mkdocs のテーマをベースとし、custom_dir 内で追加したファイルだけが上書きされてサイトに反映されます。
基本的なテーマ
以下では、具体的にテーマの作り方を見ていきます。
ただ、一から全部作るのは大変なので、テーマのひな型をダウンロードしておくといいでしょう。mkdocs-basic-theme からダウンロードできます。また、デフォルトで入っている2つのテーマ のファイルも参考になります。
main.html には、通常の HTML ファイルのように書いていきます。HTML ファイルのひな型に、最低限、次のようにしていけば、ページの表示ができます。
<title>{% if page.title %}{{ page.title }} - {% endif %}{{ config.site_name }}</title>
head タグ内には、上のようにするとタイトルが表示されます。波かっこでくくられた部分は、Jinja によるものです。MkDocs のテーマは、Jinja というテンプレートエンジンを使っています。。
page.title はページタイトルで、if や endif は Jinja の文法です。波かっこ2つで表します。上のように書くと、ページタイトルがあれば、それが表示されます。config.site_name は、mkdocs.yml ファイルで設定した site_name のことです。
<body>
{{ page.content }}
</body>
body タグは、最低限これだけでもかまいません。page.content は、ページの内容です。Markdown で書いたものが HTML に変換されて表示されます。
このように、MkDocs で用意されたいくつかの変数と、Jinja の文法を組み合わせてテーマを作っていきます。
テーマに関連するファイル
テーマ内のファイルは、ファイルの種類によって、build 時の処理方法が異なります。
画像や CSS ファイルなどは、そのままコピーされます。
テーマ内の HTML ファイルは、テンプレートファイルとして扱われます。MkDocs は、このテンプレートファイルと docs フォルダの内容とを組み合わせて、各ページを作成します。
もし、HTML ファイルをそのままコピーしたい場合は、static_templates を使います。例えば、mkdocs テーマでは、次のように設定されています。
static_templates:
    - 404.html
このように設定すると、404.html ファイルは、変換されることなく、そのまま site フォルダにコピーされます。
テーマに関連するメタファイルは、コピーされません。テーマの設定が入った mkdocs_theme.yml ファイルや、Python ファイルは site フォルダにはコピーされません。また、 . から始まるファイルやフォルダも無視されます。
テンプレート変数
テンプレートを作る際には、いろいろな変数を使います。先ほど見た、page.content や page.title などはその例です。テンプレート変数はたくさんあるのですべては紹介できないですが、いくつか見ていきます。
config
mkdocs.yml で設定した内容は、config 変数で呼び出せます。 config.site_name は先ほどの例でも出てきました。自分で新しく設定することもできますが、config.site_description や config.copyright などは、いろいろなテーマで使われるでしょう。
nav
ナビゲーションに関する設定は、nav 変数を使います。例えば、次のように書きます。
{% if nav|length>1 %}
    <ul>
    {% for nav_item in nav %}
        {% if nav_item.children %}
            <li>{{ nav_item.title }}
                <ul>
                {% for nav_item in nav_item.children %}
                    <li class="{% if nav_item.active%}current{% endif %}">
                        <a href="{{ nav_item.url|url }}">{{ nav_item.title }}</a>
                    </li>
                {% endfor %}
                </ul>
            </li>
        {% else %}
            <li class="{% if nav_item.active%}current{% endif %}">
                <a href="{{ nav_item.url|url }}">{{ nav_item.title }}</a>
            </li>
        {% endif %}
    {% endfor %}
    </ul>
{% endif %}
nav は、複数の navigation から構成されています。
nav|length の |length はフィルターといって、こう書くと要素数が取得できます。「nav の要素が1より大きければ」ということです。
{% for nav_item in nav %} は、nav にある各要素について処理する、ということです。各要素は nav_item にいれています。その次に {% if nav_item.children %} とありますが、これは、さらに下の階層があるかどうかを確認しています。
階層があればさらに下の階層を見ます。{% if nav_item.active%} は、今見ているページかどうかを判定しています。今見ているページなら、current クラスが追加されるので、さらに CSS で見た目を設定します。
{{ nav_item.url|url }} は、ページへのリンクです。|url は、これも先ほど出てきたフィルターと呼ばれるもので、こうするとパスを良い感じに調整してくれます。 {{ nav_item.title }} はページのタイトルです。
こうして、2階層下までのナビゲーションを表示することができます。
page
page オブジェクトに各ページの情報が入っており、それらすべてが pages に入っています。nav は docs フォルダに入っているものよりも、mkdocs.yml で行った設定が優先されますが、pages は本当にすべての page が入っています。また、フォルダ・ファイルのアルファベット順になっています。なので、nav に含まれているものとは中身が違っていることもあります。
page には、page.title(ページタイトル)、page.content(コンテンツ)、page.url(ページへのリンク)といった、よく使うそうな属性が用意されています。例えば、次のような書き方ができます。
<a href="{{ page.url|url }}">{{ page.title }}</a>
|url フィルターにより、相対パスに変換できます。pages に対して行えば、全記事を一覧表示することができます。
目次を作ることもできます。Markdown ファイルで、h1 タグなどを使えば、自動的に id がつきます。英語ならうまく変換できるのですが、日本語の場合は変換されず、数字だけになってしまいます。この h* タグの情報は、page.toc に入っています。これを利用して、次のような書き方ができます。
<ul>
{% for toc_item in page.toc %}
    <li><a href="{{ toc_item.url }}">{{ toc_item.title }}</a></li>
    {% for toc_item in toc_item.children %}
        <li><a href="{{ toc_item.url }}">{{ toc_item.title }}</a></li>
    {% endfor %}
{% endfor %}
</ul>
2階層までの目次を表示します。 title や url は、それぞれタイトルと URL です。 children はさらに下の階層を取得するためのものです。
page.meta を使えば、各ページのメタ情報を使うこともできます。次のように、Markdown ファイルの上の方でメタ情報の設定を行います。
---
mydata:
  - aaa
  - bbb
---
# ページタイトル
コンテンツ
YAML 記法でこのように書いたとしましょう。このとき、テンプレートに以下のように書けば、aaa, bbb がリストで表示されます。
<ul>
{% for myvalue in page.meta.mydata %}
  <li>myvalue</li>
{% endfor %}
</ul>
ナビゲーション関係では、前後の記事を表す page.previous_page や page.next_page があり、親の階層を表す page.parent があります。最初のページの前のページや、最上階層の上の階層など、該当するものが存在しない場合は、None が返ります。
テンプレートフィルター
Jinja には、変数の値を表示する際に表記を変換する等の処理ができる、フィルター と呼ばれる機能があります。MkDocs ではJinja のフィルターも使用できますが、MkDocs で用意されているフィルターもあります。
すでに出てきていますが、url フィルターがあります。これは、絶対パスはそのまま表示し、相対パスで page から呼び出された場合は、そのページからの相対パスに変換されます。それ以外は、base_url がついたものになります。
tojson フィルターは、Python を JavaScript で使える値に変換します。
おわりに
ここでは、テーマを作るための流れや構成、使用できるテンプレート変数について見てきました。テーマを作るには、HTML の知識だけでなく、CSS や JavaScript の知識も必要で、Jinja についても知る必要があります。複雑なことをするには Python に関する知識も必要になってくるかもしれません。
次のページでは、Material テーマという、オススメのテーマについて見ていきます。そのまま使うこともできますし、テーマを自作したり、カスタマイズするのにも参考になるでしょう。