Hugo : Page Bundlesルールでの「index.md」以外のマークダウンファイルの扱い
Hugo の Page Bundles のルールでは、記事となる index.md
と同じ階層に置かれた index.md
以外のマークダウンファイルはどう扱われるのか?
よく Page Bundles の説明では「配置された index.md
以外のマークダウンファイルは、あくまで Branch Bundles または Leaf Bundle 内のリソースファイルとして扱われる。」と書いてあります。実際のところはどうなのでしょうか?
まず、いくつか前提として...
- uglyURLs = true は使わない
uglyURLs の設定は推奨もされていないし、記事ファイルに関係する画像ファイルなどの扱いが面倒なことになるので基本 uglyURLs は有効にしません。
- Permalinks の設定も使わない
またセクションの階層(content以下のフォルダ構造そのまま)をカテゴリー分類として使うので、config.toml
での Permalinks の設定も使わないものとします。
するとデフォルトでは Hugo は、例えば content/posts/001/index.md
とした記事ファイルはフォルダ構造のまま書き出します。記事のURLもhttp://localhost:1313/posts/001/
です。例えば記事のマークダウン文に画像などのリンクがある場合、この記事と同階層に置いた各種の記事関連ファイルはどう扱われるのでしょうか? とくに 記事に別の index.md
ではない .mdファイルを関連付けたい場合、どうすればいいのでしょうか?
Page Bundles の仕組み
1. Branch Bundles
まず、Branch Bundles つまりセクションを表す _index.md
が置かれた階層と同じフォルダにある .mdファイルはどうなるのか?
これにはさしたる問題は無いようです。例えば sectionA/section-memo.md
という .mdファイルは、sectionA/section-memo/index.html
という名前でちゃんとレンダリングされたHTMLになります。
2. Leaf Bundles
わかりにくいのはこの Leaf Bundles つまり個々の記事本体となる index.md
が置かれたフォルダにある .mdファイルです。
虫のいい考え方だと、index.md
と同じようにそれなりにHTMLファイルにレンダリングされて、画像ファイルなどと一緒に index.html
のそばに配置されるんだろうと捉えてしまいます。しかしそううまくはいきません。
まず /content/posts
以下に 001 というフォルダを作って、中に index.md
を置いて記事を書いてみます。001
というフォルダの中はこうなっていたとします。
/content/posts$ tree -L 3 . └── 001 ├── doc.pdf ├── img │ └── test02.png ├── index.md ├── memo.md ├── movie.mp4 └── test01.png
index.md
のマークダウンにこの中の各ファイルへのリンクを書いたとします。すると次のように PDF、画像、動画の各ファイルへのアクセスはあっさり成功します。
- PDFファイル ->
doc.pdf
- 画像ファイル ->
test01.png
,img/test02.png
- 動画ファイル ー>
movie.mp4
特にファイルパスを絶対アドレスにする必要もなく、カレントディレクトリを表す「.」をつけて ./doc.pdf
とかしなくても doc.pdf
でアクセスできます。内部フォルダがある場合は img/test02.png
とします。もちろん「.」をつけて ./movie.mp4
、./img/test02.png
などとしても問題ありません。
しかし memo.md
という .mdファイルは index.md
が index.html
になるのと同じように memo.html
になるのかというとそうならないです。実際 hugo
コマンドでビルドしてみると、セクション posts
以下は、
/public/posts$ tree -L 3 . ├── 001 │ ├── doc.pdf │ ├── img │ │ └── test02.png │ ├── index.html <-- メインとなる記事 │ ├── movie.mp4 │ └── test01.png └── index.html <-- セクション「posts」のインデックス
となります。/posts/index.html
は自動的に書き出される posts
セクションの index で、/posts/001/index.html
がもとの /posts/001/index.md
がレンダリングされたメイン記事です。このとおり memo.html
なんてファイルはどこにも書き出されていません。完全無視です。
ではどうやったら memo.md
が memo.html
になるのでしょうか?
※ここではファイルの呼び名がいろいろとややこしいので、Leaf Bundles に置いたメインの記事ファイルはindex.md
、memo.md
のようなメインの記事から読みたい添付の .mdファイルは、「リーフの.mdファイル」などと呼ぶことにします。
index.md 以外の名前にしてみる
まず記事本体を /posts/001/index.md
などという名前を止めて index 以外の名前、例えば main.md
にしてみます。uglyURLs
の設定がされていなければ、これはデフォルトでは main
というフォルダが作られて、そのなかに index.html
が書き出されるはず。するとその他のファイルはどうなるか?ビルドしてみます。
/public/posts$ tree -L 3 . ├── 001 │ ├── doc.pdf │ ├── img │ │ └── test02.png │ ├── main │ │ └── index.html │ ├── memo │ │ └── index.html │ ├── movie.mp4 │ └── test01.png └── index.html
今度は main.md
が main/index.html
になり、同じルールで memo.md
が memo/index.html
に書き出されました。このルールがわかっていれば記事ファイルから、リーフの.mdファイルを ../memo/
などというパスでアクセスしても問題は無いように思えます。
しかし元の下書きのファイル構成とは変わってしまうので、かなり理解しにくいですね。
index.md
以外の名前にし、フロントマターのURLパラメータを設定する
では、記事本体に index.md
以外の名前を使い、フロントマターのURLパラメータを設定するとどうなるのでしょうか。新規記事の雛形を書き出す new
コマンドで、ファイル名を普通は index.md
とするところを 例えば main.md
とします。
$ hugo new content/posts/001/main.md
フロントマターの draft: true
はコメントアウトするか削除して、URLパラメータを次のように書き換えます。
--- url: "/posts/001/" ---
ビルドしてみると、記事となる index.html
は URLパラメータで指定した場所になり、同じ階層に他のファイルもコピーされ、memo.md
は memo/index.html
となってレンダリングされます。
$ tree -L 3 ./public/posts/ ./public/posts/ ├── 001 │ ├── doc.pdf │ ├── img │ │ └── test02.png │ ├── index.html <-- メインとなる記事 │ ├── memo │ │ └── index.html <-- memo.md がレンダリングされたHTML │ ├── movie.mp4 │ └── test01.png
これならば、下書きの階層をほぼ保ったままで、記事の中から同一階層のファイルにアクセスできるでしょう。
Branch Bundles の仕組みとマークダウンファイルのURLパラメータを併用する
さて 記事ファイルと同じ階層のフォルダに置いたリーフの .mdファイルが、Hugoの仕組みでレンダリングされるようにはなったわけですが、場合によってはフロントマターのURLパラメータに、記事ファイルの移動に影響しない不変のパーマリンクURLを指定したい場合があります。
たとえば /archetypes/default.md
に次のように書いておけば、日付をもとにしたパーマリンクURLを指定できます。
--- title: "{{ replace .Name "-" " " | title }}" thumbnail: "" description: "" date: {{ .Date }} url: "/posts/{{ .Date | dateFormat "2006-01-02T15:04:05" | urlize }}/" tags: [] draft: true ---
これで記事の雛形を書き出してみます。ただし今度は index.md で
$ hugo new content/posts/001/index.md
この場合はもし index.md
を main.md
などと名前を変えると、 memo.md
などの添付マークダウンは書き出されるものの、/post/001
以下に記事関連ファイルが書き出され、パーマリンクURLで指定した記事の index.html
は /posts/パーマリンクURL/index.html
となり、またしても別階層に別れてしまうので管理しずらい(リンクが貼りにくい)です。
そこで多少の妥協+考え方を変えてみます。メイン記事のファイル名はあくまで index.md
のままで、memo.md
などリーフの.mdファイルは /posts/docs/memo.md
などといった Branch Bundles の場所に格納し、そのmemo.md
などのフロントマターに本体記事のパーマリンクURLに書き出されるようなURLパラメータを指定してみます。ついでにこれらのリーフの.mdファイルがリスト表示されないように _build
オプションも加えます。
本体記事(/post/001/index.md
)側のフロントマター
--- title: "メイン記事" thumbnail: "" description: "" date: 2022-09-03T19:14:34+09:00 url: "/posts/2022-09-03t191434/" <- パーマリンクURL指定 tags: [] # draft: true ---
関連マークダウンファイル(/post/docs/memo.md
)側のフロントマター
--- title: memo01 url: "/posts/2022-09-03t191434/memo/" <- 記事のパーマリンクURL指定に合わせた指定 _build: list: false ---
ビルドしてみると面白いことに、記事のパーマリンクURLから作られたフォルダ階層の中にちゃんと memo/index.html
が作られました。これなら記事からのアクセスも簡単です。
$ tree -L 3 ./public/posts/2022-09-03t191434/ ./public/posts/2022-09-03t191434/ ├── doc.pdf ├── img │ └── test02.png ├── index.html ├── memo │ └── index.html ├── movie.mp4 └── test01.png
記事の index.md
があるフォルダの上位の所属セクションフォルダ配下に .md を置くので、若干記事との関連がわかりづらいですが、なんでもかんでも static
配下に置くよりマシです。本当は Branch Bundle 直下にフォルダを作るのは Leaf Bundle だけなので、ルール上は「不正なリソース」とみなされるのでしょうが、その場合は memo.md
を /posts
直下に置けばいいかと思います。ただし/posts
直下に Leaf Bundle が多く関連ファイルも多い場合は管理しにくいでしょうね。「不正なリソース」かもしれませんが、画像などではなくマークダウンファイルです。記事となるマークダウンファイルのファイル名は index.md
以外も許されているわけですし、特にエラーになるわけでもないので気にせず管理しやすい方法を選べばいいのではないでしょうか。
ちなみに、/posts/memo.md
のURLパラメータを次のように uglyURL
にしてしまえば、フォルダに内包されずに 直接 /posts/2022-09-03t191434/memo.html
が書き出されます。このへんは好みの問題でしょうか?
--- url: "/posts/2022-09-03t191434/memo.html" ---
関連マークダウンファイルに貼る画像などの置き場
この場合のリーフの .mdファイルに貼る画像などは、指定したURLに結びつける必要があります。
たとえば 上位階層のセクションフォルダに置いた関連 .md ファイルに、記事ファイルのimg
フォルダにあるtest.png
を表示させたいなら、マークダウンの画像のパスは元の記事本体ファイル(index.md)からのパスでないと認識しません。
記事本体index.md
のURLパラメータがurl: "/posts/2022-09-03t191434/"であり、
上記のmemo.md
のURLパラメータが
--- url: "/posts/2022-09-03t191434/memo.html" ---
ならば、そのmemo.md
の中にimg/test.png
を表示させるのには、次のようにパスを元記事に合わせます。
![テスト画像](/posts/2022-09-03t191434/img/test.png)
すべてをhugo
コマンドでビルドした場合に書き出されるパスに合わせる と考えればわかりやすいでしょう。
リーフの .md ファイルに独自のレイアウトを適用する
このままではリーフの .mdファイルにも、記事と同じ/layouts/_default/single.html
といったレイアウトが適用されてしまいます。もし不都合なら、独自のレイアウトを作り適用したいです。
そこで.mdファイルのフロントマターでtype
とlayout
を指定します。
ここではlayout
に/layouts/special/html-single
という中身だけを表示する単純なレイアウトファイルを適用します。
<!--Content-->
{{with .Content}}
{{.}}
{{end}}
結果的に関連ファイルとしての.mdファイルのフロントマターの雛形はこんな感じになります。.mdファイルはフォルダを作らせず直接HTMLファイル名にします。
--- url: /posts/2022-09-03t191434/memo.html type: special layout: html-single _build: list: false ---
ちなみに HugoのPage Bundlesでは、生のHTML(拡張子が.html)ファイルを関連ファイルとして配置すると「plain HTML documents not supported」といったエラーがでます。しかしHTMLファイルであっても先頭にフロントマターがあればこのエラーは回避できます。上記のフロントマターを書いて、例えば/post/mdfiles/
といった場所にmemo.md
と一緒にdoc.html
といった感じで配置すれば、本体記事からファイル名だけで関連ファイルとして参照できます。ネットからダウンロードしたページなどを参考にしたりできるようになります。
記事と同じCSSをリーフの .md ファイルにも適用する
.Content
だけのレイアウトでは、記事本文で使っているCSSスタイルは適用されないので、記事と同じスタイルをリーフの .mdファイルにも適用させたかったら、partial
で分離した head タグ部分を読み込ませればいいかと思います。ここでは記事のレイアウトと揃えるためにmd-content
などのクラス名も付けました。
{{- partial "site/head" . -}} <!--Content--> {{with .Content}} <section id="post-content" class="post-content md-content"> {{.}} </section> {{end}}
もっと簡単な方法もあるが...
実際はこんなめんどうなことをしなくても、例えば VSCode
+Markdown Preview Enhanced
の環境を作って、VSCode
で書いた .mdファイルをMarkdown Preview Enhanced
の機能でHTMLとして書き出せば、なぜか?Hugoの記事があるフォルダ(index.md
のある Leaf Bundles のフォルダ)の中に直接HTMLファイルがあってもHugoから「生のHTMLを置くな!」とか怒られたりしません。フォルダに直接HTMLをコピーすると怒られるのにそのへんの違いが謎ですが、たぶん元になる .mdファイルがある場合は扱いが違うんでしょう。
これだとわざわざ Branch Bundles 下に置かなくても記事から直接参照できるし楽ですが、ただしMarkdown Preview Enhanced
の機能でHTMLを書き出すので、変数や関数はもちろんショートコードやスタイルなどHugo側の機能は使えません。ここは目的に応じて使い分ければいいと割り切ります。