あと味

たくさん情報を食べて、たくさん発信すると、あとになって味わい深い。

MTの管理画面でMT標準のJSテンプレートエンジンを使う

管理画面のカスタマイズをしていて、JSでDOMを弄ってHTMLを出力しようと思った時に、コードの見通しを確保するためにJSテンプレートエンジンが欲しいなと思いました。

ただ、そのためにライブラリ読み込むのも微妙だし、正規表現で頑張るかと思ったところ、管理画面でJSテンプレートエンジンが使われてたことを思い出しました。

MTの管理画面で使われている、JSテンプレートエンジンのライブラリは、/path/to/mt-static/js/common/Template.js にあって、実装を確認したところ、シンプルで汎用的に使える小さなライブラリっぽいので、下記の通り機能をまとめました。

ちなみにこのライブラリは、数年前から更新が止まっていて、Github リポジトリの当該ファイルのコミット履歴も、「Common JavaScript ライブラリをリポジトリに入れました」的なログが一つあるだけのレベルです。

メンテが止まっていて、いつ役目を終えるかわからない感じの存在感なので、その点はご留意ください。

この記事の賞味期限もそれまでです。

使い方

下記のような感じで使えます。

var tmpl = new Template('Hello, [#= name #]!');
var context = new Template.Context({ name: 'taiju' });
tmpl.process(context);
// => "Hello, taiju!"

/*
下記で使う変数tmpl_textには下記のテンプレート文字列が保存されていると仮定する
  <ul>
  [# jQuery.each(vars, function (i, v) { #]
    <li>[#|h|u v#]</li>
  [# }) #]
  </ul>
*/
tmpl.compile(tmpl_text);
context.vars = { vars: ["<script>alert('foo');</script>", "バー", "baz"] };
tmpl.process(context);
/* =>
"<ul>

  <li>&lt;script&gt;alert('foo');&lt;%2Fscript&gt;</li>

  <li>%E3%83%90%E3%83%BC</li>

  <li>baz</li>

</ul>"*/

Underscore.jsのテンプレート機能のように、普通に言語の機能がそのまま使えるタイプのシンプルなテンプレートエンジンです。この手のテンプレートは学習コストが低く、言語のパワーがそのまま使えるので、個人的に好むタイプです。

管理画面はjQueryがロードされているので、jQueryのユーティリティを組み合わせてテンプレートを書くのが良いかもしれません。

ライブラリの機能

Template.jsによって、Template, Template.Context, Template.Filterというクラスが提供されます。

Template.jsは、Core.jsに依存しているので、利用するにはCore.js等の依存ライブラリがあらかじめ読み込まれている必要があります。

主に使うのはTemplateクラスになりそうですが、先のサンプルのようにTemplateクラスのインスタンスを作って処理する他、Templateクラスのクラスメソッドを使って、JSテンプレートをビルドすることもできます。

var templates = {
  foo: '[#= foo #]'
};
var vars = {
  foo: 'FOO'
};
Template.process('foo', vars, templates);
// => "FOO"

クラスメソッドの方が手軽に使えるかもしれません。

テンプレートの記法

