Hugoのテンプレート構文「template」「partial」「block」「define」のわかりやすい解説

Blog Hugo

Hugo のオリジナルテーマ作成を勝手に応援する企画、記念すべき第 1 弾はテンプレート構文の基本となる「template」「partial」「block」「define」の違いについてです。以下のバージョンで確認しています。

Hugo Static Site Generator v0.53/extended darwin/amd64 BuildDate: unknown

Hugo のテンプレート構文

Hugo には様々なテンプレート構文が用意されています。今回は、その中でも基本となる以下の 4 つについて、特につまずきやすい点を中心に解説します。記事の内容に対するご質問やご指摘等はコメント欄でお待ちしております。

  • template
  • partial
  • block
  • define

template

template構文は、Hugo であらかじめ定義されている内部のテンプレートファイルを読み込むための構文です。オリジナルテーマの独自の HTML ファイルを同構文で使用することはできません。

{{ template _internal/disqus.html . }}

また、templateに指定できるテンプレートは、Hugo のバージョンに依存します。さらに、テンプレートとして指定できる HTML ファイルは、Hugo に組み込まれているため、表面上はどこにもありません。中身を知るためには、GitHub のソースコードを追うしかありません。

Hugo 0.53 では、以下のテンプレートが用意されています。テンプレートの中身の説明は別の機会に譲り、ここでは詳細は割愛します。

内包されているテンプレートのソースも、Hugo の構文に従って記載されています。そのため、テンプレートのソースを読み込むことで、Hugo の構文の理解に役立ちます。時間のあるときに一度目を通しておくことをオススメします。オススメは、hugo/opengraph.html at master · gohugoio/hugoです。

partial

templateは、Hugo 独自の内部のテンプレートを読み込むための構文でした。Hugo に内包されていて、直接内容を参照することはできませんでした。それに対してpartialは、オリジナルテーマで作成した、独自の HTML ファイルを読み込むための構文です。

{{ partial article-header.html . }}

