Closed25

HUGO におけるコンテンツの部品化(ショートコード)

piyopiyo

ドキュメントを書いていると、同じ説明を複数のページに記載したくなる。
たとえば、リクエストヘッダーなどは API の URL が違っても、同じリクエストヘッダーを指定することは多い。
もちろん複数のコンテンツファイルに同じことを書けば良いが、内容を修正する場合に対象のすべてのファイルを修正してまわる必要があって面倒くさい。

piyopiyo

できれば共通の説明は部品化して、いろんなコンテンツファイルで使い回したい。
HUGO だと部品化したコンテンツをショートコードで呼び出せばよさそう。

piyopiyo

部品化したコンテンツを読み込む include というショートコードを作る。
部品化したコンテンツのファイルパスを第1引数で受け取って、その内容を表示するショートコード。

ショートコードを利用するコンテンツは次のように書くイメージ。
ファイルパスは content からのルート相対パスで指定する。

{{< include "/modules/foo.md" >}}
piyopiyo

第1引数で渡された部品化コンテンツのファイルパスを .GatPage して、その内容をレンダリングする。
.RawContent は Markdown ファイルの内容がそのままレンダリングされるのに対し、.Content は、Markdown ファイルの内容が HTML にパーズされてレンダリングされる。
今回は、HTML にパーズして欲しいので .Content を使う。

themes/example-theme/layouts/shortcodes/include.html
{{- $page := site.GetPage (.Get 0) -}}
{{- $page.Content }}
piyopiyo

呼び出される部品コンテンツである Markdown ファイルを作る。
Markdown シンタックスがレンダリングされることも確認したいので、ちょっとした装飾をする。

content/modules/foo.md
Hello, **chick-p**!
piyopiyo

コンテンツで include ショートコードを呼び出してみる。

content/_index.md
---
title: Home
---

{{< include "/modules/foo.md" >}}

piyopiyo

HTML を生成して中身を確認する。
modules/foo.md の内容について、装飾を含めレンダリングされていることを確認できた。

public/index.html
<!DOCTYPE html>
<html>
  <body>
    <div id="content">
      <p>Hello <strong>chick-p</strong></p>
    </div>
  </body>
</html>
piyopiyo

ontent/modules 以下に置いたファイルは HTML として生成されるかは気になる。
コンテンツページとしてサイトの閲覧者に見えてほしくない。

piyopiyo

今回は部品化したファイルを content/modules 以下に配置したので、public/modules/foo.html が生成されていなければ OK。

piyopiyo

foo.html は生成されなかったけど、その上のセクションページが生成されているのでだめです。

public/
├── css
├── index.html
├── js
├── modules
│   └── index.html <==== これが生成されるのは困る
└── sitemap.xml
piyopiyo

下書きモード(draft: true)は効果があるかを調べる。

content/modules/_index.md
---
draft: true
---
piyopiyo

効果はあって、modules/index.html は生成されなくなった。
でも部品化したコンテンツも部品化したコンテンツを埋め込んで表示することができなくなった。
modules のページがなくなったので、部品化したコンテンツはページなし(「noPage」)となったせい。

piyopiyo

というわけで、modules 以下をヘッドレスモードにして、部品化したコンテンツの内容をショートコードから取得してみる。

piyopiyo

ヘッドレスモードにするには、セクションページのファイルを次のようにする。

  • _index.md -> index.md にする
    シングルページになるらしい。
  • Frontmatter に headless: true を記載する
piyopiyo

ショートコードはこう。
/modules 以下のページ一覧を取得して、ファイル名が一致するページのコンテンツをレンダリングする。
ファイル名は一意なはずなので、最初の要素にする。

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 }}

部品化したコンテンツを置く「/modules」はベタ書きにしたけど、設定に逃してもよさそう。

piyopiyo

ショートコードを使う側はこう。

content/_index.md
---
title: Home
---

{{< include "foo.md" >}}

piyopiyo

HTML を生成してみる。
問題の modules/index.html は生成されてないので OK!

public/
├── css
├── index.html
├── js
└── sitemap.xml
piyopiyo

あとは、実用を考えると以下を考えた方がいいかも

  • modules の下にディレクトリ作ってコンテンツを管理したい場合がありそう
  • where $headlessPages "Name" (.Get 0) が 0 件だったときに死にそう
piyopiyo

あとは操作手順を部品化したときに、正しく連番になるんだろうか。
つまり、ショートコードで読み込む部品化したコンテンツの箇条書きと、ショートコードを呼び出す側のコンテンツの箇条書きは、同じ番号付きリスト(<ol>)内にあって欲しい。

piyopiyo

手順の部品を作って

content/modules/operation.md
1. トップページにアクセスする。
1. 認証情報を入力してログインする。

ショートコードで呼び出したときに

content/_index.md
1. Web ブラウザーを開く。
{{< include "/modules/operation.md" >}}
1. [Foo]をクリックする。

こうなるのが理想。

<ol>
  <li>Web ブラウザーを開く。</li>
  <li>トップページにアクセスする。</li>
  <li>認証情報を入力してログインする。</li>
  <li>[Foo]をクリックする。</li>
</ol>

ならない気がする。

piyopiyo

ならない。もっと言えばネストされていて最悪。

public/index.html
<!DOCTYPE html>
<html>
  <body>
    <div id="content">
      <ol>
        <li>Web ブラウザーを開く。
          <ol>
            <li>トップページにアクセスする。</li>
            <li>認証情報を入力してログインする。</li>
          </ol>
        </li>
        <li>[Foo]をクリックする。</li>
     </ol>
    </div>
  </body>
</html>
piyopiyo

ショートコードの前後に空行を入れると、ネストは解消されるけどやっぱり別の <ol> になる。

content/_index.md
1. Web ブラウザーを開く。

{{< include "/modules/operation.md" >}}

1. [Foo]をクリックする。
public/index.html
<!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>
piyopiyo

というわけでまとめ。

  • 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> が生成されてしまうため。
このスクラップは2023/08/11にクローズされました