[ Middleman で超速プロトタイピング ] #02 Middleman の便利機能 7 選

2013.08.12

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Middleman logo

そんな訳で、前回は Middleman のインストールから基本的な機能まで学習しました。今回はより本格的に使いこなすために、Middleman の持つ膨大な機能の中から僕が比較的多用している機能を7つばかり紹介していくとします。Middleman って何?という方は、先に以下の記事をご参照ください。

テンプレート - レイアウトを無効にする

すべてのテンプレートファイルは何らかのレイアウトに囲われた状態で出力されますが、時にレイアウトを全く使用したくないという状況もあるでしょう。具体例として、モーダルダイアログ内に読み込む外部ファイルなどが考えられます。そんな時はテンプレートファイルがレイアウトに囲われないように無効化する訳ですが、以下の二種類の方法でこれを解決することが出来ます。

Frontmatter で指定

---
layout: false
---

こうすることで、対象のテンプレートファイルはどのレイアウトにも囲われずに出力されます。指定するテンプレートファイル数が少ない場合はこの方法で充分でしょう。

config.rb で指定

page 'hoge/*', layout: false

任意のディレクトリ以下にあるテンプレートファイル全てに対してレイアウト無効としたい場合は、上記の例のように config.rb で指定するのが良いでしょう。上記の例では hoge ディレクトリ以下にある全てのテンプレートファイルにレイアウト無効が適用されます。

ローカルデータ - データの外部ファイル化

テンプレートファイルからコンテンツデータを外部ファイルとして外出しにしておくことで、後々の修正やテンプレートファイルの使い回しなど何かと利便性が向上します。ここではユーザー管理ツールを例にとってみます。よくあるタイプとしてユーザー一覧ユーザー新規登録ユーザー編集といった三画面の構成が考えられるでしょう。この場合、新規登録画面と編集画面の入力フォームは同じであることが多いため、フォーム部分をパーシャル化してユーザー情報をローカルデータとして外部ファイル化しておくと、コードの重複を抑えることが出来て非常に効率的です。

まずはテンプレートファイルです。新規登録画面に register.html.haml、編集画面に edit.html.haml、フォーム部分の _form.hamlの3ファイルを用意します。

register.html.haml

%h1 新規登録
= partial 'form'

edit.html.haml

%h1 編集
= partial 'form'

_form.haml

- form_tag '#', method: 'post', class: 'form-horizontal' do
	- field_set_tag do
		.control-group
			= label_tag 'Name', class: 'control-label'
			.controls
				= text_field_tag 'name', id: 'name'
		.control-group
			= label_tag 'email', class: 'control-label'
			.controls
				= email_field_tag 'email', id: 'email'
		.control-group
			= label_tag 'password', class: 'control-label'
			.controls
				= password_field_tag 'password', id: 'password'
		.control-group
			= label_tag 'Retype-Password', class: 'control-label'
			.controls
				= password_field_tag 'retype', id: 'retype'
	.form-actions
		= submit_tag 'Submit', class: 'btn'

基本的に新規登録画面は各フォームインプットが空欄なのに対して、編集画面は既存データがインプットに初期値として入力されています。次にその既存ユーザーデータをローカルデータに定義します。ローカルデータは source ディレクトリと並ぶ data ディレクトリ内に配置します。ここに .json もしくは .yml 形式で作成するわけですが、今回は .json 形式で作成することにします。

try_middleman
├─ data
│    member.json
│
└─source
    │  _form.haml
    │  edit.html.haml
    └─ register.html.haml

/data/member.json

{
	"wakamsha": {
		"name": "wakamsha",
		"email": "wakamsha@example.com",
		"password": "foowakamsha"
	}
}

簡単なユーザー情報を定義しました。ここで定義されたローカルデータはテンプレートファイル、レイアウトファイル、config.rb のどこからでも参照することが出来ます。参照式は以下のとおりです。

