JavaScript でお手軽テンプレートエンジン

JavaScript で DOM をがしがしいじるのは大変だけど、でかいテンプレートエンジンを使うのはちょっと大げさだ、というときがある。そんなときに気軽に使えるテンプレートエンジンを作ってみた。
ソースは以下の通り。

function applyTemplate(template, f)
{
  try {
    return template.replace(/#\{[^#{}]+}/g,
             function(s) {
               var v = f(s.slice(2, -1));
               return v == null ? '' : v.toString().escapeHTML();
             });
  } catch (e) {
    return '[' + e.name + '] ' + e.message;
  }
}

使い方は、

<div id="template" style="display:none">
  <h2>#{title}</h2>
  <p>#{langs[0]} is invented by #{authors[langs[0]]}.</p>
  <p>#{langs[1]} is invented by #{authors[langs[1]]}.</p>
</div>

のように隠しエレメントでテンプレートを用意しておいて、

var title = 'Authors of Programming Languages';
var langs = ['JavaScript', 'Ruby', 'Perl'];
var authors = {
  'JavaScript' : 'Brendan Eich',
  'Ruby' : 'Matz',
  'Perl' : 'Larry Wall'
};
var result = applyTemplate($('template').innerHTML,
                           function(v){ return eval(v); });
Element.update('canvas', result);

という感じでテンプレートを適用する。
ポイントは、第2引数に eval を包んだ関数を渡してやり、呼び出し側環境を持って行くところ。こうしてやることで、テンプレート内部でローカル変数をそのまま参照できるので、JavaScript のテンプレートエンジンにありがちな、

var env = {
  'title' : '...',
  'lang[0]' : '...'
};
s = applyTemplate(template, env);

というように、あらかじめハッシュに変数を登録して渡さなくていいところがうれしい。
ちょっとトリッキーだけど、JavaScript には Ruby の binding のような仕掛けがないので仕方がない。
ただし、このままでは列挙や繰り返しには対応してないので、使えるところは限定的かも。
動作サンプル: http://limechat.net/sample/template/