Hugoをセットアップしてデプロイするまで[前編]Hugoのセットアップからビルドするまで

今回はWordPressからHugoへの乗り換えの一部始終を記載していきます。

ちょっと長くなったため、前編と後編に分割しています。前編はHugoでサイトを生成するまで、後編はデプロイを自動化するまで、の内容でお届けします。

またHugoのドキュメントを元に、動作した結果を記述しています。筆者はGo言語のお作法をよく理解していませんので、その辺りで間違いがありましたら優しくご指摘ただけると幸いです。

Hugoの準備

これは前回も記述していますが、サイトからプラットフォームに合わせたインストーラーをダウンロードし、ローカルで実行してください。

OSXをお使いでHomebrewがインストールされていれば、次のコマンドでもインストール可能です。

$ brew install hugo

私自身はHomebrewでインストールしたため、それを前提で進めます。

Homebrewについて

今回の内容とは直接関係ないのですが、「Homebrew便利だよ!」と言われるがままインストースしたのち、きちんとbrew updatebrew doctorしてますか?

brew updateはHomebrew本体やインストールしているパッケージをアップデートするのではなく、パッケージリストの更新を行います(パッケージのバージョンアップはbrew upgrade [PACKAGENAME]です)。アップデートしないままインストールした場合、古いものがインストールされたりするので、定期的なアップデートが必要なのです。リストは頻繁にアップデートされているため、前回の実行から24時間経っていると、メッセージが表示されます。

brew doctorは自己チェックを行い、問題があればその解決方法を提示してくれます。メッセージは英語で表示されますが、簡単な英語で例えば「次のコマンドを実行せよ」という感じで書かれていますので、落ち着いて読んでその通りに実行していけば、大概解決します。

このあたりを説明しだすときりがないので、詳しく知りたい方はこもりまさあきさんの電子書籍「Development Environments for Web Designers」をお勧めします。未完のまま随分アップデートされていませんが、このあたりの基礎は学べると思います。

WordPressのコンテンツをHugo向けに書き出し

プラグインを使ってWordPressから投稿やページを書き出します。

今回使ったプラグインは「WordPress to Hugo Exporter」です。プラグインディレクトリへ登録されていないため、自分でアップロードしてインストールしてください。

プラグインをインストールすると、「ツール」メニューの中に「Export to Hugo」という項目が表示されます。これをクリックすればすぐに処理が開始され、変換が終わるとZIPのダウンロードが始まります。プログレスバーなど粋なU.I.はありませんので、ご注意ください。

ダウンロードしたZIPを解凍すると、投稿は_postというディレクリへまとめられています。固定ページは個別に設定しているパーマリンク名のディレクトリへ格納されます。

画像類はwp-content/uploadsが丸ごとダウンロードされるようになっています。これ以外の領域にアップロードしている場合は、個別にダウンロードが必要です。

_config.ymlは、Hugoで使うサイトの設定ファイルです。ただ、記述されている内容が古い項目を採用しているため、これを使う場合は書き換えが必要です。

またテーマファイルに付随しているCSSやJavaScriptも含まれていませんので、必要であれば別途ダウンロードしておきましょう。

Hugoを使う

ここからHugoを使ってサイトを制作していきます。

Hugoのサイトも当然ながらHugoで作成されています。サイトのソースも公開されているので、ドキュメントを見ていて気になるところがあればチェックしてみましょう。

Hugoで新規サイトを生成する

次のコマンドを実行すると、指定したフォルダを初期化し、雛形となるフォルダとファイルが生成されます。

hugo new site path/to/directory

例えば、ホームディレクトリにある「hugo」ディレクトリ作成する場合は次のようになります。

$ hugo new site ~/hugo

必要なディレクトリと、サイト全体の設定ファイルであるconfig.tomlが生成されます。ディレクトリは、次の役割を担います。

archetypes
ターミナルからコマンドを用いて新しいドキュメントを作成するときに、テンプレートとするファイルを格納しておくディレクトリです。

content
実際のコンテンツを格納するディレクトリです。

data
TOML / YAML / JSON形式のファイルをデータストアして使うためのディレクトリです。今回は使っていないため、ドキュメントを参照してください。

layouts
コンテンツを挿入する、テンプレートファイルを格納するディレクトリです。

static
CSSやJavaScriptなど、HTMLで使うリソースデータを格納するディレクトリです。内包しているデータの構成は、プレビューで生成される次の「public」ディレクトリへそのままコピーされます。

public
初期化時には生成されませんが、プレビューやビルドをすると公開に必要なリソースが格納されるディレクトリです。ビルドした場合、基本的にこの中身をサーバの公開ディレクトリへアップロードすればOKです。

