読者です 読者をやめる 読者になる 読者になる

JavaScriptのテンプレートエンジン

こんばんは、south37です。
今日はJavaScriptのテンプレートエンジンを紹介したいと思います。
本当は何種類か順番に解説していきたかったのですが、予想以上に時間がかかって結局Handlebars.jsの一つだけになってしまいました。すいません。

Handlebars.js

最初にご紹介するのはHandlebars.jsです。これはhtmlのテンプレートエンジンで、js側で処理をかます事で変数をその中身の値に変えてくれます。むちゃくちゃシンプルな構造で分かり易いのが特徴でしょうか。

例えば、

<script id="input" type="text/x-handlebars-template">
<section class="inner">
  <h1 class="header">{{title}}</h1>
  <div class="box">
    <img src="{{img.url}}" alt="{{img.alt}}" />
    <p class="flex">{{text}}</p>
  </div>
</section>
</script>
<div id="output"></div>

みたいなhtmlファイルを考えます。コードの一部が{{と}}で囲まれた変数となっていますね。これに対して、

values =
  title: 'Hello Handlebars!'
  img:
    url: 'http://example.com'
    alt: 'Something..'
  text: 'My first Handlebars!'

のような変数と値のペアを格納したコンテナを用いて、

template = Handlebars.compile $('#input').html()
$('#output').html template(values)

のように変換の処理をかましてやると、

<div id="output">
<section class="inner">
  <h1 class="header">Hello Handlebars!</h1>
  <div class="box">
    <img src="http://example.com" alt="Something..">
    <p class="flex">My first Handlebars!</p>
  </div>
</section>
</div>

のようなhtmlが出力されます。変数名だった部分が値に置換えられています。まあ、そのまんまですね。
あと、#eachのようなイテレーション用の関数が用意されていて、

<script id="input" type="text/x-handlebars-template">
  <ul>
  {{#each tweets}}
    <li>{{tweet}} by {{user}}</li>
  {{/each}}
  </ul>
</script>
<div id="output"></div>

のようなhtmlに対して、

values =
  tweets: [
    user:  'Yuji'
    tweet: "I'm hungry..."
  ,
    user:  'Koji'
    tweet: "I'm sleepy..."
  ,
    user:  'Ryuji',
    tweet: "I'm tired..."
  ]

のような変数と値のペアを用いて変換を行うと、

<div id="output">
  <ul>

    <li>I'm hungry... by Yuji</li>

    <li>I'm sleepy... by Koji</li>

    <li>I'm tired... by Ryuji</li>

  </ul>
</div>

のようなhtmlが出力されます。これがなかなか便利っぽいです。この#eachってのは実はただの関数で、自分で#eachのような関数を定義する事も出来ます。自分でガシガシ実装して拡張出来るのも、handlebars.jsの大きな特徴のようです。

ちなみに、途中で出てきたcoffeescriptのコードは、jsコードへコンパイルすると

values = {
  tweets: [
    {
      user: 'Yuji',
      tweet: "I'm hungry..."
    }, {
      user: 'Koji',
      tweet: "I'm sleepy..."
    }, {
      user: 'Ryuji',
      tweet: "I'm tired..."
    }
  ]
};

みたいな感じになります。オブジェクトのarrayになっているのが分かるかと思います。

このhandlebars、シンプルで便利なだけでなく、プリコンパイルしておく事によって動作がむちゃくちゃ早くなるといった特徴もあるようです。ここで言うコンパイルとは、上記のコードでは

template = Handlebars.compile $('#input').html()

と書いている部分に相当します。実は、Handlebarsはターミナルからhandlebarsコマンドを叩く事によって、JST(JavaScript Template)へと変換したjsコードを出力出来ます。例えば、

// テンプレートファイルであるpartial.hbs
<div>{{name}}</div>

のようなテンプレートファイルを渡して

handlebars partial.hbs -f partial.js

のようなコマンドでコンパイルを実行すると、

// 出力されたpartial.js
(function() {
  var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
templates['partial.hbs'] = template(function (Handlebars,depth0,helpers,partials,data) {
  this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression;


  buffer += "<div>";
  if (stack1 = helpers.name) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
  else { stack1 = (depth0 && depth0.name); stack1 = typeof stack1 === functionType ? stack1.call(depth0, {hash:{},data:data}) : stack1; }
  buffer += escapeExpression(stack1)
    + "</div>\n";
  return buffer;
  });
})();

のような内容のjsファイルが生成されます。パッと見は何をやってるのか全然分からないんですが、よく見るとtemplates['partial.hbs']にテンプレートファイルを変換する関数がつっこまれてるのが分かります。すなわち、templates['partial.hbs']に変数と値のペアをオブジェクトとして渡してやる事で、変換後のhtmlコードが出力されるのです。

var html = templates['partial.hbs']({
  name: 'taro'
});
console.log(html);  //<div>taro</div>が出力

実際には、手動でいちいちhandlebarsコマンドをたたいてコンパイルをするのではなく、gruntと呼ばれるツールを使って自動でコンパイルまで行う事が多いようです。

gruntについては自分もきちんと勉強したいと思っていたところなので、次回以降にまたブログを書きたいと思います。今日はここまでです。

ちなみに、今回参考にしたのは以下の3つのブログです。
http://lealog.hateblo.jp/entry/2012/12/09/093059
http://qiita.com/mamoida/items/3bfd40de062a3f601939
http://blog.mitsuruog.info/2013/03/backbonejshandlebarjs.html

特に1つ目なんかほぼコードを丸パクリしちゃった訳ですがw、とても参考になりました。興味を持った方は読んでみるといいと思います。3つ目はgruntとbackbone使ってカッコいい感じにhandlebarsを使ってて、何となくクールな雰囲気が味わえると思います。

あと、JavaScriptのテンプレートエンジンって他にもいっぱいあるようです。
http://www.infiniteloop.co.jp/blog/2013/02/js-template-engine/
そのへんもいつか取り上げてみたいですね。