[#から#]で囲まれた部分が JS テンプレートの記述部分になります。

JavaScript の任意の式や文が記述でき、式の結果を出力したり、出力結果にフィルタをかけたりできます。また、Template.ContextのインスタンスをTemplateクラスのインスタンス(compile済み)の持つ、processメソッドに渡すことで、テンプレートに任意の変数を渡すことができます。

[##]というテンプレートの開始位置、終了位置を指定するための文字列は、TemplateクラスのインスタンスbeginTokenプロパティとendTokenプロパティを変更することで変更可能です。

頻繁に使うことになるのは、[# ... #] と [#= ... #] ですかね。テンプレートエンジンでよく見かける形式だと思います。

テンプレートのサンプル

[# ... #]

[# if (false) { #]
ここは出力されない
[# } #]

[#から#]で囲まれた範囲では、任意のJavaScriptの式や文を評価することができます。評価内容は出力されません。

[#-- ... --#]

[#-- 下記でゴニョゴニョする --#]
ゴニョゴニョ

[#--から--#]で囲まれた範囲はコメントになります。コメント中の式や文は実行されず、出力もされません。

[#= ... #]

[#= true ? 1 : 0 #]

[#=から#]で囲まれた範囲は、任意のJavaScriptの式を記述でき、評価された値が出力されます。

[#* ... #]

[#= foo #]
[#* return #]
[#= baz #]<!-- この変数の値は出力されない -->

[#*から#]で囲まれた範囲は、定義された特定のコマンドを記述できます。

とは言え、実際には、returnしか定義されておらず、returnコマンドを記述すると、その記述以降のテンプレートは評価されません。

[#| ... #]

[#|i "foo ${bar} baz" #]<!-- ${foo} や $foo という記述が変数展開される -->
[#|h "<script>alert(1);</script>" #]<!-- HTMLをエスケープする -->
[#|H "&lt;script&gt;alert(1);&lt;/script&gt;" #]<!-- HTMLをアンエスケープする -->
[#|u "テスト" #]<!-- URLエンコードする -->
[#|U "%E3%83%86%E3%82%B9%E3%83%88" #]<!-- URLデコードする -->
[#|lc "ABC" #]<!-- 小文字にする -->
[#|uc "abc" #]<!-- 大文字にする -->
[#|substr(0, 3) "abcdefg" #]<!-- 指定した位置から指定した位置まで文字を切り出す -->
[#|ws "   foo   " #]<!-- 前後の空白を除去する -->
[#|trim(3) "abcdefg" #]<!-- 引数で指定した文字以降を…(U+2026)で省略する -->
[#|date "2014-06-05T00:00:00+09:00" #]<!-- ISO形式の日付文字列をISO形式の年月日文字列に変換する -->
[#|localeDate "2014-06-05T00:00:00+09:00" #]<!-- ISO形式の日付文字列をロケールに合わせた表記に変換する -->
[#|rt "<script>alert(1);</script>" #]<!-- タグを削除する(内容は残す) -->
[#|rp(/foo/g,"bar") "foobarfoobar" #]<!-- 引数に指定した文字を引数に指定した文字に置換する -->

[#|から#]で囲まれた範囲は、評価された式の値に、定義されたフィルタを適用した上で値を出力します。

上記の中でも、iフィルタや、dateフィルタは、内部で使っている関数がCore.jsに依存しています。

MTのJSライブラリあまり調べたことなかったので意識していなかったんですが、結構グローバルオブジェクトを拡張してるようです。

rpフィルタはうまく動いていなかったので、PRしました。

また、フィルタはUnixのパイプのような感じで、複数組み合わせることができます。

[#|ws|rt|uc "   <span>abcd</span>  " #]<!-- 前後の空白を除去してから、タグを除去して、内容を大文字にする -->

Template.Context, Template.Filter について

Template.Contextには、いろいろメソッドが用意されていますが、Templateクラスを通して透過的に利用することがほとんどな気がしたので、紹介は割愛します。フィルタに関しても、基本はテンプレート内に記述するケースがほとんどでしょう。

Template.Contextのincludeメソッドは単体でも使うかもしれません。

var templates = {
  header: 'Header!!',
  body: '[#= context.include("header") #] Body!! [#= context.include("footer") #]',
  footer: 'Footer!!'
};
Template.process('body', {}, templates);
// => "Header!! Body!! Footer!!"

まとめ

よく使う機能に関しては、上記で紹介した通りです。あとはソース読んで確認してください。

昔はいろいろとMT標準のJSライブラリについて、ナレッジが共有されていたのかもしれませんが、今は、ググってもあまり情報が見つからない感じがします。

Template.jsに限らず汎用的に使えそうなライブラリもありそうですが、ドキュメントないし、自分のような新参者はソース読むしかない感じです。公式ドキュメントが待たれるところ。

Template.jsは、汎用的に使えるテンプレートエンジンだと思うので、MTの管理画面のカスタマイズ用途等で、特に他のライブラリを読み込む予定が他にない時は、このJSテンプレートエンジンを使おうかなと思ってます。

ほなの。