config.toml
サイトの設定ファイルです。次項で説明します。

config.toml

サイト名やベースURLなどの基本情報から、サイト全体で使うパラメータを設定するファイルです。雛形はTOML形式ですが、WordPress to Hugo Exporterで生成される通り、YAML形式でも記述できます。記述のフォーマットは「ドキュメント」ページの最下部にありますので、参照してください。

流用するデータを格納して、記述を修正する

ダウンロードしたMarkdownファイルは「content」ディレクトリへ、画像やCSS・JavaScriptは「static」ディレクトリへ入れてください。基本的にディレクトリ構造はそのままURLに使われます。

「static」へ入れたリソースも「public」へそのままコピーされますので、構成を変更したい場合は、「static」ディレクトリ内で変更しておきます。

リソース(特に画像)へのパスが絶対パスになっていますので、エディタの検索置換機能などで変更しておきましょう。 私の場合は、記事ごとに画像ディレクトリを作成することにしたため、次のようになりました。

元のパス
http://creative-tweet.net/blog/wp-content/uploads/2014/12/hoge.png

変更後のパス
/assets/img/2014/post-title/hoge.png

ちなみに検索置換だけでは変更しきれなかったため、記事の内容をリファインついでにパスを追記していきました。

記事の作成

Hugoの記事はMarkdown形式で記述し、「content」ディレクトリへ保存します。WordPressから書き出したファイルは、もちろんMarddown形式になっているのでHugoでそのまま使うことができます。

reStructuredText形式でも記述できるようです。

メタデータ

タイトルや概要などを、ファイルの先頭へメタデータとして記述します。記述の形式は3つあり、私はYAML形式を使用しています。

必須の項目は次の通りです。

  • title
  • description
  • date
  • taxonomies(categoriesまたはtagsもしくは両方)

書き出したMarkdownファイルを見ると、WordPressで投稿やページへ設定していたカテゴリーやタグなどもメタデータへ記載されていることがわかります。

以前に書いたこちらのページのメタデータは、次のように変換されていました。

---
title: Sketch 3でスライドづくりに役立つプラグインやTipsなど紹介しますよ。 Sketch 3 Advent Calendar 2014
author: littlebusters
layout: post
date: 2014-12-24
excerpt: Sketch 3 でプレゼンテーションスライドを作成するためのTipやプラグインを紹介しています。
url: /archives/1361
leadimage:
  - 1375
categories:
  - イベント
  - ツール
tags:
  - Advent Calenter 2014
  - Sketch
---

authorexcerptなどドキュメントにない項目も記述されていますが、独自メタデータとして設定することができるため、修正などせずこのまま使うことは可能です(独自メタデータについては、個別ページのテンプレート作成でもう少し詳しく解説します)。

ただ、必須のdescriptionがないため、excerptを変更こちらに変更しました。

dateはこのままでもよいのですが、時間とタイムゾーンを追加して次のような形式にしています。

2014-12-25T09:30:00+09:00

layouturlは制作の都合で、leadimageは不要なため削除しました。

ショートコード

ショートコードはコードを外部ファイル化し、記事内で使い回す仕組みです。引数を与え、ショートコード内で使うこともできるようです(今回は使っていませんが)。

例えば次のパラグラフはショートコードにしているものです。

Sketch 3の基本。というSketch 3の基本操作にフォーカスした電子書籍をリリースしました。詳しくはこちらの「Sketch 3の基本。」のページをご覧ください。

この内容をHTMLファイルとしてlayouts/shortcodesへ保存し、使う場合は次のように記述します。

{{% sketch3basics %}}

例では「%」が2バイト文字になっていますが、実際には1バイトを使ってください。

コンテンツの構造

Hugoでは、「content」ディレクトリ配下の構成そのままが「public」ディレクトリへ反映されます。

例えば前回の記事は、次のパスへ保存しています。

/content/blog/2015/10/good-bye-wordpress.md

これを公開すると、次のアドレスになります。

/blog/2015/10/good-bye-wordpress/

しかし、WordPressで運用していたものは.html付きのURLにしていたため、このままではURLが変更になってしまいます。そこで拡張子付きのアドレスになるよう、config.tomluglyURLs = trueという設定を追加し、次のようなアドレスで公開するようにしています。

/blog/2015/10/good-bye-wordpress.html

セクション

「content」ディレクトリ直下のディレクトリがセクションにあたり、上記の例では「blog」がそれに該当します。

基本的にセクションを含むURLになりますが、記事のメタデータへ次のいずれかを設定すると公開アドレスを変更できます。

  • slug
  • url