partialで指定したファイルは、以下の優先度順で読み込まれます。

  1. layouts/partials/*<PARTIALNAME>.html
  2. themes/<THEME>/layouts/partials/*<PARTIALNAME>.html

ファイル名は任意で構いませんが、必ず上記のフォルダ配下に配置する必要があります。もし、上記のディレクトリ配下に該当のファイルが見つからない場合は、ビルド時にエラーが発生します。

また、partialの読み込み元で、さらに別の HTML ファイルを読み込むことができます。

partialは、HTML ファイルを最小限のコンポーネント毎に分割した部品です。テーマで共通的に使用するサイトのヘッダーやフッター、ソーシャルネットワークのシェアボタンなど、共通的に使用する部品を分割しておき、後から用途に応じて使用できます。

1 つのテンプレートファイルが肥大化した場合、そのファイル自身の用途がわかりづらくなってしまうため、用途毎にpartialによって Partial Template とすると良いでしょう。

具体例

{{ partial "site-header.html" . }}
{{ partial "site-content.html" . }}
{{ partial "site-footer.html" . }}

block

Hugo で最初につまずく構文が、blockと、後述するdefineではないでしょうか。前述のpartialと混同してしまうことがよくあります。

partialはオリジナルテーマの HTML ファイルを読み込むために使用する構文でした。それに対して、blockはファイルを読み込むための構文ではなく、文字通り「ブロック」を定義するための構文です。

{{ block main . }}
...
{{ end }}

partialはファイルを読み込むものでしたが、blockは引数に定義した名称のブロックの範囲を定義します。範囲を定義するため、必ず終わり(end)を指定する必要があります。

そして、partialで読み込まれる HTML ファイル中に、blockを定義できます。

では、なぜblockという構文が必要なのでしょうか。それは、後述のdefineで解説します。

define

defineは、必ずblockとペアで使用します。単独で使用しても意味がありません。逆もまた同様です。defineは、blockで定義したブロックを実装、もしくは上書きする役目を持っています。プログラマティックに表現すれば、blockが抽象、defineがその実装だと解釈すればわかりやすいかもしれません。

blockで定義したブロックの実装、すなわちdefineが存在しない場合でも、ビルド時にはエラーになりません。逆もまた然りで、defineで定義した実装の、blockの範囲が存在しない場合でも、ビルド時にはエラーになりません。

blockで定義したブロックの実装が存在しない場合、ビルド時はblock構文に記述した内容がそのまま表示されます。blockで定義された内容が空の場合は、何も表示されません。また、defineで定義したブロックが存在しない場合、define構文は無視され、何も表示されません。

では、blockdefineはどのような時に使用すれば良いのでしょうか。理解を深めるためには、Hugo の Base Template を合わせて理解しておくと良いでしょう。

Base Template

Hugo のテンプレートには予約されているファイルがあります。Base Template と呼ばれています。Base Template は、記事執筆時点では以下のファイルがあります。

  1. /layouts/section/<TYPE>-baseof.html
  2. /themes/<THEME>/layouts/section/<TYPE>-baseof.html
  3. /layouts/<TYPE>/baseof.html
  4. /themes/<THEME>/layouts/<TYPE>/baseof.html
  5. /layouts/section/baseof.html
  6. /themes/<THEME>/layouts/section/baseof.html
  7. /layouts/_default/<TYPE>-baseof.html
  8. /themes/<THEME>/layouts/_default/<TYPE>-baseof.html
  9. /layouts/_default/baseof.html
  10. /themes/<THEME>/layouts/_default/baseof.html

ビルド時に上から順番に捜査され、記事の<TYPE>に応じた Base Template が読み込まれます。このテンプレートファイルの中で、テーマ作成に必須のファイルは、/layouts/_default/baseof.htmlのみです。これを踏まえた、オリジナルテーマ作成時の最小の構成は以下の通りです。

├── _default
│   ├── baseof.html
│   ├── list.html
│   └── single.html
└── index.html

index.htmlは、トップページ(フロントページ)のレンダリングに使用されます。list.htmlは、カテゴリーやタグ、またフロントページ等に表示する記事の一覧を表示させるためのテンプレートです。最後のsingle.htmlは、記事単体をレンダリングするための、最も重要なテンプレートファイルとなります。baseof.htmlファイルは、すべてのファイルが読み込まれる際に合わせて読み込まれます。

通常は、これらに前述の Partial Template 等を組み合わせて使用することとなります。

baseof.htmlの記述例をみてみましょう。

<!DOCTYPE html>
<html lang="{{ $.Site.LanguageCode | default "ja" }}" class="no-js">

<head>
	<meta charset="utf-8" />
	<meta http-equiv="x-ua-compatible" content="ie=edge">
	<title>{{ block "title" . }}{{ .Site.Title }} {{ with .Params.Title }} | {{ . }}{{ end }}{{ end }}</title>
	<meta name="viewport" content="width=device-width, initial-scale=1.0" />
	<meta name="robots" content="noindex, nofollow">

	{{ if .RSSLink }}
	<link href="{{ .RSSLink }}" rel="alternate" type="application/rss+xml" title="{{ .Site.Title }}" />
	<link href="{{ .RSSLink }}" rel="feed" type="application/rss+xml" title="{{ .Site.Title }}" />
	{{ end }}

	{{- template "_internal/opengraph.html" . -}}
	{{- template "_internal/google_news.html" . -}}
	{{- template "_internal/schema.html" . -}}
	{{- template "_internal/twitter_cards.html" . -}}
	{{- template "_internal/google_analytics_async.html" . -}}
</head>

<body>
	{{ block "header" . }}{{ partial "site-header.html" .}}{{ end }}
	<main class="grid-container" role="main">
		{{ block "main" . }}{{ end }}
	</main>
	{{ block "footer" . }}{{ partial "site-footer.html" . }}{{ end }}
	{{ block "scripts" . }}{{ partial "site-scripts.html" . }}{{ end }}
</body>

</html>

このテンプレートには、Hugo で生成される静的サイトの骨格とも言える内容が含まれています。例えば、<head>タグに囲まれた内容は、記事の内容を問わず毎回レンダリングすべき内容です。index.htmllist.htmlsingle.htmlのすべてのファイルに同一の内容を記述しても良いのですが、冗長であることは言うまでもありません。baseof.htmlにまとめて記述しておくことで、サイトの共通部分をすべて 1 つにまとめることができます。

ここで、下記の部分に注目してください。

	<main class="grid-container" role="main">
		{{ block "main" . }}{{ end }}
	</main>

mainと呼ばれるブロックが定義されていることがわかります。ブロックの内容は空であり定義されていません。index.htmlであれば個別の記事への誘導やホームページに関する情報、single.htmlであれば個別記事の内容、list.htmlであれば記事の一覧を表示するべきです。そこで、baseof.htmlではmainと呼ばれる空のブロックのみ定義しておき、実際のブロックの実装は各テンプレートに委ねています。こうすることで、各テンプレート側では個別に実装する部分のみを意識すればよく、サイトのヘッダーやフッター等の共通部分はbaseof.htmlにお任せすれば良いことになります。

では、index.htmlの記述例を見てみます。

{{ define "main" }}
<div id="main" class="grid-x grid-margin-x" style="margin-top: 5rem;">
	<div class="cell small-12 article-list">
		{{ range .Paginator.Pages }}
		{{ .Render "li" }}
		{{ end }}
	</div>
	{{ template "_internal/pagination.html" . }}
</div>
{{ end }}

index.htmlには、ブロックに対する実装部分のみが記述されています。フロントページが呼び出された場合、index.htmldefineで定義されたブロックが、baseof.htmlmainブロックを上書きし、上記の内容に書き換えます。blockdefineのイメージが少し湧いてきましたでしょうか。

別の例をお見せします。OGP(Open Graph Protocol)で、記事のアイキャッチ画像を<meta>タグ内に記述したい場合があります。フロントページでは、デフォルトの画像、個別の記事ではアイキャッチ画像をタグ内に記述したいとします。このような場合、まずbaseof.htmlにデフォルトのタグを定義しておきます。

{{ block "ogp-image" . }}
<meta property="og:image" content="[Default Image URL]">
{{ end }}

仮にogp-imageというブロックを定義しました(このようにブロックには任意の名称を付与できます)。そして、single.htmlに個別のアイキャッチ画像を定義するためのブロックの実装を記述します。

{{ define "ogp-image" }}
<meta property="og:image" content="[Eyecatch Image URL]">
{{ end }}

これで、アイキャッチ画像が設定されていない場合はデフォルトの画像、個別記事などアイキャッチ画像が指定されている場合はその画像の URL で<meta>タグの内容が上書きされるようになります。

このように、blockdefineを有効活用することで、サイトの全体最適化を図ることができます。

オリジナルテーマ作成にあたって

ただし、いきなりこのような全体最適化を図ることは難しいものです。そこで、以下のような順序でテンプレートを最適化していくことをオススメします。

  1. index.htmllist.htmlsingle.htmlを作成する(Hugo の構文を使用しない素の HTML ファイルを生成しましょう)
  2. 共通部分(ヘッダー、フッター等)を別ファイルに切り出して、Partial Template 化する
  3. テンプレートによって内容が異なる部分(コンテンツ)を、blockとして切り出す

このように大きな一枚岩のファイルから、徐々に全体最適化された個別のファイルを切り出していく方法をオススメします。こうすることで、必要な情報をすべて網羅した上で、かつテンプレートファイルを最小限に抑えることができます。

参考リンク

comments powered by Disqus