JekyllでHTMLを動的生成し、Webページの更新を効率化する|技術情報

JekyllでHTMLを動的生成し、Webページの更新を効率化する

IPLのホームページを内部的にリニューアルしました。 レイアウト崩れのテストからJenkinsによる自動更新までを扱います。

Jekyll

久々の技術記事更新です。

IPLのホームページも、前回リニューアル時よりかなり時間が経過し、メンテナンスが難しくなってきました。 HTMLを手書きしていると、どうしてもコピーペーストで作成する部分が発生してしまい、 デザインやサイト構成に手を入れることが難しくなってきます。

そういった時に役立つのが、**「静的サイトジェネレータ」(Static Site Generator)**です。 これは、PHPのようにHTMLをスクリプトによって生成し、生成後のHTMLファイルをサーバにアップロードすることで、 部品やレイアウトの共通化を図ることが出来るツールです。

静的サイトジェネレータの利点

よく使われる静的サイトジェネレータに、下記のものがあります。

この手のツールにも流行り廃りがありますが、 現状最も古く、多く使われているのはJekyllでしょう。

他にも多数の静的サイトジェネレータが存在します。主なものは下記のサイトにまとめられています。

StaticGen

今回は、デファクトということでJekyllを採用します。 ただ、Ruby製のツールはWindowsでの動作に問題が多い(Windowsのファイルシステム上でテストされていない、ネイティブモジュールを使っている等)ため、 Windows上での開発が見込まれる場合は、hexoの方が適しているかもしれません。

自動テスト

まず、既存のサイトの振る舞いを破壊しないように、テストを整備する必要があります。 しかし、HTMLは一般的なプログラムと異なり、記述ミスによるエラーはほとんど出ませんし、レイアウト崩れなどは目視によってしか確認が出来ません。

そこで、自動でスクリーンショットを撮影し、gifを作成するようにしておくと、 レイアウト崩れやカラム落ちなどのミスに気づくことが出来ます。 手動でサイト内をくまなく見て回る必要もありません。

この手順は、リファクタリング前に行なうことに意義がありますので、最初に着手することをおすすめします。

テストには、Seleniumベースの自動テストツール「webdriverio」を使うのがおすすめです。

WebDriverIOのインストール

まず最新のChromeとNode.jsが入っていることを確認してください。 次に、下記コマンドを実行して、Selenium Serverのスタンドアロン版とWebDriverIOをインストールします。

# Seleniumのインストール
npm install selenium-standalone@latest -g
selenium-standalone install

# WebDriverIOのインストール
npm install webdriverio

自動スクリーンショット

下記のコードは、サイトのスクリーンショットを取って回るサンプルコードです。

var selenium = require('selenium-standalone');
selenium.start({spawnOptions: {stdio: 'inherit'}}, function(err, child) {
    if(err){
        console.log(err);
        return;
    }
    console.log("selenium-standalone started");
});

var cnt = 0;
function s(){
    function zeroPadding(number, length){
        return (Array(length).join('0') + number).slice(-length);
    }
    return "test/" + zeroPadding(cnt++, 3) + ".png"
}


var io = require('webdriverio');
var options = { desiredCapabilities: { browserName: 'chrome' } };

var prefix = "http://localhost:4000/";

client = io.remote(options);
client.init()
    .setViewportSize({
        width: 1024,
        height: 1024
    })
    .url(prefix + "/")
    .saveScreenshot(s())
    .url(prefix + "about/")
    .saveScreenshot(s())
    .url(prefix + "sitemap.html")
    .saveScreenshot(s())
    .end();

client.end();

以上のコードを、test/e2e.jsとして保存し、下記のように実行すると、gifが生成されます。 (別途imagemagickが必要です)

node test/e2e.js && convert -delay 50 -loop 0 test/*.png test/e2e.gif && rm test/*.png

生成されたgifのファイル名にタイムスタンプを追加して保管しておくと、どのタイミングでレイアウトが崩れたのか追うことが出来ます。

Jekyllプロジェクトの基本構成

Jekyllのファイル構成は、基本的に自由に構成することができます。デフォルトでは下記のような構成になります。

ファイル構成

jekyll buildを実行すると、サイト全体が_sites内に出力されます。また、サイトのタイトル等の設定は_config.ymlに記述します。

また、上記以外のファイルを配置すると、単に_sites以下にファイルがコピーされます。 画像やCSS、Javascriptファイルは適当なフォルダを作成して格納しておけばOKです。

インストール方法、設定手順などは、日本語公式ドキュメントをご参照下さい。

Front-matter

Jekyllの基本的な動作は、ファイルをそのまま_sitesにコピーするというものですが、 下記のような記法がファイル先頭にある場合は、特別なファイルとして処理を行います。

---
layout: post
title: Blogging Like a Hacker
---

ここでは変数定義や、使用するレイアウト(後述)の指定を行なうことが出来ます。

部品の切り出し

まず、ソースの総量を減らし、共通部品を洗い出すために、ヘッダ、フッタ、サイドバーなどを_includesに切り出します。 これにより、共通化が容易な部分と、再利用の難しいコンテンツの切り分けも出来ますので、最初に実施することをおすすめします。

読み込み側では、下記のように記述します。

{% raw  %}
{% include footer.html %}
{% endraw %}

レイアウトの作成

_layoutsには、コンテンツの外側部分を定義することが出来ます。 _includesに似ていますが、入れ子構造に出来るのが特徴です。

layoutsを利用した共通化はスマートですが、やり過ぎると、オブジェクト指向における継承のように、柔軟性が失われてしまいます。 まず構造を把握しやすくする意味で、includesでの切り出しを先に行った方がいいでしょう。

プレビュー

jekyll serveでプレビュー用のサーバが起動します。

ファイルが更新されるたびにブラウザのリロードを行なうのが面倒に感じられるのであれば、wtchを使うと、リロードの自動化を行なうことが出来ます。

ビルド

jekyll buildを実行すると、_site下にhtmlファイルが生成されます。 Gitでバージョン管理を行なう場合、.gitignore_siteを指定しておくべきでしょう。

また、ビルドし忘れを防ぐために、自動ビルドの設定をすることをおすすめします。 弊社では、サイト構成ファイルが更新されると、Jenkinsサーバに通知され、 自動でビルドが走り、アップロードまで完了するようになっています。

また、自動ビルドの設定を行っておくと、環境構築をしていないユーザでも サイトの更新が可能になります。

おわりに

Webの管理をこれで省力化できました。 Jekyllはどのような構成のWebサイトでも生成することができるので、HTMLマニュアルの作成にも知見を活かすことが出来ると考えています。

副次効果として、HTMLではなくMarkDown記法で記事を書くことも可能になりました。 これで、更新が楽になればいいなと思っています。