slugを設定した場合は、ファイル名を上書きします。

slug: /slug/string
-> /blog/2015/10/slug/string.html
(uglyURLs = falseであれば、/blog/2015/10/slug/string/となります)

urlは完全にURLを変更します。slugを設定していても、こちらが優先されます。

url: /url/string.html
-> /url/string.html

開発環境の生成

Hugoとは関係ありませんが、今回はサイトの見た目を変更するため、gulpで処理するためのディレクトリを作成しました。ルートになっているディレクトリへ「_dev」ディレクトリを作成し、gulpで処理するファイルを入れています。

SCSSやJavaScriptをはじめ、画像関連もMinifyをかけるために一度こちらへ格納するようにしています。

gulpで処理したファイルは、staticディレクトリへ出力するようにしています。

テンプレートの作成

コンテンツを元に、最終的なHTMLを生成するためのテンプレート、いわゆるテーマを作成します。

ファイルは、「layouts」ディレクトリへ格納します。最低限必要なファイルは、セクションやタクソノミーのページ一覧を作成するためのlist.htmlと、単一ページを生成するためのsingle.htmlです。これらのファイルはlayouts/_defaultへ格納します。

今回はHTMLを使って記述しましたが、Go言語のHTMLテンプレートエンジンであるAceAmberを使って、JadeやSlimのように記述することもできるようです。

ビルドインサーバの立ち上げ

まずはビルドインサーバを立ち上げ、ブラウザで確認しながら作成できるようにします。ターミナルで次のコマンドを実行しましょう。

$ hugo server --watch --buildDrafts=true -v

サーバーを立ち上げるだけであれば、hugo serverだけでOKです。ただ、保存時に自動的にリロードさせたいので--watchオプションをつけました。

また--buildDrafts=trueは、下書き状態の記事も一緒にレンダリングするオプションです。記事のメタデータにdraft: trueとある記事もレンダリングされるため、新しく作成する場合など確認しながら作成できます。デフォルトではfalseになっているため、万が一そのままデプロイしても公開されることはありません。

最後の-vオプションをつけておくと、ターミナルへデバッグ情報を書き出してくれるため、エラーの特定がしやすくなります。

既存のテーマを使う

ひとまずHugoを試してみたい場合は、テーマファイルを使うと良いかもしれません。

公開されているテーマからお好きなもの、またはテーマ一式git cloneなりダウンロードし、テーマ一式を「themes」ディレクトリへ格納します(ファイルダウンロードした場合、解凍後のフォルダ名を変更する必要があります)。例えば「steam」というテーマを使う場合は、config.tomlへ次のように設定します。

theme: steam

トップページ(ホームページ)の作成

「layouts」ディレクトリ直下にあるindex.htmlがホームのテンプレートとして使われます。

index.htmlが存在しなかった場合は、layouts/_default/list.htmlが、さらにlist.htmlも存在しなかった場合は、layouts/_default/single.htmlがテンプレートとして適用されます。

ここからは、私が作成したコードを見ていきます(コードは整形済み、実際のソース)。

ヘッダーとフッターは、後述のパーシャルテンプレートを使い、共通化したものを読み込んでいます。

{{ partial "head.html" . }}

続いて本文にあたる箇所ですが、今回作成したトップページでは、blogセクションから最新の3記事を取得するようにしています。セクション部分は次のコードになっています。

<section>
  <h2>blog</h2>
  {{ range first 3 ( where .Data.Pages "Section" "blog" ) }}
  <section>
    <time datetime="{{ .Date.Format "2006-01-02T03:04:05+09:00" }}">
      {{ .Date.Format "2006/1/2" }}
    </time>
    <h3>
      <a href="{{ .Permalink }}">{{ .Title }}</a>
    </h3>
  </section>
  {{ end }}
  <p>
    <a href="/blog.html">more blog →</a>
  </p>
</section>

3行目でセクションが「blog」に属する記事を最新から3件取得し、{{ end }}までの内容が繰り返されます。

{{ range }}内では記事のメタデータ(変数)を使うことができます。

まずtime要素のdatetime属性へ、記事の作成日をW3Cの仕様に則ったフォーマットで挿入しています。{{ .Date }}だけでも挿入できますが、.Dateに続けて.Formatとその形式を記述することで、メタデータの日付を指定した形式で挿入することができます。

それを利用し、time要素のテキストノードで同じメタデータを使い、フォーマットを変えて挿入するようにしました。

h3要素の子a要素では、パーマネントリンク{{ .Permalink }}を挿入し、そのテキストノードへ記事のタイトル{{ .Title }}を挿入しています。

