HUGO におけるコンテンツの部品化(ショートコード)
ドキュメントを書いていると、同じ説明を複数のページに記載したくなる。
たとえば、リクエストヘッダーなどは API の URL が違っても、同じリクエストヘッダーを指定することは多い。
もちろん複数のコンテンツファイルに同じことを書けば良いが、内容を修正する場合に対象のすべてのファイルを修正してまわる必要があって面倒くさい。
できれば共通の説明は部品化して、いろんなコンテンツファイルで使い回したい。
HUGO だと部品化したコンテンツをショートコードで呼び出せばよさそう。
HUGO v0.117.0 で確認。
部品化したコンテンツを読み込む include
というショートコードを作る。
部品化したコンテンツのファイルパスを第1引数で受け取って、その内容を表示するショートコード。
ショートコードを利用するコンテンツは次のように書くイメージ。
ファイルパスは content
からのルート相対パスで指定する。
{{< include "/modules/foo.md" >}}
第1引数で渡された部品化コンテンツのファイルパスを .GatPage
して、その内容をレンダリングする。
.RawContent
は Markdown ファイルの内容がそのままレンダリングされるのに対し、.Content
は、Markdown ファイルの内容が HTML にパーズされてレンダリングされる。
今回は、HTML にパーズして欲しいので .Content
を使う。
{{- $page := site.GetPage (.Get 0) -}}
{{- $page.Content }}
呼び出される部品コンテンツである Markdown ファイルを作る。
Markdown シンタックスがレンダリングされることも確認したいので、ちょっとした装飾をする。
Hello, **chick-p**!
コンテンツで include
ショートコードを呼び出してみる。
---
title: Home
---
{{< include "/modules/foo.md" >}}
HTML を生成して中身を確認する。
modules/foo.md
の内容について、装飾を含めレンダリングされていることを確認できた。
<!DOCTYPE html>
<html>
<body>
<div id="content">
<p>Hello <strong>chick-p</strong></p>
</div>
</body>
</html>
ontent/modules 以下に置いたファイルは HTML として生成されるかは気になる。
コンテンツページとしてサイトの閲覧者に見えてほしくない。
今回は部品化したファイルを content/modules
以下に配置したので、public/modules/foo.html
が生成されていなければ OK。
foo.html は生成されなかったけど、その上のセクションページが生成されているのでだめです。
public/
├── css
├── index.html
├── js
├── modules
│ └── index.html <==== これが生成されるのは困る
└── sitemap.xml
下書きモード(draft: true)は効果があるかを調べる。
---
draft: true
---
効果はあって、modules/index.html
は生成されなくなった。
でも部品化したコンテンツも部品化したコンテンツを埋め込んで表示することができなくなった。
modules のページがなくなったので、部品化したコンテンツはページなし(「noPage」)となったせい。
.GetPage
は下書きモードのページを取得できないらしい。
ヘッドレスモードを使えとのこと。
というわけで、modules
以下をヘッドレスモードにして、部品化したコンテンツの内容をショートコードから取得してみる。
ヘッドレスモードにするには、セクションページのファイルを次のようにする。
-
_index.md
->index.md
にする
シングルページになるらしい。 - Frontmatter に
headless: true
を記載する
ショートコードはこう。
/modules
以下のページ一覧を取得して、ファイル名が一致するページのコンテンツをレンダリングする。
ファイル名は一意なはずなので、最初の要素にする。
{{- $headless := site.GetPage "/modules" }}{{- $headlessPages := ($headless.Resources.ByType "page") }}
{{- $page := index (where $headlessPages "Name" (.Get 0)) 0 }}
{{- $page.Content }}
部品化したコンテンツを置く「/modules
」はベタ書きにしたけど、設定に逃してもよさそう。
ショートコードを使う側はこう。
---
title: Home
---
{{< include "foo.md" >}}
HTML を生成してみる。
問題の modules/index.html
は生成されてないので OK!
public/
├── css
├── index.html
├── js
└── sitemap.xml
あとは、実用を考えると以下を考えた方がいいかも
- modules の下にディレクトリ作ってコンテンツを管理したい場合がありそう
-
where $headlessPages "Name" (.Get 0)
が 0 件だったときに死にそう
あとは操作手順を部品化したときに、正しく連番になるんだろうか。
つまり、ショートコードで読み込む部品化したコンテンツの箇条書きと、ショートコードを呼び出す側のコンテンツの箇条書きは、同じ番号付きリスト(<ol>
)内にあって欲しい。
手順の部品を作って
1. トップページにアクセスする。
1. 認証情報を入力してログインする。
ショートコードで呼び出したときに
1. Web ブラウザーを開く。
{{< include "/modules/operation.md" >}}
1. [Foo]をクリックする。
こうなるのが理想。
<ol>
<li>Web ブラウザーを開く。</li>
<li>トップページにアクセスする。</li>
<li>認証情報を入力してログインする。</li>
<li>[Foo]をクリックする。</li>
</ol>
ならない気がする。
ならない。もっと言えばネストされていて最悪。
<!DOCTYPE html>
<html>
<body>
<div id="content">
<ol>
<li>Web ブラウザーを開く。
<ol>
<li>トップページにアクセスする。</li>
<li>認証情報を入力してログインする。</li>
</ol>
</li>
<li>[Foo]をクリックする。</li>
</ol>
</div>
</body>
</html>
ショートコードの前後に空行を入れると、ネストは解消されるけどやっぱり別の <ol>
になる。
1. Web ブラウザーを開く。
{{< include "/modules/operation.md" >}}
1. [Foo]をクリックする。
<!DOCTYPE html>
<html>
<body>
<div id="content">
<ol>
<li>Web ブラウザーを開く。</li>
</ol>
<ol>
<li>トップページにアクセスする。</li>
<li>認証情報を入力してログインする。</li>
</ol>
<ol>
<li>[Foo]をクリックする。</li>
</ol>
</div>
</body>
</html>
というわけでまとめ。
- HUGO でコンテンツの部品化をするには、ショートコードを使えばよさそう。
- 部品化したコンテンツを置くディレクトリのセクションページをヘッドレスモードすると、不要なセクションページが生成されない。
- 実用を考えるともう少し考慮が必要だけど、こんな感じのショートコードで実現できる。themes/example-theme/layouts/shortcodes/include.html
{{- $headless := site.GetPage "/modules" }}{{- $headlessPages := ($headless.Resources.ByType "page") }} {{- $page := index (where $headlessPages "Name" (.Get 0)) 0 }} {{- $page.Content }}
- 一部の操作手順を部品化するのには向いていない。それぞれ別々の
<ol>
が生成されてしまうため。