SitespecでRackアプリから静的サイトをつくる

Sitespec というGemを利用して、Rackアプリから静的サイト用のファイル群を生成する方法について説明します。

Sitespecとは

Sitespecは、Rackアプリとrequest-specを利用して静的ファイルを生成するためのGemです。ここで言うrequest-specとは、RSpecでRackアプリに対してHTTPリクエストを送信し、アプリの振る舞いやレスポンスの内容を確かめるテストのことを指しています。Sitespecは、request-specで利用されたリクエストとレスポンスを元に、buildディレクトリの中に静的ファイルを生成します。GitHub Pagesなどを利用すれば、ここで生成された静的ファイル群をWebサイトとして公開できます。

よいところ

Rackアプリの知見や道具を再利用できる

Rackアプリを書けば静的サイトを構築できるため、これまで利用していたRackアプリを再利用できます。HerokuからGitHub Pagesに移行する場合などには、直接的に恩恵を受けられるでしょう。また、RackアプリのためのGemや知見を再利用できます。例えば、SinatraやPadrino、Railsなどを利用したことがある場合、それらのプラグインや開発ノウハウが活かせるでしょう。逆に言えば、静的サイトをつくるために新しいフレームワークを学習するコストを削減できると言えます。

動的サイトと静的サイトを両方生成できる

開発中はRackアプリとして動作させてローカルで利用することもできるので、ファイルの変更を自動で適用する機能など、開発用の便利ツールを簡単に利用できます。

テストとドキュメントの代わりになる

RSpecで記述したテストを通して静的ファイルを生成するため、実際に動作するテストが用意され、またそれらが簡単なドキュメントの代わりにもなります。テストが通ったエンドポイントのファイルだけが生成されるため、Travis CIなどのサービスでビルドすることにすると相性が良いと思います。

使い方

簡単な例でSitespecの使い方を説明します。

1. sitespecをGemfileに追加する

gem "sitespec"

2. テスト内で sitespec/rspec を読み込む

これを読み込むことで、特定の条件下でテストが成功したときに静的ファイルを生成するフックが設定されます。

require "sitespec/rspec"

3. :sitespec というメタデータを持ったテストケースを書く

Sitespecは、RSpecで :sitespec というメタデータを持ったテストケースを実行したときに静的ファイルを生成します。このメタデータが設定されたテストケース内では、rack-test が利用できるように設定されています。:sitespec を設定し、rack-testを利用して1つのテストケースで1つのリクエストを検証するようなテストを書くことが、Sitespecを利用する上で守らなければならない制約です。

describe "Sitespec" do
  let(:app) do
    MyRackApp
  end

  %w[
    /
    /2000/01/01/hello
    /stylesheets/all.css
  ].each do |path|
    describe "GET #{path}", :sitespec do
      it "returns 200" do
        expect(get(path).status).to eq 200
      end
    end
  end
end

4. テストを実行する

テストを実行すると、成功したテストケースに対してそれぞれ静的ファイルが生成されます。生成されるファイルは、build/index.html、build/2001/01/01/hello.html、build/stylesheets/all.css のように、URLのパスに応じた位置に配置されます。

$ bundle exec rspec --format documentation

Example application
  GET /
    returns 200
  GET /2000/01/01/hello
    returns 200
  GET /stylesheets/all.css
    returns 200

Sitespec generated 3 files into build directory.

Finished in 0.08302 seconds (files took 0.79161 seconds to load)
3 examples, 0 failures

細かい設定

Sitespec.configuration.build_path = "artifacts" のように設定すれば、出力先のディレクトリを変更できます。他に、Sitespec.configuration.auto_complete_html_path = false を設定すると、レスポンスヘッダのContent-Typeが text/html だった場合に .htmlindex.html を生成するファイル名に自動で付ける機能を無効化できます。他に、Sitespec.configuration.disable を呼ぶと、:sitespec のメタデータの有無に関係なく、Sitespecによるファイルの生成を無効化できます。環境変数の有無など、何かの条件に応じて動的に切り替えたい場合に有用でしょう。

GitHub Pagesで使う

GitHub Pagesと利用する方法について説明しておきます。r7kamura/r7kamura.com ではSitespecとTravis CIを利用してGitHub PagesでWebサイトを公開しているので、このレポジトリを直接参考にするのが手っ取り早いのですが、大事そうな点について補足しておきます。

GitHubのアクセストークンを利用した認証

r7kamura/r7kamura.comではビルドとデプロイのためにTravis CIを利用していますが、テストを実行して生成された静的ファイルをGitHubにgit-pushするには、少し工夫する必要があります。まず、Travis CIからGitHubのレポジトリにgit-pushするためには、GitHubに対して認証する必要があります。SSH経由での認証、あるいはHTTPSとGitHubのアクセストークンを利用した認証が利用できますが、後者の方法を取るのが手軽です。使い方としては、https://[email protected]/r7kamura/r7kamura.com.git のように、HTTPS用のGitレポジトリのURL内に、ユーザ名としてアクセストークンを埋め込む形になります。GitHubのアクセストークンは、https://github.com/settings/tokens/new から発行できます。

アクセストークンの暗号化

.travis.yml にアクセストークンを書き込むことになるため、暗号化して書き込んでおきましょう。travisというGemに付いてくる travis encrypt というコマンドを使えば、与えた値を暗号化しながら環境変数として利用できるように設定ファイルを更新してくれます。

$ gem install travis
$ travis init
$ travis encrypt GITHUB_ACCESS_TOKEN="..." --add

ブランチ

今回は、sourceブランチに静的ファイル生成用のソースコードを置き、masterブランチに生成した静的ファイルを置くようにしました。:user/:user.github.io というレポジトリでは、masterブランチがGitHub Pages用のブランチとして扱われるためです。GitHubの設定で、デフォルトブランチをsourceブランチに変更しておくと便利です。他のレポジトリでは、普段通りソースコードはmasterブランチに配置し、gh-pagesブランチを静的ファイル配置用のブランチとして運用することになります。

.travis.yml

Travis CI上でgit-commitを利用するには、Gitのcommiterとauthorの設定が必須ですが、これらは環境変数で指定できます。あとは、テストが全て成功したときにgit-pushを実行するようなスクリプトを追加すれば完成です。実際に利用している .travis.yml の内容がこれです。

language: ruby
env:
  global:
    - GIT_COMMITTER_NAME: "r7kamura"
    - GIT_COMMITTER_EMAIL: "[email protected]"
    - GIT_AUTHOR_NAME: "r7kamura"
    - GIT_AUTHOR_EMAIL: "[email protected]"
    - secure: "caVcOKNB8nhLbR25WFpzm9nOvWOPuLc+VF8OsdPrSrMEVvK5k2t4d/RGSzVzOnr54kQNJs5ttcaKVXwkyl1dnKrxzwxE7Z3QEF4XjqaOMuJeJ6qatNe4hco9w2DEqPzIfOzt/zjwPa58ZYfAgolP9kF5m5iwN/jaNYRjKt40dUI="
script:
    - bundle exec rspec
after_success:
    - cd build
    - echo "task :default" > Rakefile
    - git init
    - git add --all
    - git commit -m "Update from Travis CI"
    - 'git push --quiet https://[email protected]/r7kamura/r7kamura.com.git master -f 2> /dev/null'

branches:
  only:
    - source

背景

Sitespecは2年ほど前につくったものなんですが、今回Herokuの無料プランが変更になるということで、GitHub Pagesへの引っ越しのために利用しました。その際に、当時最新だったRSpec 2にしか対応していないなど幾つか不便な点があったので、改善を加え、ついでに説明記事を書いてみたという背景です。