この辺りは記述方法が違えど、CMSだろうとStatic Site Generatorだろうと同じような感じですね。

ちなみにセクションを限定して取得しているため、{{ range }}の部分がいきなり難しいことになっていますが、セクションに関わらず最新の10件を取得するには、次のように記述すればOKです。

{{ range first 10 .Data.Pages }}
  // code here
{{ end }}

toolsセクションはblogセクションとほぼ同じため、解説は省略します。また、writtingセクションは変更することがほとんどないためベタ書きです。

セクション

セクションごとにテンプレートを変更することができます。「layouts」ディレクトリへ「section」ディレクトリを作成し、その中へセクション名のHTMLファイルを作成します。「blog」セクションであれば、layouts/section/blog.htmlです。

セクションのテンプレートは、次の優先順位で適用されます。テーマのほうが後に適用されるため、テーマを使う際に残骸が残っているとうまく適用できないため注意してください。

  1. /layouts/section/SECTION.html
  2. /layouts/_default/section.html
  3. /layouts/_default/list.html
  4. /themes/THEME/layouts/section/SECTION.html
  5. /themes/THEME/layouts/_default/section.html
  6. /themes/THEME/layouts/_default/list.html

タクソノミーもテンプレートを個別に変更することができます。今回は使っていないのでタクソノミーのドキュメントを参照してください。

続いてblogセクションで使っているコードの一部です(実際のソース)。

<h1 class="icon-{{ lower .Title }}">{{ lower .Title }}</h1>
<div>
  <main>
  {{ range .Data.Pages.GroupByDate "2006" }}
  <section>
    <h2>{{ .Key }}</h2>
    <ul>
      {{ range .Pages }}
        <li>
          <time datetime="{{ .Date.Format "2006-01-02T03:04:05+09:00" }}">
            {{ .Date.Format "2006年1月2日" }}
          </time>
          <h3>
            <a href="{{ .Permalink }}">
              {{ .Title }}
            </a>
          </h3>
        </li>
      {{ end }}
    </ul>
  </section>
  {{ end }}
  </main>
</div>

1行目の{{ lower .Title }}は、記事のタイトルではなくセクション名を取得しています。

変数は大まかにPage / Page Params / Site / Node / Hugoの5つに分類され、自動的に生成されるセクションやタクソノミーなどの一覧ページでは、Nodeの変数が利用できます。といっても、変数名が同じなので、難しいことをしない限りはあまり意識しなくてもよいでしょう。

またlowerというのが.Titleの前についてますが、これは文字列をスモースキャプスしてくれるものです。

main要素下のrangeでは、年を基準にグループ化してページを読み込んでいます。グループ化の基準は他にもありますので、詳しくはドキュメントを参照してください。

h2要素で{{ .key }}を挿入していますが、グループ化の基準に応じて挿入されます。今回は年を基準にしているため、西暦が挿入されています。

グループ化してページを読み込んだ場合、さらに{{ range .Pages }}を使ってページを個別に読み込む必要がありますので、ul要素下で実行しています。内容自体はトップページで作成したものとほぼ同じため、解説は省略します。

ちなみに{{ .Summary }}を使えば、自動で先頭から70文字または記事内の<!--more-->より前を出力できます。

個別ページ

個別ページでは、セクション単位での変更や、個別にテンプレートを指定することもできます。

「layouts」ディレクトリ直下にセクション名のディレクトリを作成し、single.htmlを格納します。「blog」セクションであればlayouts/blog/single.htmlです。

  1. /layouts/TYPE-or-SECTION/LAYOUT.html
  2. /layouts/TYPE-or-SECTION/single.html
  3. /layouts/_default/single.html
  4. /themes/THEME/layouts/TYPE-or-SECTION/LAYOUT.html
  5. /themes/THEME/layouts/TYPE-or-SECTION/single.html
  6. /themes/THEME/layouts/_default/single.html

1・2で「TYPE-orSECTION」とありますが、任意のディレクトリを「type」としてセクションの代わりに使うことができるようです(今回は使っていません)。

また1の「LAYOUT.html」については、「section「または「type」ディレクトリへsingle.html以外の名前で作成したテンプレートを指します。

個別にテンプレートを適用できる仕組みで、例えば「blog」セクションで、一部だけを「pickup.html(layouts/blog/pickup.html)」というテンプレートを適用するには、「blog」セクションに属するコンテンツのメタデータへ次のように記述します。

layout: pickup

このサイトでは「blog」「tools」「sketch-3-basics」という3つのセクションがあり、「tools」「sketch-3-basics」へセクションテンプレートを適用、さらに「tools」内でFireworks拡張機能のページは個別にテンプレートを用意しました。