data.member.wakamsha    # data.{ファイル名}.{要素名}

これで材料は全て揃いました。_form.haml がパーシャルとして読み込まれる際に has_value というフラグを受け取り、true ならローカルデータを初期値として各フォームアイテムに入力し、false なら入力しないというコードを追記します。

register.html.haml

%h1 新規登録
= partial 'form', locals: {has_value: false}

edit.html.haml

%h1 編集
= partial 'form', locals: {has_value: true}

_form.haml

- member = data.member.wakamsha

- form_tag '#', method: 'post', class: 'form-horizontal' do
	- field_set_tag do
		.control-group
			= label_tag 'Name', class: 'control-label'
			.controls
				= text_field_tag 'name', id: 'name', value: (has_value)? member[:name] : ''
		.control-group
			= label_tag 'email', class: 'control-label'
			.controls
				= email_field_tag 'email', id: 'email', value: (has_value)? member[:email] : ''
		.control-group
			= label_tag 'password', class: 'control-label'
			.controls
				= password_field_tag 'password', id: 'password', value: (has_value)? member[:password] : ''
		.control-group
			= label_tag 'Retype-Password', class: 'control-label'
			.controls
				= password_field_tag 'retype', id: 'retype', value: (has_value)? member[:password] : ''
	.form-actions
		= submit_tag 'Submit', class: 'btn'

これで完成です。共通のパーシャルファイルを使用していますが、新規登録画面ではフォームアイテムには何も入力されておらず、編集画面では初期値としてユーザー情報が入力されるようになりました。

動的ページを作る

例えばブログサイトのように表示するコンテンツが違うだけでページデザインは全く同じというケースの場合、ページの数だけテンプレートファイルを作成するのはスマートではありません。単一のテンプレートで複数のコンテンツページが出力できれば、管理も非常に楽になります。

以下に簡単な例を紹介します。はじめに動的ページの元となるテンプレートを作成します。

/stones/template.haml

%h1 Rolling Stones
%p= name

メンバーの名前を出力するだけの簡単なテンプレートを用意します。2行目に name という変数を定義し、ここに呼び出し元から値が渡ってきてブラウザ上に出力される訳です。

次に呼び出し元ですが、config.rbproxy メソッドを呼び出し、作成(※出力)したいパスと、使用するテンプレートファイルのパスを指定します。そして最後にテンプレートファイルに渡すパラメータを記述すれば、単一のテンプレートファイルで複数の Web ページを出力することが出来るというわけです。

['Mick', 'Kieth', 'Ronnie', 'Charlie'].each do |name|
    proxy "/stones/#{name.downcase}".html, "/stones/template.html", locals: {name: name}
end

するとプロジェクトがビルドされる度に以下のように各 Web ページが出力されます。

/stones/mick.html

<h1>Rolling Stones</h1>
<p>Mick</p>

/stones/kieth.html

<h1>Rolling Stones</h1>
<p>Kieth</p>

/stones/ronnie.html

<h1>Rolling Stones</h1>
<p>Ronnie</p>

/stones/charlie.html

<h1>Rolling Stones</h1>
<p>Charlie</p>

ローカルデータを活用することでより多くのパラメータを一括で渡す

先のサンプルでは簡単な配列データを元にしてテンプレートに一つの値だけを渡しただけですが、ローカルデータを使うことでより多くのデータをテンプレートに一括で渡すことが出来る上、ソースコードもスッキリと整頓することが出来ます。

data/member.json

{
    "stones": [
        {"name": "Mick",    "last_name": "Jagger",   "birth": "1943-07-26", "part": "Vocal"},
        {"name": "Kieth",   "last_name": "Richards", "birth": "1943-12-18", "part": "Guitar"},
        {"name": "Ronnie",  "last_name": "Watts",    "birth": "1947-06-01", "part": "Guitar"},
        {"name": "Charlie", "last_name": "Wood",     "birth": "1942-06-02", "part": "Drums"}
    ]
}

