JavaScriptのテンプレートエンジン「T.js」を公開しました

Posted by Hiraku on 2011-06-21

結構前に公開していたんですが、きちんとした紹介を書いてなかったので改めて。Githubにて、T.jsという名前のJavaScript用テンプレートエンジンを公開しています。

T.js - DOMBuilder-like template engine for JavaScript

T.jsとは

JavaScriptでHTMLを書くためのライブラリ(一種のテンプレートエンジン)です。

T.table("#addressbook")({style:{borderCollapse:"collapse"},border:"5"},
    T.thead(
        T.tr(
            T.th("name"),
            T.th("address"))),
    T.tbody(
        T.tr(".odd")(
            T.td("alice"),
            T.td("a street")),
        T.tr(
            T.td("bob"),
            T.td("b street")),
        T.tr(".odd")(
            T.td("charlie"),
            T.td("c street"))));
<table id="addressbook" style="border-collapse:collapse" border="5">
 <thead>
  <tr>
   <th>name</th>
   <th>address</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td class="odd">alice</td>
   <td>a street</td>
  </tr>
  <tr>
   <td>bob</td>
   <td>b street</td>
  </tr>
  <tr>
   <td class="odd">charlie</td>
   <td>c street</td>
  </tr>
 </tbody>
</table>

別にこんなライブラリを使わなくても、innerHTMLやDOMを使えばいいのですが、無闇にinnerHTMLを使うと問題があるし、かといってDOMのAPIは名前が長くて書くのが大変です。そんなとき、T.jsを使えば短くすっきり書くことができます。

手法としては全然目新しいものではなく、DOM BuilderとかMarkup Builderとか呼ばれるものです。似たような手法を使っている競合ライブラリは前回の記事を参照してください。

T.jsの特徴

  • Independent. 単独で動作するので、好きなライブラリと組み合わせて使うことができます。
  • Lightweight. 圧縮版で2.3kBという超軽量サイズです。
  • Pure JavaScript. 素のJavaScript文法で可能な限り短く書けるように設計しました。
  • MIT Licenseです。

加えて、可変長引数と配列引数を混ぜて書くことができるというのもこだわりポイントです。これによって汎用性が格段に上がっています。

DOMをWrapした形をとっているため、タグの閉じ忘れが原理的に発生しません。HTMLエスケープをする必要もありません。

速度…はなるべく遅くならないように書いているつもりですが、他のテンプレートエンジンと比較してないのでまだ売り文句にはできないかな。。。

使い方

GithubからT.jsもしくはT.min.jsをダウンロードして、読み込みます。

<script src="T.js"></script>

初期化のために次の一行を書いてください。

T.Shorthand();

あとは、冒頭のようなコードを書いて、適当な変数に代入し、

var template = T.div("テンプレートのテスト");

好きな場所へappendChild()すれば画面に反映されます。

//bodyタグの最後尾に追加
document.body.appendChild(template);

//id="target"の要素に追加
document.getElementById("target").appendChild(template);

//jQueryが使える場合はこんな書き方もできます。
$("#target").append(template);

T.js文法の基本

T.tag(attr, 子要素1, 子要素2, ...)が基本形で、DOM(HTMLElement)が返されます。要素に属性がある場合は第一引数にJSON形式で渡します。属性がない場合は第一引数を省略できます。

じゃんじゃん入れ子にしていくことができます。

T.div(T.div(T.div(T.div("hogehoge"))));

CSSを埋め込む

{style:"background-color:red"}のように書いた場合、動かないブラウザがあります。CSSはオブジェクト形式でセットしましょう。

T.div({style:{backgroundColor:"#ccc",color:"#000",borderBottom:"1px"}}, "hogehoge");

CSSのプロパティ名はJavaScript風にlowerCamelCaseで書かなくてはなりません。でも覚え直す必要はないです。ちゃんと法則があって、「-(ハイフン)」の直後の文字を大文字にして、「-」を取り払えばJS風に変換できます。

background-color => backgroundColor
border-bottom => borderBottom

idとclassの省略記法

id="", class=""はよく使うと思いますので、更なる省略記法を用意しました。CSSセレクタ風に書くことができます。

T.div("#id.class.class2")("hogehoge");
//------------
/<div id="id" class="class class2">hogehoge</div>

今のところ#idと.classのみ対応で、[title=foo]やZenCodingのような高度な記法は対応していません。あくまで補助的なものです。

ちなみにこの記法は少しパフォーマンスを犠牲にします。省略せずに書く場合は以下のようになります。class:ではなくclassName:と書くので注意してください。

T.div({id:"id",className:"class class2"},"hogehoge");

T.Shorthand()は何をやっているのか

冒頭では特に説明せずに「初期化処理」と書きましたが、具体的には「T」オブジェクトにT.divやT.spanなどの関数を登録する処理をしています。(T.jsを読み込んだ時点ではこれらの関数は存在せず、使えません)

何でわざわざ書かせているかというと、登録する関数の数に応じて若干のメモリを消費するからです。必要に応じて選択できるようにしてあります。

