JavaScriptテンプレートエンジン「Handlebars.js」入門

こんにちは、つみきのフロントエンドエンジニアの佐藤です。

JavaScript でサーバからデータを受信し、そのデータを元に HTML を整形して出力するという作業が必要な場合があるかと思います。

単純なものでしたら、 jQuery など DOM 操作系ライブラリを使用すればわりと簡単に書くことができますが、大量かつ複雑なデータを扱う際や、 REST API と連携してクライアントサイド MVC を実現する際などでは、 JavaScript のテンプレートエンジンを利用すると便利です。

今回は JavaScript のテンプレートエンジン、 Handlebars.js について紹介してみたいと思います。


まずテンプレートエンジンを利用しない場合を見てみます。

例えば以下の様なデータがあるとします。

var data = [
  {
    "id": 2436426,
    "name": "sato",
    "date": "2014-11-19 21:35:22",
    "gender": "male",
    "skill": [
      "html",
      "css"
    ]
  }
];

ビューは以下を想定します。

<div>
  <h1>sato</h1>
  <div>
    <span>2436426</span>
  </div>
  <div>
    <span>2014-11-19 21:35:22</span>
  </div>
  <div>
    <span>男</span>
  </div>
  <ul>
    <li>html</li>
    <li>css</li>
  </ul>
</div>

JavaScript のコードは例えばこうでしょうか。文字列結合して HTML の断片を作成し出力します。

var html = '<div>' +
            '<h1>' + data[0].name + '</h1>' +
            '<div><span>' + data[0].id + '</span></div>' +
            '<div><span>' + data[0].date + '</span></div>' +
            '<div>' +
              '<span>' +
                ((data[0].gender === 'male')? '男' : '女') +
              '</span>' +
            '</div>' +
            (function() {
              var ul = '';
              var len = data[0].skill.length;
              if (len > 0) {
                ul += '<ul>';
                for (var i = 0; i < len; i++) {
                  ul += '<li>' + data[0].skill[i] + '</li>';
                }
                ul += '</ul>';
              }
              return ul;
            })() +
            '</div>';

var output = document.getElementById('output');
output.innerHTML = html;

または以下の書き方はよく見かけますね。

var html = '';
html += '<div>';
html += '<h1>' + data[0].name + '</h1>';
html += '<div><span>' + data[0].id + '</span></div>';
html += '<div><span>' + data[0].date + '</span></div>';
html += '<div><span>';
html += (data[0].gender === 'male')? 'male' : 'female';
html += '</span>></div>';
if (data[0].skill.length > 0) {
  html += '<ul>';
  for (var i = 0; i < len; i++) {
    html += '<li>' + data[0].skill[i] + '</li>';
  }
  html += '</ul>';
}
html += '</div>';

var output = document.getElementById('output');
output.innerHTML = html;

これらに加え、適切なエスケープ処理が必要です。また、もっと複雑な構造の場合は、別で関数を定義する必要もあるでしょう。

いずれにしても上記 2 つとも見通しが悪いかと思います。 テンプレート部分とロジック部分が混合し、これでデータ構造や見た目に変更が加わった場合、修正が大変です。

Handlebars.js

Handlebars.js は「セマンティックテンプレート」を謳っている JavaScript テンプレートエンジンです。

Mustache という様々な言語で開発されているテンプレートエンジンと記法が似ていて、クライアントサイド、サーバサイド両方で利用でき、プリコンパイルも可能です。


上記のサンプルを Handlebars.js を利用して実装してみます。


まずは Handlebars.js を読み込みます。この例では jQuery も利用してみます。

<script src="handlebars.js"></script>
<script src="jquery.js"></script>

利用するデータは以下とします。

var data = [
  {
    id: 2436426,
    name: 'sato',
    date: '2014-11-19 21:35:22',
    gender: 'male',
    skill: ['html', 'css']
  },
  {
    id: 976955,
    name: 'hibarino',
    date: '2014-11-20 11:05:25',
    gender: 'female',
    skill: ['direction']
  }
];

まず HTML には出力先の div 要素を用意しておきましょう。ここに出力される想定です。

<div id="output"></div>

そして <script> 要素を用意し、 type 属性を type="text/x-handlebars-template" として、テンプレートを記述していきます。