stones メンバーの生年月日と担当パートを含めたデータを作成しました。

config.rb

data.member.stones.each do |member|
  proxy "/stones/#{member[:name].downcase}.html", "/stones/template.html", locals: {member: member}
end

配列 stones をループ処理にかけます。配列内の個々のメンバーのオブジェクトは member という変数に格納され、そいつを template.html.haml にパラメータとして渡します。

/stones/template.html.haml

%h1 Rolling Stones

%dl
    %dt Name
    %dd= "#{member[:name]} #{member[:last_name]}"
    %dt Birth
    %dd= member[:birth]
    %dt Part
    %dd= member[:part]

受け取ったパラメータを出力します。出力結果は以下のようになります。

/stones/mick.html

<h1>Rolling Stones</h1>
<dl>
    <dt>Name</dt>
    <dd>Mick Jagger</dd>
    <dt>Birth</dt>
    <dd>1943-07-26</dd>
    <dt>Part</dt>
    <dd>Vocal</dd>
</dl>

テンプレートファイルそのものは出力させない

上記のままでは Stones のメンバーページだけでなく、その元になったテンプレートファイルまでも template.html として出力されることになります。それで問題ないというならともかく、多くの場合は不要かと思います。その際は config.rb に記述したproxy メソッドを以下のように書き換えることで解決出来ます。

data.member.stones.each do |member|
  proxy "/stones/#{member[:name].downcase}.html", "/stones/template.html", locals: {member: member}, ignore: true
end

パラメータに ignore: true を新たに追加しました。こうすることで /stones/mick.htmlstones/kieth.htmlstones/ronnie.htmlstones/charlie.html だけが出力されるようになります。

LiveReload ※Mac のみ(?)

Middleman をインストールすると em-websocket という gem も同時にインストールされます。また middleman-livereload という gem も併せてインストールされ、これによってプロジェクト内のファイルを編集するたびに Web ブラウザを自動的にリロードされるようになります。地味な機能ですが、あるとそれなりに恩恵を感じる優良な機能といえます。

LiveReload 機能はデフォルトでは OFF になっており、config.rb にある以下のコードをコメントインして有効化することができます。

activate :livereload

LiveReload 機能は Mac 環境でのみ動作を確認出来ました。Windows や Linux 環境においては、Chrome や Firefox にLiveReloadプラグインをインストールすることで動作するらしいですが *1、僕の環境では動作させることができませんでした。

(2014年5月28日追記) コメントにて打開策をご紹介いただきました。
Windowsやリモート環境でMiddlemanのLiveReloadをする - Qiita

サイトマップ

プロトタイピングで作られた静的サイトというのはあくまでモックアップという用途で使われるため、正当な手順を踏まなくても URL をダイレクトに指定すれば全ての画面に進めることが求められます(※いきなり登録完了画面を表示させたいだとか)。とはいえ、その都度 URL をて入力するのは何かと手間なので画面一覧となるサイトマップが欲しいところですが、そのための HTML を自作するのはあまりにも手間です。Middleman は自動的にサイトマップを生成し、Web ブラウザからhttp://0.0.0.0:4567/__middleman/sitemap/と URL 指定すれば以下のようなサイトマップを表示させることができます。

img-middleman-sitemap

各ページへのリンクはもちろん、どういった構造になっているのかもここで確認することができます。

ディレクトリインデックス

プロトタイピングという用途においては全く意味のない機能ですが、知識として抑えておくとします。例として以下の様な Middleman ソースファイルがあったとします。

try_middleman
│
└─source
    ├─stones
    │      mick.html.haml
    │      kieth.html.haml
    │      ronnie.html.haml
    │      charlie.html.haml

これらをビルドすると以下のような構成になります。

try_middleman
│
└─build
    ├─stones
    │      mick.html
    │      kieth.html
    │      ronnie.html
    │      charlie.html

