Hugoは静的サイトジェネレーターですが、テンプレート(Go Template)を使用することで、比較的自由にカスタマイズ可能です。このブログも以前はWordPressを使用していましたが、現在はHugoを使用しています。
WordPressからHugoへ移行する場合、色々考慮点はあると思いますが、頭を悩ませる点が今までプラグインで補っていた機能をどうするかということです。と言っても、一般的なブログに必要と思われる要素は、Hugoがデフォルトで具備しています。
- OpenGraph
- Twitter Card
- Google Analytics
- Disqus
上記は一例ですが、さまざまなテンプレートが用意されていますので、Hugoのテーマを作成される場合には、ぜひ活用してみてください。
しかし、テンプレートには限界があるため、ある程度のカスタマイズを必要とする場合には自分で実装する必要があります。
ただ、静的サイトジェネレーターで、そこまでのカスタマイズはできないんじゃないか、と思われるかもしれません。しかし、Hugoは柔軟なテンプレート構造となっており、Go Template構文で書かれたテンプレートは、比較的自由にカスタマイズできます。もちろん、シンプルに実装することも可能です。
今回は、上記のデフォルトテンプレートで補うことが難しい一例として、Google AdSenseの記事本文への挿入を実装します。記事の前後に広告コードを配置するだけであれば単純ですが、記事の中に埋め込む場合には一工夫が必要です。
筆者自身、基本的な性格が横着なので、できうる限りのことは自動化したいと思うのです。
単一記事の表示時にGoogle AdSenseのコードをランダムな位置に表示する
Hugoには、.Scratchと呼ばれる「Scratchpad」(いわゆるメモ帳)のように、変数や値を格納しておける便利な機能があります。通常のテンプレートで使用する場面は少ないと思いますが、.Scratchを活用することでテンプレートの幅が広がります。バージョン0.43からnewScratch関数が実装され、ローカルスコープを持つスクラッチパッドを作成できるようになりました。
Markdownで生成された記事本文、正確には.Contentの戻り値であるHTMLを加工して、Google AdSenseのコードのランダム埋め込みを、この.Scratchを使って実装してみます。
変数や値の一時領域として使用できる.Scratch
.Scratchの使い方は公式サイトを参考にしてください。とりあえず、.Set、.Get、.Addあたりを覚えておけば十分でしょう。.Scratchと通常の変数の違いについては、例を見ながら後述します。
single.html
.Contentの戻り値であるHTMLから、<p>タグの数を抽出して、Google AdSenseのコードをランダム(今回の場合は、真ん中)に埋め込む実装例をご紹介します。ただし、意図せぬ埋め込むことによって、記事自体が見づらくなっては本末転倒です。実装する場合は、その点に十分注意してください。
.Contentは、改行コード区切りの整形されたHTMLを返却するため、改行コードで分割し配列に格納できます。配列に格納した文字列が<p>タグから始まり、かつコードを挿入する位置であった場合、Google AdSenseのコードを読み込み変数に挿入します。
Google AdSenseのコードを挿入する位置、.Contentを分割した配列を格納するために、.Scratchを使用します。
{{ /* 記事本文から、<p>タグの数を抽出 */ }}
{{ $p_tag := findRE "<p>(.|\n])+?</p>" .Content }}
{{ /* Google AdSenseのコードを配置する場所を決定 */ }}
{{ /* 下記の例では、記事の真ん中 */}}
{{ $position := div (len $p_tag) 2 }}
{{ /* 記事本文を改行コードで区切って配列に格納 */ }}
{{ $contents := split .Content "\n" }}
{{ $.Scratch.Set "Content" "" }}
{{ /* <p>タグの数をカウントするための一時変数をセット */ }}
{{ $.Scratch.Set "Counter" 0 }}
{{ range $contents }}
{{ /* 現在の行の末尾に改行コードを付与して追記 */ }}
{{ /* 改行コードを付与しないと、生成されるHTMLの改行が削除されるため注意 */ }}
{{ $.Scratch.Add "Content" (print . "\n") }}
{{ if hasPrefix . "<p>" }}
{{ /* <p>タグから始まる行をカウント */ }}
{{ $.Scratch.Add "Counter" 1 }}
{{ if eq ($.Scratch.Get "Counter") $position }}
{{ /* Google AdSenseのコードをテンプレートから読み込み */ }}
{{ $.Scratch.Add "Content" (partial "google-adsense.html") }}
{{ end }}
{{ end }}
{{ end }}
解説
ポイントを解説します。
{{ $p_tag := findRE "<p>(.|\n])+?</p>" .Content }}
{{ $position := div (len $p_tag) 2 }}
.Contentから返却される戻り値は、HugoのMarkdownパーサーであるBrackfridayによって整形されたHTMLです。今回は、段落を基準にGoogle AdSenseのコードを挿入する位置を決めるため、HTMLコードの中から<p>タグの数を検出します。抽出した数を加工し(今回は中央値)、挿入する位置を決めます。
{{ $contents := split .Content "\n" }}
HTMLを改行コードで分割し、配列に格納します。
{{ $.Scratch.Set "Content" "" }}
{{ $.Scratch.Set "Counter" 0 }}
いよいよ.Scratchの登場です。ContentとCounterという新たな変数を定義しています。先ほど、$contentsや$positionといった変数を定義しました。ContentやCounterも、わざわざ.Scratchを使用することなく、変数を定義すれば良いだけではないのかと思われる方もいらっしゃるかもしれません。では、なぜ.Scatchを利用するのでしょうか。
{{ range $contents }}
{{ /* do stuff */ }}
{{ end }}
これはおなじみの構文です。配列の要素に対して、1つずつ処理を行います。
さて、ここで覚えておくべきHugoのテンプレート(Go Template)の変数のスコープに関する制限事項があります。先ほど定義した$contentsや$positionは、rangeからend、ifからendで定義されたブロックの範囲外のみで有効な変数です。つまり、これらの変数をrangeやifの構文中で上書きすることはできませんし、ブロックの中で参照することもできません。
{{ $counter := 5 }}
{{ if eq $counter 5 }}
{{ $counter := add $counter 1 }}
{{ /* 表示される値は1です */ }}
{{ $counter }}
{{ end }}
{{ /* 表示される値は5です */ }}
{{ $counter }}
では、条件に応じて変数の値を動的に変更したい場合にどうするか。そこで登場するのが.Scratchです。.Scratchで定義した変数のスコープはそのページ内でグローバルです。
以上のことを念頭に置いて、もう一度実装例をご覧になってみてください。もうほぼ解説する点はないのですが、1点のみ注意事項があります。
{{ $.Scratch.Add "Content" (print . "\n") }}
オリジナルのContentを生成する過程で、必ず要素の末尾に改行コードを挿入してください。split関数により改行コードをデリミターとして使用したため、そのまま要素を結合していくと、改行コードが失われた状態になります。<pre>タグ内に記述したソースコード中の改行コードも抹消されてしまうため、大変みづらい状態になります。改行が適切でないソースコードなど、誰も目を通してくれないはずです。
記事の表示
最後に記事を表示したい部分に先ほど生成したContentを取得し、表示して完了です。.Contentの出力を意図的に加工したため、safeHTML関数を使用して出力しています。
{{ $.Scratch.Get "Content" | safeHTML }}
google-adsense.html
Google AdSenseのコードを直接記述しても構わないのですが、メンテナンス性を考慮すると、分離しておいた方が良いと思われます。partialsディレクトリ配下に作成します。内容は、Google AdSenseの公式サイトから広告ユニットのコードを取得し、貼り付けているだけです。さらに改良するなら、data-ad-client等の固定値はconfig.tomlに移動させても良いでしょう。
<ins class="adsbygoogle"
style="..."
data-ad-client="ca-pub-..."
data-ad-slot="..."></ins>
<script>(adsbygoogle = window.adsbygoogle || []).push({});</script>
パフォーマンスへの影響は?
テンプレートを作り込めば作り込むほど、パフォーマンスへの影響は現れます。ただ、Hugo自体が非常に高速であることから、今回のように.Contentの出力結果を加工するだけであれば、パフォーマンス(ビルド時間への影響)はほぼありませんでした。
ただ、単純に出力する場合と比較して、リソース(CPUやメモリ)の使用率は高くなる可能性がありますので、やりすぎには注意が必要です。