blog.unresolved.xyz

Hugoのテーマを何個か作ったので知見をまとめてみる

Sun Oct 02 2016

    Hugoは静的サイトジェネレータの中でもだいぶ柔軟性のあるツールだと思う。

    でも、その分気をつけなきゃいけないことがあったり、実現する方法が分かりづらかったりすることが多いので、 そういう点をまとめてみる。

    テーマの作成

    まずはテーマを作る。 この章では、テーマを作るための環境の作り方を解説する。

    HugoBasicExampleをcloneしよう

    HugoにはHugoBasicExampleという、サンプルの記事や基本的な機能を使ったテスト用レポジトリが用意されている。

    spf13/HugoBasicExample: Example site to use with Hugo & Hugo Themes

    Hugoのテーマづくりは、このレポジトリのcloneから始まる。

    hugo new theme

    次にテーマの雛形を作る。 Hugoにはテーマの雛形を作り上げるコマンドがある。

    hugo new theme [name]
    

    hugo newコマンドは、新しくサイトを作るときやページを作るときにも使われる。 とにかく、何か新しいものを作るときに使われるコマンドで、テーマを作るときもこれを使う。

    cloneしたHugoBasicExampleのルートディレクトリでコマンドを実行すれば、./themes/XXXXに雛形が作られる。

    後述するthemes.gohugo.ioに載せてもらうために必要なファイルも揃っているので、0からファイルを作るよりこのコマンドを使うのが賢明だと思う。

    ページの構造とテーマファイルの基礎的な構造を理解する

    Hugoのページ構造はだいたい次の通り。

    • HomePage
    • list
    • single
    • taxonomies
    • terms
    • 404.html

    で、hugo newで作られるテーマファイルの構成は次の通り。

    .
    ├── LICENSE.md
    ├── archetypes
    │   └── default.md
    ├── layouts
    │   ├── 404.html
    │   ├── _default
    │   │   ├── list.html
    │   │   └── single.html
    │   ├── index.html
    │   └── partials
    │       ├── footer.html
    │       └── header.html
    ├── static
    │   ├── css
    │   └── js
    └── theme.toml
    

    実は、この雛形には足りないものや要らないものもある。 それはおいおい自分の好きなように足したり消したりすればいい。

    テンプレートファイルと対象ページの紐付けを理解する

    テンプレートファイルと対象ページの紐付けは、実は少しややこしい。

    なぜかというと、Hugoは対象のテンプレートを特定の順番で走査する仕組みになっていて、 ページごとに細かくデザインを変えたり、全体で1つのテンプレートに統一したりと、融通が効くようになっている。

    listページを例に具体的な構造を示すとこうなる。

    /layouts/section/SECTION.html
    /layouts/_default/section.html
    /layouts/_default/list.html
    /themes/THEME/layouts/section/SECTION.html
    /themes/THEME/layouts/_default/section.html
    /themes/THEME/layouts/_default/list.html
    

    Hugo - Content List Template

    上から順に、最初に見つかったテンプレートを採用する。 つまり、最終的に/themes/THEME/layouts/_default/list.htmlまですべて見つからなければ、何も描画されない。

    オーバーライドの仕組みを理解する

    Hugoの最も便利なしくみの1つとして、テンプレートファイルは./themesとルート直下のものでそれぞれ用意できるような仕様がある。

    つまり、themesにおいたテーマファイルをユーザが独自にオーバーライドできるようになっている。

    先の例にあげたlistで言えば、/layouts/themesで同じファイルを走査しているのがわかる。

    /layouts/section/SECTION.html
    /layouts/_default/section.html
    /layouts/_default/list.html
    /themes/THEME/layouts/section/SECTION.html
    /themes/THEME/layouts/_default/section.html
    /themes/THEME/layouts/_default/list.html
    

    例えば、ユーザがあなたのテーマを使ってくれたとする。

    そのまま使えばあなたがデザインしたとおりに描画されるが、対象のテンプレートファイルと同じ名前をルートディレクトリに配置することで、 そのファイルだけを独自に変更することができる。

    ここで大事なのが、これはテンプレートファイルに限った話ではなく、CSSや画像などの静的ファイルにも同じことが言えるということ。

    これをうまく利用することで、ユーザが豊富なバリエーションで使うことができるテーマを作ることができる。

    何から手をつけるか

    ここまでで、Hugoのテーマづくりにおける基礎的な解説はできたと思う。

    しかし、実際に手をつけるとなるとどこから着手していいかわからなくなると思う。 というわけで、僕がテーマを作るときの作業順序をまとめてみる。

    ./layouts/index.htmlの削除

    Hugoは./layouts/index.htmlがなければ、./layouts/_default/list.htmlが採用される。

    僕は複雑な構造にするのが嫌なので、まず./layouts/index.htmlを消してしまう。

    トップとそれ以外の記事一覧で表示を変えたい場合は、if文と.IsHomeでなんとかする。

    トップと一覧が大幅に乖離する場合は、ちゃんと分けたほうがいい。

    ./layouts/_default/baseof.htmlの作成

    後述するblock templatesを使うため、baseof.htmlを作る。 ここでHTMLに必要な<!DOCTYPE html>metaタグの配置、ヘッダとフッタのマークアップとデザインをする。

    Hugo - Block Templates

    ./layouts/_default/list.htmlの作成

    次にlist.htmlを作る。 前述したとおり、Hugoは./layouts/index.htmlがなければ、./layouts/_default/list.htmlが採用される。

    このタイミングで、トップページのマークアップとデザインを終わらせる。

    ./layouts/_default/li.htmlの作成

    Hugoは断片的なパーツ、例えば一覧表示用の記事など、使い回しをする素材を切り出すことを推奨している。

    これはPageが持つ.Renderを使って実現する。

    Hugo - Hugo Template Functions

    ./layouts/_default/list.htmlには最新記事の一覧を表示する場合がほとんどであるため、 ./layouts/_default/list.htmlで作った記事1つ1つをli.htmlに切り出す。

    ./layouts/_default/single.htmlの作成

    次に記事詳細を作ってしまう。 これはここまでで説明したものとなんらかわりないので、特筆なし。

    ./layouts/_default/terms.htmlの作成

    次にtermsのページを作る。 このページは質素になりがちなので、デザインが難しい。

    ./layouts/404.htmlの作成

    わすれがちなのが404.html。 ちゃんと作っておかないと空白のページが作られる。

    気をつけたほうがいいこと

    ここまでで基本的なテーマは作れるまでの説明をした。 しかし、これだけだときっとハマるであろう落とし穴がいくつかある。

    相対パスは避ける

    画像や静的資材の参照には相対パスを使ってはいけない。これはHTML上にあらわれるあらゆるファイルすべてに言える。

    ユーザはドメインのルートであなたのテーマを使うとは限らない。 例えばthemes.gohugo.ioがいい例。

    Hugo Themes Site

    各テーマのデモはサブディレクトリで構成される。 つまり、相対パスを使ってしまうと参照が壊れてデザインが崩れる。

    {{ .Site.BaseURL}}を使って絶対参照で構築するべき。

    マジックナンバーは避ける

    よくあるのが、Taxonomyに使われる値をべた書きしてしまうものだ。 categoriestagsなど。

    これをマジックナンバーで書いてしまうと、ユーザが独自にTaxonomyを定義できない。 Hugoは.Site.Taxonomiesで定義されたTaxonomyをすべて取得できるので、できるだけ柔軟に使えるようにしよう。

    Hugo - Displaying Taxonomies

    Hugo側でcategoriestagsはデファクトみたいなスタンスを取っているので、それを理解した上でべた書きするのはありだと思う。

    必要最低限の機能を付ける

    サイトを作る上で、誰もが使いたいであろう機能がある。 そういった機能はできるだけつけたほうがいい。

    GoogleAnalytics

    代表的なものがGoogleAnalytics。 Hugoはconfig.tomlにGoogleAnalyticsのUserAgentを記述できるようになっている。

    googleAnalytics = "UA-123-45"
    

    この値が設定されていればGoogleAnalyticsのタグを配置するべきだし、 設定されてないなら出すべきじゃない。

    そういったときにはwithが便利にはたらく。

    Conditionals | Hugo - Go Template Primer

    GoogleAnalyticsのタグは_internal/google_analytics.htmlという内部テンプレートが用意されている。 自分で書くよりもこれを呼び出してしまえば、値の設定、未設定の判定もやってくれる。

    Hugo - Analytics in Hugo

    Disqus

    静的サイトのコメント機能でもっとも有名なDisqus。 これもGoogleAnalyticsと同様、変数名が決まっている。

    disqusShortname = "XYW"
    

    Hugo - Comments in Hugo

    DisqusもGoogleAnalytics同様、内部テンプレートが用意されているので、それを呼び出すのがいい。

    Generator

    HugoはHugoのコミュニティを成長させるために、metaタグにgeneratorを明記することを推奨している。

    1<meta name="generator" content="Hugo 0.17-DEV" />

    generatorは{{ .Hugo.Generator }}でタグとともに出力される。 ユーザに影響をあたえるものではないが、Hugoの発展のためにできるかぎり入れてあげたい。

    generator-meta-tag | Hugo - Creating a Theme

    Share button

    シェアボタンも、基本的にはどのサイトにも必要になる。 これは国によって使われるサービスやSNSが異なるため、可変にできるとなおいい。

    また、シェア自体いらないという場合もあるため、config.tomlで表示、非表示が切り替えられたり、 記事単位に.Paramsで切り替えられたりするといいと思う。

    よりよいテーマにするために

    ここまでで、必要最低限なものが揃ったテーマを作るための説明はできたと思う。 次は少し発展させて、ユーザが柔軟にテーマを使うための機能を作る。

    だいたいのWebサイトには、メニューが配置される。 HugoにはMenusという機能があり、これを使うこともできる。

    Hugo - Menus

    が、実際にこれを使ってMenuを形成している人は少なそうな印象。 そのため、config.tomlなどで設定できるようにしているテーマが多く見られる。

    自分がもっとも適切と思える形で実装し、READMEで設定の仕方を説明するといい。

    Shortcodesを用意する

    Wordpressやその他のブログシステムにはShortcodesというとてつもなく便利な機能がある。

    もちろんHugoにもあるので、基本的なものはテーマ側で用意しておくといい。

    Hugo - Shortcodes

    これはマークダウン内で定型的なHTMLを差し込むことができるもの。 例えば、画像やiframe、Twitterの埋め込みなどは、毎回HTMLを書くよりも手軽に呼び出せるようにしたかったりする。

    テーマが用意するShortcodesとしては、画像の埋め込みと右寄せ、左寄せくらいを提供できるといい。 機能を作ったら、README.mdに使い方を書いておく。

    custom.cssを作っておく

    ユーザが独自に、デザイン的なオーバーライドをしたい場合がある。

    styles.css1つ用意しておけば、ユーザがまるまるHugoのルートディレクトリにコピーしてオーバーライドすることもできるが、 全体をコピーしないといけなくなる。

    そういったときのため、custom.cssという空っぽのスタイルシートを用意して読み込ませておくといい。 ユーザは必要な場合のみcustom.cssを作ればいいし、そうでないなら空っぽのまま使われるだけ。

    テーマのバージョンアップをしたときも、影響を少なくすることができる。

    言語を超えて拡張性をもたせる

    Hugoは全世界のあらゆる国で使われている。 テーマもできるだけ、特定の国に依存しないようにする。

    DateFormatを可変にする

    まず言えるのが、日付の形式だ。

    日本は2016/01/01といった形式だが、世界では違う。 これはできるだけ、柔軟に変えられるといい。

    例えば、config.tomlに次のように設定できるようにする。

    [params]
      dateformat = "Jan 2, 2006" # Optional
    

    テンプレートファイルでは次のように描画する。

    {{ .Lastmod.Format ( .Site.Params.dateformat | default "Jan 2, 2006") }}
    

    フォントを可変にする

    英語圏ではHelveticaやArialなどでいいフォントも、国によってはそうとも言えない。

    これもDateFormatと同じように、config.tomlなどで設定できるといい。 custom.cssを用意する方法でも良いが、フォントだけのためにファイルを作らせるよりは、設定で変えられると親切だと思う。

    開発における発展的なノウハウ

    ここでは、テーマを作る上でのさらに発展的なノウハウについてまとめる。

    block templatesを使いたい

    すでに登場したが、Hugoはblock templatesという機能が使える。

    Hugo - Block Templates

    これはベースとなるテンプレートと子となるテンプレートを用意できる機能で、 HTMLの共通的な部分をベーステンプレートに切り出すことができる。

    しかし、このHugoのこの機能には現在バグがあり、時折Golangのエラーメッセージを吐いてクラッシュする。

    fatal error: concurrent map read and map write
    

    この記事を書いている今も、かなりの回数クラッシュしている。

    おそらく記事生成をgoroutineで回していて、その中でmapへの不適切なアクセスが走っているのだと思う。 そのうち直ると思っているが、気になる人はblock templates自体を使わないほうがいい。

    Concurrent map read and write error in template handling · Issue #2224 · spf13/hugo

    Issueもあがっているが、再現できていないらしい。

    AMP対応をしたい

    AMP対応は制約がいくつか付くだけなので、そこまで難しい実装ではない。

    おそらくいちばんこまるのがスタイルシートの導入だと思う。 僕は./layouts/partials/styles.cssにスタイルを配置して、次のようにしてインラインCSSとして描画している。

    <style amp-custom>
      {{ replaceRE " +" " " (replaceRE "\n" "" (partial "styles.css" .)) | safeCSS }}
    </style>
    

    themes.gohugo.ioに載せてもらう

    テーマを作ったら、Hugoのテーマポータルであるthemes.gohugo.ioに載せてもらうための手順を進める。

    Hugo Themes Site

    theme.tomlを書く

    まず、theme.tomlを書く必要がある。 これはhugo newで雛形が作られているので、それにそって書けばいい。

    基本的な説明は、Hugoのテーマ用レポジトリのREADMEにある。

    spf13/hugoThemes: All Themes Hugo

    スクリーンショットを撮る

    themes.gohugo.ioに表示されるサムネイルが2つの異なるサイズで必要になる。

    Thumbnail should be 900×600 in pixels

    Screenshot should be 1500×1000 in pixels

    Media must be located in:

    [ThemeDir]/images/screenshot.png

    [ThemeDir]/images/tn.png

    一番映えるページでスクリーンショットを撮り、配置する。

    README.mdを書く

    README.mdはユーザにもっとも近いドキュメントになる。 ここにはできるかぎり親切な情報を載せたほうがいい。

    例えば次のようなもの。

    • Screenshots
    • Features
    • config.toml example
    • frontmatter example
    • Shortcodes example

    必要であれば、画像も使って説明をする。

    GitHubにIssueをあげる

    ここまでできたら、自分のテーマレポジトリをHugoのテーマ用レポジトリにIssueとして連絡すればいい。

    spf13/hugoThemes: All Themes Hugo

    コントリビューターの方が確認して、修正すべきポイントがあればIssueをあげてくれるし、 問題なければ取り込んでCloseしてくれる。