当然ですがこのように静的な HTML ファイルとなるため、ブラウザから見た URL はhttp://try_middleman/stones/mick.htmlとなります。何一つおかしいところは無いのですが、ファイル拡張子を表示せずhttp://try_middleman/stones/mick/と、あたかも Web アプリケーションのような URL になるようにビルドすることが出来ます。方法は至って簡単で、config.rbに以下のコードを追記して Middleman を再起動すれば OK です。

activate :directory_indexes

タネを明かすとなんてことはなく、Middleman はビルド時に .html ごとにフォルダを作成してそのフォルダのindex.htmlとして各テンプレートファイルを出力します。つまりhttp://try_middleman/stones/mick.htmlとビルドされるところをhttp://try_middleman/stones/mick/index.htmlとビルドされているだけのことです。ちなみに元からindex.htmlとしてビルドされるテンプレートに関してはそのまま無視されます。また、特定のテンプレートだけディレクトリインデックス形式でビルドしたくない場合は、config.rbで以下のように指定すれば OK です。

page "/stones/brian.html", direcroty_index: false

もしくはテンプレートファイル毎に Frontmatter でdirectory_index: falseと指定することも出来ます。

アセットパイプライン

アセットパイプライン(Assets Pipeline)とは、複数ある CSS や JavaScript、画像といった HTML に紐づく細々としたファイル群(アセット)を一つのファイルとして連結したり、コードの文法上不要なインデントや改行を全て取り払ってファイルサイズを圧縮する機能のことです。HTTP リクエスト数が削減できたり圧縮することでファイルサイズを小さくするなど、Web サイト高速化のテクニックとして知られています。Ruby on Rails に実装されている機能ですが、Middleman にも実装されています。

プロトタイピングで高速化を意識する必要性は無いと思いますが、サーバーサイドを絡めた本実装では当然取り入れるべきなので、CSS や JS のコーディングといった用途に特化して使い続けるのもアリでしょう。

ファイルの連結機能

Middleman には Sprockets という Rails 向けの gem が最初から組み込まれているので、最初からこの機能を使うことが出来ます。サンプルとして以下の様な JavaScript があったとします。

application.js

$(function() {
    $(window).on('load', function() {
        $('#container').pinterestGrid({
            offsetX: 8,
            offsetY: 8,
            gridElement: '.grid'
        });
    });
});

見ての通り jQuery を使った JavaScript コードです。当然動作させるには、このコードよりも前に jQuery, pinterestGrid というプラグインファイルが読み込まれている必要があります。通常ならば合計3つのファイルをそれぞれ HTML 側で読み込むわけですが、以下のように追記することで jQuerypinterestGrid プラグインを application.js に連結してしまうことが出来ます。

//= require "jquery"
//= require "jquery.pinterestGrid"

$(function() {
    $(window).on('load', function() {
        $('#container').pinterestGrid({
            offsetX: 8,
            offsetY: 8,
            gridElement: '.grid'
        });
    });
});

※各ファイルは javascripts ディレクトリに配置されているものとする

javascripts
│  jquery.js
│  jquery.pinterestGrid.js
│  application.js

こうすることでapplication.js依存関係にある2つのJSファイルを連結して一つのファイルになります。たったこれだけのコードを書くだけでファイル連結が実現できてしまうのだから、この機能に関しては本実装でも活用して行きたいところです。

また、この機能は JavaScript だけでなく CSS ファイルに対しても使うことができます。

/*
 *= require normalize
 */
 
body {
    color: #333;
 }

CSS においても使用方法は同じです。上記のように連結したいファイル名をコメントとして指定するだけです。ピュア CSS の場合はこの方法を使いますが、SCSS(Sass) においては@import という機能が SCSS にはあるので、そちらを使う方がベターです。

require メソッドは必ずソースコードの一番最初に記述する必要があります。二行目以降に記述すると、単なるコメント行として処理されてしまいます。

Bootstrap を取り込んでみる

