LoginSignup
25
25

More than 5 years have passed since last update.

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

Posted at

軽量なJavascriptのテンプレートエンジンを探していたところ、JQueryの開発者John Resigが書いたJavaScript Micro-Templatingの短さにちょっと感動したので、コードリーディングした。

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

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 についてによると、これらの問題を解消して互換性があるmicro-template.jsが公開されている模様。

参考ページ

25
25
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
25