わりと高速なJavaScriptテンプレートエンジン、doT.js を使おう

Webアプリ作っているとPHPやRubyでHTMLを返すよりNode.js (io.js)でJSONを返してそれをクライアント側で処理してHTMLにするってのがやってみるとめっちゃ楽だったので。

で、まずどれを使おうって調べてると結構でてくるんです。

jquery-tmpl
元jQuery公式のテンプレートプラグイン。超遅い。死ぬ。
JsRender
jquery-tmplの後継。JsViewsと合わせるといいらしい。
Hogan.js
Twitter製。わりと使ってるのを見かけるし、そこそこ速い。
Handlebars.js
テンプレートがHogan.jsと似てるがやや遅い。
micro-template.js
スクリプトが軽量だけど for, if などが面倒。Handlebars.jsよりやや遅い。
doT.js
Node.jsでも使える。速度重視という謳い文句。

テンプレートの相互性を考えたらよくある形式のHogan.jsでいいのだけど速度重視ということで doT.js というのを使うことにしました。

jsPerf で速度比較したやつ
適当に用意したテストデータだと doT.js が一番速そうです。
(Windows 7のChrome, Firefox 31, Firefox 36で10回計測、OtherはIE11。)
doT.js vs Hogan.js

<!-- doT.js 用のテンプレートを用意する -->
<script type="text/template" id="tmpl">
<h1>{{!it.title}}</h1>
<p>{{!it.description}}</p>
<ul>
{{~it.entries :item:i}}
<li><a href="{{=item.url}}">{{!item.name}}</a><span>{{=item.tag}}</span></li>
{{~}}
</ul>
</script>
/* 置換するやつデータ */
var data = {
    title: 'Hogan.js vs doT.js vs Handlebars.js',
    description: '',
    entries: []
}
var entry = {
    url: 'http://example.com/',
    name: 'name',
    tag: '<b>tag</b>'
};
for (var i = 0; i < 10; i++) {
    data['entries'].push({url: entry['url']+i, name: entry['name']+i, tag: entry['tag']+i});
}

/* doT.js でテンプレートをコンパイルして #content に書き出す */
var tmplText = $('#tmpl').text(),
    tmpl = doT.template(tmplText);

$('#content').html(tmpl(data));

{{!it.name}}, {{=it.url}}や{{~it.entries}}…{{~}}など少し変わってますがJavaScriptそれなりに書ける人ならむしろこっちのが分かりやすいかと思います。

{{!it.hoge}} hogeをhtmlencodeしたやつに置換
{{=it.hoge}} HTMLタグを含めてhogeをそのまま置換する
{{? it.hoge }} … {{?}} if (hoge) { }
hogeが空じゃなければ{{? it.hoge }}~{{?}}内を置換する
{{~ it.entries :item:index }} … {{~}} for文
{{ for (var key in it.data) { }}
<li> {{!it.data[key]}} </li>
{{ } }}
for in なんかも書ける
{{= encodeURIComponent(it.hoge) }} JavaScriptの関数をそのまま使ったりもできる

この辺を覚えとけばなんとかいけます。

というわけで、doT.jsより速いのあったらそっちを使おう。

node-mysqlのコールバック地獄をthunkifyでなんとかする

Node.js は –harmony を付けて起動、 io.js ならそのまま使えるのでちょろい。

まず、node-mysql (node-mysql2) の createConnection を使う場合。そこまで変なことしないで thunkify(db.query.bind(db)) にだけ気をつけたらいけます。
ググるとこの方法使ってるのよく見ます。

var mysql    = require('mysql2'),
    thunkify = require('thunkify'),
    co       = require('co');

var db = mysql.createConnection({host: '***', user: '***', password: '***'}),
    query = thunkify(db.query.bind(db));

co(function* () {
    var res = yield query('SELECT * FROM hoge');
    console.log(res);
    db.end();
})

そして、createPoolを使う場合。
pool.getConnection(function(err, db){}) をやってから thunkify(db.query.bind(db)) とするのでちょっと面倒臭いです。

// pool connection 普通のやつ

var pool = mysql.createPool({ ... });

pool.getConnection(function(err, db) {
    db.query(' ... ', function(e, res) {
        console.log(res);
    });
    db.release();
});

このなんかすっきりしないコールバックを下のような感じにすれば、それなりに便利な書き方ができるんじゃないでしょうか。

// pool connection + thunkify

var mysql    = require('mysql2'),
    thunkify = require('thunkify'),
    co       = require('co'),
    pool;

function* getConn() {
    if (!pool) pool = mysql.createPool({host: '***', user: '***', password: '***'});
    var db    = yield thunkify(pool.getConnection.bind(pool))(),
        query = thunkify(db.query.bind(db));

    // db.query を残したまま ES6 generator 用の db.exec を作る
    db.exec = function* () {
        return yield query.apply(null, arguments);
    }
    return db;
}

co(function* () {
    var db  = yield getConn(),
        res = yield db.exec('SELECT * FROM hoge');

    console.log(res);
    db.release();
})

ちょっと長くなったような気もしますが、 co(function* () … の中身がスッキリして見やすくなりました。
今使ってるio.jsのオレオレRSSリーダーでcreateConnectionよりcreatePoolの方がほんの少し速いので、しばらくこれでいこうかなと。