普通に Bootstrap 公式サイトからダウンロードしてくればそこで話は終わりなのですが、ここではあえて少しだけ捻くれた方法で取り込んでみます。Bootstrap は元々 LESS という拡張メタ言語で開発されており、GitHub 上で公開されているプロジェクトにあるファイルも当然それになりますが、Ruby on Rails 開発者向けにソースコードを SCSS(Sass) に書きなおされているモノが存在します。

gem としても公開されているので、こいつを Middleman プロジェクトに取り込んで使ってみるとします。最初に Gemfile に以下のコードを追記します。Gemfile はMiddleman プロジェクト作成時に既に作られているはずなので、それを利用すれば OK です。

gem "bootstrap-sass", require: false

オプションとして require: false を指定していますが、これは gem が Rails で使うことを前提として作られているためです。この gem はデフォルトで Rails や Compass といったモジュールにフックしようとしますが、Middleman でそれをやられるとエラーが発生してしまうので予め OFF にしておくというわけです。

追記して 保存 したら、ターミナルを起動して以下のコマンドを実行します。bundler を使って Middleman をインストール、プロジェクト作成した場合は5行目のコマンドを実行してください。

# グローバルレベルにインストール
bundle install

# プロジェクト限定でインストール
bundle install --path vendor/bundle

これで Bootstrap の gem がインストールされたはずです。すでにパスは Middleman が上手いことやってくれているので、コレまでどおりの指定方法で連結することが出来ます。

/*
 *= require bootstrap
 */
 
body {
    color: #333;
 }

ファイルサイズの最適化 - ミニファイ

これもアセットパイプラインの一つですが、Middleman には CSS や JavaScript ファイルを圧縮する機能が最初から実装されています。やはりプロトタイピングにおいてはあまり意味のない機能ですが、静的サイトの作成事態が目的の場合は無くてはならない機能と言えます。やり方は非常に単純で、config.rb にある以下の行をコメントインするだけです。

config.rb

・・・
# Build-specific configuration
configure :build do
  # For example, change the Compass output style for deployment
  # activate :minify_css

  # Minify Javascript on build
  # activate :minify_javascript

  # Enable cache buster
  # activate :asset_hash

  # Use relative URLs
  # activate :relative_assets

  # Or use a different image path
  # set :http_path, "/Content/images/"
end
・・・

あとはmiddleman build とビルドコマンドを実行すれば、CSS と JavaScript ファイルが圧縮された状態で出力されます。しかもファイル名に.minの文字列が含まれているものは圧縮処理の対象外となるので、jquery.min.jsといった既に圧縮されたモノには影響が及ばないので、下手にプラグインを壊してしまうといった心配もありません。

※補足) Middleman を動作させる上での注意点

Linux 上で使うときは Middleman 起動コマンドにオプションを指定すること

Middleman は Mac 環境での動作を前提に作られているのか、Linux 上で動作させるには公式サイトで紹介されているコマンドに以下のようなオプションをつける必要があります。

middleman server --force-polling

Middleman は Ruby on Rails と同様、プロジェクトファイルを編集する度にターミナルで起動コマンドを実行しなくても Web ブラウザをリロードすれば即座に編集結果が反映されるようになっています。しかし Linux 上では上記のオプションを付けた上で起動しないと Middleman を再起動するまで編集結果が反映されないので、開発効率が著しく低下します。このことは以下のリンク先に書いてありますが、公式サイトには掲載されていないので知らないと泥沼にハマってしまいます(※ソースは山田)

Config.rb やローカルデータの編集後は再起動すること

とはいえ編集結果が Web ブラウザのリロードで反映されるのは、テンプレートファイルやレイアウトファイル、CSS や JavaScript といったものだけで、config.rb やローカルデータの編集、新規作成した結果に関しては、Middleman を再起動する必要があります。これも公式サイトには記載されていない情報なので知らないと泥沼にはまってしまいます(※ソースは山田)

脚注