続いてblogセクションの個別ページのソースを見ていきましょう。まずはヘッダー部分のソースです(実際のソース)。

<header>
  <section>
    <time datetime="{{ .Date.Format "2006-01-02T03:04:05+09:00" }}">
      {{ .Date.Format "2006/1/2" }}
    </time>
    <h1>{{ .Title }}</h1>
    {{ if isset .Params "hero"}}
    <img src="{{ .Params.hero }}" alt="">
    {{ end }}
  </section>
</header>

h1要素の下にある{{ if isset .Params "hero" }}は、.Param(独自メタデータ)へheroが設定されているかどうかを判定しています。設定されていれば、img要素のsrc属性へ{{ .Params.hero }}(画像までのパス)を出力するようにしています。

独自メタデータについては、記事(Markdownファイル)の先頭へ次のように記述します。

hero: "/file/to/path/filename.png"

独自メタデータは.Param.variablenameという形式で、.Paramsに続けて設定したメタデータを記述します。

またメタデータ名に記号(「-」「_」)は使えませんので、注意してください。

そして、記事の本文を読み込むには次のコードを記述します。

{{ .Content }}

続けて記事下の前後記事へのリンク部分です。

<ul>
  {{ if .NextInSection }}
  <li>
    <a href="{{ .NextInSection.Permalink }}">
      <span>newer: </span>
      {{ .NextInSection.LinkTitle }}
    </a>
  </li>
  {{ end }}
  {{ if .PrevInSection }}
  <li>
    <a href="{{ .PrevInSection.Permalink }}">
      <span>older: </span>
      {{ .PrevInSection.LinkTitle }}
    </a>
  </li>
  {{ end }}
</ul>

{{ if .NextInSection }}{{ if .PrevInSection }}で前後に記事があるかどうかをチェックし、記事があればリンクを作成するようにしています。

{{ .NextInSection.Permalink }}はパーマネントリンクの取得、{{ .NextInSection.LinkTitle }}は記事タイトルの取得です。

パーシャルテンプレート

ヘッダーやフッターなど、共通パートはパーシャルテンプレートとして分割しておくと使い回しが可能です。

パーシャルテンプレートはlayouts/partialsディレクトリへ保存します。パーシャルテンプレート内でも変数を使うことができます。

layouts/partials/head.htmlというパーシャルテンプレートをテーマ本体へ読み込むには、内容を展開したい箇所へ次のコードを記述します。

{{ partial "head.html" . }}

最後の「.」はパーシャルテンプレート内で変数を使うために必要です(いわゆる引数みたいなもの?)。

続いてパーシャルテンプレート化したhead要素のソースの一部を解説します。(実際のソース)。

<title>
{{ $isHomePage := eq .Title .Site.Title }}
{{ .Title }}{{ if eq $isHomePage false }} | {{ .Site.Title }}{{ end }}
</title>

title要素では、現在のページタイトルとconfig.tomlで設定しているtitleを比較、すなわちトップページ(ホームページ)かどうかを判定し、その真偽値を変数$isHomePageへ入れています。

次の行の{{ if eq $isHomePage false }}で、トップページでなければ記事のタイトルに続けて、「 | creative tweet.」と出力するようにしています。

次はOPGの種類を出力する部分です。

{{ $.Scratch.Set "ogpType" "article" }}

{{ if eq .Title "Blogs" }}
  {{ $.Scratch.Set "ogpType" "blog" }}
{{ end }}

<meta property="og:type" content="{{ $.Scratch.Get "ogpType" }}" />

{{ $.Scratch.Set "ogpType" "article" }}では、キーバリューストアを用いてテンプレート内での変数を設定しています。

変数を設定するには{{ $.Scratch.Set "キー名称" "値" }}で、値を読み出すには{{ $.Scratch.Get "キー名称" }}とします。

続いてのif文で、blogセクションの一覧かどうかを判定し、必要に応じて書き換えを行なっています。そして最終的に$.Scratch.Getで値を読み出しています。


ひとまずここまでで、サーバーへ公開できるデータを生成することができます。「public」ディレクトリがあればそれを削除して、次のコマンドを実行します(--baseUrlの中身は適宜書き換えてください)。

$ hugo --baseUrl="http://creative-tweet.net/"

おそらく一瞬で生成されると思います。生成された「public」ディレクトリの中身を公開ディレクトリへアップすれば、ひとまずHugoでのサイト制作は完了です。

後編は「CIサービスでビルドからデプロイを自動化するまで」です。

tags