<script type="text/x-handlebars-template" id="member-template">
  {{#each this}}
    <div>
      <h1>{{name}}</h1>
      <div>
        <span>{{id}}</span>
      </div>
      <div>
        <span>{{date}}</span>
      </div>
      <div>
        <span>{{convert gender}}</span>
      </div>
      {{> skills}}
    </div> 
  {{/each}}
</script>

<script type="text/x-handlebars-template" id="skill-template">
  {{#if skill}}
    <ul>
      {{#each skill}}
        <li>{{this}}</li>
      {{/each}}
    </ul>
  {{/if}}
</script>

Handlebars.js では変数展開の区切り文字を {{}} で表し、HTML タグを適切にエスケープして展開してくれます。 {{{}}} こう(3つに)するとエスケープせずに展開することもできます。

制御構文は {{#each}}{{#if}} のように使用します。


実行部分は以下のようになります。

// 出力先要素
var $output = $('#output');

// 定義したテンプレートを取得
var source = $('#member-template').html();
var skillPart = $('#skill-template').html();

// ヘルパー関数定義
Handlebars.registerHelper('convert', function(gend) {
  if (gend === 'male') {
    return '男'
  } else if (gend === 'female') {
    return '女'
  }
});

// パーツテンプレート定義
Handlebars.registerPartial('skills', skillPart);

// コンパイルして実行可能なテンプレート関数を生成
var template = Handlebars.compile(source);

//  テンプレート関数を使用して HTML を生成
var compiledHtml = template(data);

// 出力
$output.html(compiledHtml);

ヘルパー関数

Handlebars.registerHelper で独自の関数を登録することで柔軟に結果を出力することができます。

Handlebars.registerHelper('convert', function(gend) {
  if (gend === 'male') {
    return '男';
  } else if (gend === 'female') {
    return '女';
  }
});

<span>{{convert gender}}</span> 部分のようにして、定義した関数を使用します。

Handlebars.registerPartial

Handlebars.registerPartial を利用し、パーツごとにテンプレートを分割して管理することもできます。

var skillPart = $('#skill-template').html();
Handlebars.registerPartial('skills', skillPart);

上記のように定義し、 {{> skills}} とすることで使用できます。

プリコンパイル

Handlebars.js では予めテンプレートをコンパイルしておくことで速度面での改善を図れます。

コンパイラは npm で公開されています。

npm i -g handlebars

先ほどのテンプレートを別ファイルにしましょう。こうすることで管理や編集も楽になるので便利ですね。ファイル名は、 member.hbs と skill.hbs とします。

{{#each this}}
  <div>
    <h1>{{name}}</h1>
    <div>
      <span>{{id}}</span>
    </div>
    <div>
      <span>{{date}}</span>
    </div>
    <div>
      <span>{{convert gender}}</span>
    </div>
    {{> 'skill.hbs'}}
  </div> 
{{/each}}

 

{{#if skill}}
  <ul>
    {{#each skill}}
      <li>{{this}}</li>
    {{/each}}
  </ul>
{{/if}}

コンパイルコマンドの実行例としては以下です。

handlebars member.hbs -n Tmpl -f member.js

-f で出力先名と、 -n で名前空間を定義しておきます。

パーツテンプレートの場合は -p オプションをつけます。

handlebars skill.hbs -p -f skill.js

他にも機能がありますので、詳しくはリファレンスを参照してみてください。

https://github.com/wycats/handlebars.js/#usage-1


上記で生成された JavaScript のテンプレートファイルを HTML 側で読み込みます。

また、 Handlebars 本体は、実行コア部分のみで軽量の handlebars.runtime.js を利用しましょう。

<script src="handlebars.runtime.js"></script>
<script src="skill.js"></script>
<script src="member.js"></script>

名前空間を Tmpl としたので <head> 内などでグローバル変数として定義しておきましょう。

var Tmpl;

あとはプリコンパイルしたテンプレート関数を以下のように使用すれば前述の例と同じように出力できます。

var compiledHtml = Tmpl['member.hbs'](data);
$output.html(compiledHtml);

まとめ

駆け足でしたが、以上になります。

単純なものならテンプレートエンジンを利用するまでもないとは思いますが、 JavaScript から HTML 部分を分離することができるので便利ですね。

スマホアプリ制作、Web制作、UIコンサルなどのご依頼はこちら

お問い合わせ