T.Shorthand()と引数を省略して書いた場合はHTML4にしかないタグや、HTML5にしかないタグは登録されません。

T.Shorthand("div a p"); //divとaとpしか使わない場合はこれでもOK。
//丸ごと登録するよりメモリ消費量か抑えられるかも?

T.Shorthand.html4(); //5で廃止される予定のタグも登録されます。fontとかcenterとか。
T.Shorthand.html5(); //5で導入される予定のタグも登録されます。canvasとかvideoとか。
T.Shorthand.full();  //T.jsにプリセットされている全てのタグを登録します。

通常は引数省略のT.Shorthand()で十分だと思います。

応用:"T."を消す

with構文を使うと"T."を省略でき、さらに短く書けます。ただしあまり推奨しません。a, b, iといった一文字の変数ができてしまい、混乱しやすいからです。

with(T) var template =
div("#wrapper")(
    div("#header")(
        h1("page title"),
        p(
            "description",
            a({href:"http://www.sample.com"}, sample.com))));

withを使うときは変数名が衝突しないか注意してください。

応用:T.DocumentFragment()

appendChildできるのは一つの要素だけです。通常、複数の要素を追加するときはappendChildを何度も書くかループ構文を使わなくてはなりません。

そんなとき、T.DocumentFragment()で囲っておくと、他のT.tag()と同じインターフェースで書けるようになります。

document.body.appendChild(T.DocumentFragment(
  T.h2("h2"),
  T.p("paragraph"),
  T.h2("h2"),
  T.p("paragraph")
));

要はdocument.createDocumentFragment()のラッパーです。通常はパフォーマンス改善目的で使うことが多いと思いますが、書きやすくする目的でサポートしています。

ちなみに、T.DF()という関数でも同じことができます(T.DF === T.DocumentFragment)。DocumentFragmentという名前が長すぎると思ったら使ってください。

応用:条件分岐

いわゆる三項演算子(条件演算子)を使えばテンプレート中に条件分岐を書き込めます。

isVIP ? T.div("VIP専用コンテンツ")
      : T.div("あなたはVIP会員ではありません");

応用:くり返し

T.jsは引数に配列を混ぜても動きます。くり返し現れる要素は配列にしてから渡せばいいのです。

単純なものであればArray.prototype.mapを使うと便利です。(古いブラウザで動かないですが)

T.ul(
    [1,2,3].map(function(){
        return T.li(this);
    }),
    T.li("aaa"),
    T.li("bbb"));
//-------------
//<ul>
// <li>1</li>
// <li>2</li>
// <li>3</li>
// <li>aaa</li>
// <li>bbb</li>
//</ul>

即時関数ブロックを使って書いてもいいですし

T.ul(
    (function(){
        var i,len,data=[1,2,3],r=[];
        for (i=0,len=data.length; i<len; ++i) {
            r[r.length] = T.li(data[i]);
        }
        return r;
    })(),
    T.li("aaa"),
    T.li("bbb"));

読みにくくなるのであれば別の関数に切り出すのもいいでしょう。

function subTemplate(data) {
    var i,len,r=[];
    for (i=0,len=data.length; i<len; ++i) {
        r[r.length] = T.li(data[i]);
    }
    return r;
}
//-------------------------------
T.ul(
    subTemplate([1,2,3]),
    T.li("aaa"),
    T.li("bbb"));

JavaScriptをべたーっと埋め込んでいくと、テンプレートは汚くなりがちなので、条件分岐だろうがくり返しだろうが、ちょっと複雑になってきたら関数で切り出していくのがよいと思います。

コーディングスタイル

T.jsのテンプレートはPure JavaScriptにこだわったために、読みにくい部分もあります。

  • 括弧の入れ子がすごいことになる → 括弧の入力補助や対応を強調表示してくれるエディタを使いましょう。
  • カンマを付け間違えると動かない → 書き方を工夫しましょう。

個人的には、「)」はできるだけまとめ、改行をしないようにしています。こうすると行末には「(」「,」「;」しか登場しないので、カンマの付け忘れを見つけやすくなるからです。

T.div(
    T.div(
        T.div("hogehoge")),
    T.div(".hogeclass")(
        T.div("foofoo")));

好みの問題もあるので、これがベストというのはなかなか難しいですが、説明文ではなるべくこのスタイルで統一するようにしています。


これで一通りの機能は紹介できました。シンプルな機能だけですね。

こんな人にオススメ

  • あまりゴツいテンプレートエンジンは使いたくない
  • HTMLを書くのが嫌だ (T.jsは生のHTMLよりも短く書けます)
  • DOMを使いたいけどDOMのAPIを書くのが面倒だ

こういう場合には不向き

  • JavaScriptが全くわからない人にはとっつきにくい
  • テンプレートの原本がHTMLの場合、書き直さなくてはならない
  • 自由度が高い(JSを埋め込める)ので、適当に書いていくとスパゲッティコード化する恐れがある

まあ車輪の再発明なわけですが、もしよければ使ってみてください。Githubでまともに何かを公開するのは初めてなので、何か不手際があれば教えていただけると幸いです。

keyword: javascript

JavaScriptの最新記事