John Resigが書いたシンプルなテンプレートエンジンJavaScript Micro-Templatingをコードリーディング

軽量なJavascriptのテンプレートエンジンを探していたところ、JQueryの開発者John Resigが書いた<a href="http://ejohn.org/blog/javascript-micro-templating/">JavaScript Micro-Templating</a>の短さにちょっと感動したので、コードリーディングした。

このテンプレートエンジンは、こんなテンプレートや、

```template1.html
<script type="text/html" id="item_tmpl">
  <div id="<%=id%>" class="<%=(i % 2 == 1 ? " even" : "")%>">
    <div class="grid_1 alpha right">
      <img class="righted" src="<%=profile_image_url%>"/>
    </div>
    <div class="grid_6 omega contents">
      <p><b><a href="/<%=from_user%>"><%=from_user%></a>:</b> <%=text%></p>
    </div>
  </div>
</script>
```

インラインでロジックを書いたこんなテンプレートを使えて、

```template2.html
<script type="text/html" id="user_tmpl">
  <% for ( var i = 0; i < users.length; i++ ) { %>
    <li><a href="<%=users[i].url%>"><%=users[i].name%></a></li>
  <% } %>
</script>
```

こんな感じでテンプレートを展開することができる。

```
var result = document.getElementById("user_list");
result.innerHTML = tmpl("user_tmpl", users);
```

それでいて、テンプレートのコードはこれだけ。

```simple_template.js
// Simple JavaScript Templating
// John Resig - http://ejohn.org/ - MIT Licensed
(function(){
  var cache = {};
 
  this.tmpl = function tmpl(str, data){
    // Figure out if we're getting a template, or if we need to
    // load the template - and be sure to cache the result.
    var fn = !/\W/.test(str) ?
      cache[str] = cache[str] ||
        tmpl(document.getElementById(str).innerHTML) :
     
      // Generate a reusable function that will serve as a template
      // generator (and which will be cached).
      new Function("obj",
        "var p=[],print=function(){p.push.apply(p,arguments);};" +
       
        // Introduce the data as local variables using with(){}
        "with(obj){p.push('" +
       
        // Convert the template into pure JavaScript
        str
          .replace(/[\r\t\n]/g, " ")
          .split("<%").join("\t")
          .replace(/((^|%>)[^\t]*)'/g, "$1\r")
          .replace(/\t=(.*?)%>/g, "',$1,'")
          .split("\t").join("');")
          .split("%>").join("p.push('")
          .split("\r").join("\\'")
      + "');}return p.join('');");
   
    // Provide some basic currying to the user
    return data ? fn( data ) : fn;
  };
})();
```

以下がポイント。

* tmplの引数の数で挙動を変えて(1つの場合はテンプレートから展開用の関数オブジェクトを返却し、2つの場合は関数オブジェクトにデータオブジェクトを渡しHTMLを返却)再帰呼び出しする。
* 一度生成した関数オブジェクトをキャッシュしておく。
* テンプレートを展開するための関数オブジェクトを動的に生成する。

3つめの関数オブジェクトを動的に生成する処理（後半でreplace、splitしまくってるあたり）は、テンプレートのHTMLを文字列置換してJavascriptのコードに変換している。

例えば以下のテンプレートを入力すると、

```sample_tmpl.html
<script type="text/html" id="user_tmpl">
  <% for ( var i = 0; i < users.length; i++ ) { %>
    <li><a href="<%=users[i].url%>"><%=users[i].name%></a></li>
  <% } %>
</script>
[/html]
```

以下の関数オブジェクトが作成される。（読みやすいように整形）

```
function anonymous(obj/**/) 
{
  var p=[],print=function(){p.push.apply(p,arguments);};
  with(obj){
    p.push('  ');
    for (var i = 0; i < users.length; i++) {
      p.push(' <li><a href="', users[i].url ,'">', users[i].name ,'</a></li> ');
    }
    p.push(' ');
  }
  return p.join('');
} 
```

ここが肝ですね。

ただ、このテンプレートエンジンを実際に使う場合、デフォルトでエスケープされなかったり、テンプレートの記述を間違えると、どこが問題かを特定するのが難しかったりしますが、[micro-template.js について](http://subtech.g.hatena.ne.jp/cho45/20121003/1349195285)によると、これらの問題を解消して互換性がある[micro-template.js](https://github.com/cho45/micro-template.js)が公開されている模様。


## 参考ページ

* [micro-template.js について - 冬通りに消え行く制服ガールは✖夢物語にリアルを求めない。 - subtech](http://subtech.g.hatena.ne.jp/cho45/20121003/1349195285)
* [John Resig が 2008 年に書いた JavaScript Micro Templating のコードがわからない - 刺身☆ブーメランのはてなダイアリー](http://d.hatena.ne.jp/a666666/20110203/1296667318)
* [3つの簡単便利Javascriptテンプレートエンジン。Mustache.js, Handlebars.js, Microtemplating | codechord](http://codechord.com/2013/07/javascript_template_engine-mustache-handlebars-microtemplating/)
* [micro-template.js](https://github.com/cho45/micro-template.js)