Mkdocs と GitHub Markdown を相互運用

Mkdocs は Markdown を簡単にウェブサイトに変換できる静的サイトジェネレータです。しかし、デフォルトの設定ですと、GitHub の Markdown 仕様と異なるため、GitHub （や vscode 等の Markdown Preview）から見た時と Mkdocs で生成した時で見え方が異なります。両方で正しく動作させるために幾つかの追加設定が必要だったため備忘録的として残します。

## 背景

元々 Markdown でドキュメントをメンテしていたレポジトリで GitHub Pages も作る方針になったので、ユーザー体験を変えず、メンテ負荷も最小限に抑え管理するために Mkdocs で既存の Markdown を再利用しながら GitHub Pages を作成しました。

ちなみに今回 GitHub Page を作成したレポジトリと GitHub Pages は以下です。OSS プロジェクトに後からいい感じの GitHub Pages を追加する際設計の参考にしていただければ幸いです。

https://github.com/aws-samples/generative-ai-use-cases-jp

https://aws-samples.github.io/generative-ai-use-cases-jp/

:::note info
宣伝です。

上記のレポジトリ Generative AI Use Cases (GenU) は AWS Japan メンバー有志で開発している OSS の生成 AI アプリケーション実装です。サーバーレス構成で完全従量課金制で小さく生成 AI アプリケーションを導入でき、デフォルトで多数のユーズケースを収録しているためそのまま利用いただくこともできますし、MIT License の OSS なので好きにカスタマイズして利用いただくことが可能です。
:::

## 設定

### ヘッダへの Permalink

GitHub の Markdown ではヘッダへの Permalink が自動作成されリンクすることができます。 (例: `/page#header`)

Mkdocs ではデフォルトでは有効化されていませんが、`toc` の `permalink` オプションでヘッダの Permalink の生成を有効化できます。

しかし、生成する URL は GitHub のように日本語対応していないためリンクに乖離が発生します。この問題については slug 生成アルゴリズムを `pymdownx.slugs.slugify` に置き換えることで GitHub と同じ URL になります。

```
markdown_extensions:
  - toc:
      permalink: true
      slugify: !!python/object/apply:pymdownx.slugs.slugify
        kwds:
          case: lower  
```

関連 Issue

https://github.com/mkdocs/mkdocs/issues/762

### レポジトリのファイルへのリンク

GitHub の Markdown では他のコードファイルへのリンクを入れることがあります。絶対リンク/相対リンクのままだと Mkdocs はレポジトリのファイルに飛ばしてくれず 404 になるため、リンクをレポジトリ URL に書き換えるカスタム Hook を作成する必要がありました。

ここでは、絶対リンクのみ `absolute_path_replace_uri` に書き換える実装 Hook を作成しました。そのため運用上 docs 以外のレポジトリのファイルを参照したい場合は絶対リンクで記述するルールを設ける必要があります。

```anchors.py
import re
from mkdocs import utils as mkdocs_utils

# Override absolute path to start with edit_uri
def on_page_content(html, page, config, files):
    absolute_path_replace_uri = config.get('extra', {}).get('absolute_path_replace_uri')
    if not absolute_path_replace_uri:
        return html

    if absolute_path_replace_uri.endswith('/'):
        absolute_path_replace_uri = absolute_path_replace_uri[:-1]

    link_pattern = r'<a\s+(?:.*?\s+)?href="(.*?)"'
    links = re.findall(link_pattern, html)
    for link in links:
        url = link.strip()
        if url.startswith('/'):
            new_url = f"{absolute_path_replace_uri}{url}"
            html = html.replace(f'href="{url}"', f'href="{new_url}"')

    return html
```

```mkdocs.yml
extra:
  # Used in custom hook anchors.py
  absolute_path_replace_uri: https://github.com/aws-samples/generative-ai-use-cases-jp/blob/main/

hooks:
  - docs/mkdocs/material/overrides/hooks/anchors.py

validation:
  links:
    absolute_links: info
    anchors: warn
    unrecognized_links: warn
```


### doc フォルダ外のファイルの利用

Mkdocs ではデフォルトでは docs フォルダ外部のファイルを参照できません。そのため、トップレベルの `README.md` などを読み込めません。いくつか代替案はありますが制約のあるものが多く、いくつか試した中で`include-markdown` が最も使い勝手が良かったのプラグインです。

```mkdocs.yml
watch: [mkdocs.yml, README.md]

...

plugins:
  - include-markdown:
      rewrite_relative_urls: true
```

```docs/ABOUT.md
{%
    include-markdown "../README.md"
%}
```

`include-markdown` で指定した Markdown ファイルを読み込んでくれます。

さらに、`rewrite_relative_urls` オプションを有効化すると、相対 URL を更新してくれます。

（2025/1 時点では Markdown の URL のみの対応で、Markdown 内の HTML の URL  (img, anchor タグ等）は更新してくれないため修正し Contribute しました）

https://github.com/mondeja/mkdocs-include-markdown-plugin/pull/252

また、Mkdocs の仕様として HTML の URL (img, anchor タグ等）はデフォルトのディレクトリ形式の URL の場合だと生成時のパス書き換えの対象となっていないため、間違ったパスになってしまいます。（例: `ABOUT.md` を `/ABOUT/` にするため、`./assets` も `../assets` にする必要がある。Markdown の画像やリンクは更新してくれるが Markdown 内の HTML は更新しない）

そのため現状はディレクトリ形式 URL (`/ABOUT/`) ではなくファイル形式 URL (`/ABOUT.html`) を使用する必要がある。

```mkdocs.yml
use_directory_urls: false
```

https://github.com/mkdocs/mkdocs/issues/991

関連 Issue / Discussion

https://github.com/mkdocs/mkdocs/discussions/3062


### Admonition

Github 形式の Admonition に対応させるため `gh-admonitions` プラグインを追加する必要があります。（admonition 拡張単体だと GitHub スタイルに対応していません）

:::note info
Admonition はこのように INFO, WARN などを強調表示することを指します。

GitHub Style では以下のフォーマットで記述します

```
> [!NOTE]
> コメント
```
:::

```mkdocs.yml
plugins:
  - gh-admonitions

markdown_extensions:
  - admonition
```

### md_in_html

GitHub では HTML タグの中に Markdown を入れても動作しますが、Mkdocs だと追加の拡張機能として `md_in_html` が必要です。

Markdown 側は HTML タグに `markdown="1"` の Attribute を追加する必要があります。

```
markdown_extensions:
  - md_in_html
```

```
<div markdown="1">
  [example](https://example.com)
  ![](https://example.com/sample.png)
</div>
```

Extension の Doc

https://python-markdown.github.io/extensions/md_in_html/

## 運用上のルールを変えるべき点

上記の設定で概ね GitHub 向けに Markdown を書けば Mkdocs でもページが生成されます。運営上追加で意識する点としては

- ファイルへのリンクは絶対 URL、ドキュメントへのリンクは相対リンクで記述する
- mkdocs serve でも動作確認する

の2点です。

## まとめ

以上が GitHub 上での Markdown によるドキュメント管理を維持・再利用しながら Mkdocs を導入し GitHub Pages を作成する際に使用した設計です。

他にも Hugo、Gatsby、Astro などの静的サイトジェネレータも運用していますが「GitHub 上でもそのままドキュメントとして機能する」、「ディレクトリ構造を変える必要なし」、「ファイル追加を最小限」（極論 `mkdocs.yaml` のみ) という点で Mkdocs は既存レポジトリに導入しやすい使用感でした。

しばらく運用してみてまたアップデートありましたら更新します。