Handlebars.jsのincludeヘルパー

Handlebars.jsでのテンプレートファイルの取り込みはpartial使えみたいな感じだけど、partialにするものを全て定義しなくちゃいけない。partialのテンプレートでの記述もpartialごとに変わる。それをSSIのインクルードみたいに{{include "foo.hbs"}}でコンテキストを考慮して取り込めたら簡単なんじゃないかなーと。でincludeするだけのヘルパーを作って使うようにしたメモ。

色々遠回りしたけど、結局はテンプレート・ファイルを読んでコンパイル→レンダーする関数を作ってそれをヘルパー関数にすれば良いだけだった。Handlebars.jsがカシコイのでコンテキストとかは自動的に良しなにしてくれる。

var fs = require('fs');
var hbs = require('handlebars');

function applyTemplate(file, data) {
  var tmpl = hbs.compile(fs.readFileSync(file, 'UTF-8'));

  return tmpl(data);
}

hbs.registerHelper('include', function (file, options) {
  return new hbs.SafeString(applyTemplate(file, this));
});

console.log(applyTemplate('./template.hbs', {
  "he": {
    "name": "John"
  },
  "she": {
    "name": "Jane"
  }
}));

使うテンプレート・ファイルは、template.hbs

{{#he}}His name is {{include "./include.hbs"}}{{/he}}
{{#she}}His girlfriend name is {{include "./include.hbs"}}{{/she}}

と、include.hbs

{{name}}

の2つ。実行すると結果はこうなる。

His name is John

His girlfriend name is Jane

includeヘルパー関数への引数で指定するテンプレート・ファイルのパスが、それを記述するテンプレート・ファイルからの相対パスにならない辺りが面倒くさいのは直した方が良さそう。


テンプレート・ファイルからテンプレート・ファイルを呼ぶ必要があるケースというのはあんまりない。大抵の場合はデータとテンプレート・ファイルの書き方の工夫でどうにかなる。Handlebars.jsでは{{#if}}とかもあるので、そっちでどうにかするのが王道(ロジック入っちゃうけど)で、テンプレート・ファイルも増えずメンテナンス性が高い気がする。

こういったテンプレート・ファイルのインクルードが威力を発揮するのは、微妙に違うテンプレート・ファイルを使わざるを得ない時に共通化したい部分を切り出すというケースやHTML5のようなモジュール化しやすい形式で出力するケース。前者ではテンプレートの一部をコピペしたりすることがなくなり、後者ではテンプレート・ファイルの編集しやすさが向上する。