TypeScript+ブラウザ向けプロジェクトのためのテンプレートエンジン Chonmage を作った

TypeScriptの勉強用に小さなテンプレートエンジンを作った。 型チェックができるmustacheを目指して作り始めたんだけど、そもそもmustacheが型にゆるふわ過ぎる仕様だったので途中で後悔した。結局mustacheの仕様からいろいろ削って何とか形にはなったので公開してみる。

こんな感じ

テンプレートエンジン Chonmage (https://github.com/ryiwamoto/chonmage) Chonmageを使ったプロジェクトのサンプル(https://github.com/ryiwamoto/chonmage-sample)

テンプレートに渡すデータのinterface

テンプレートファイル

生成されるTypeScriptコード

テンプレートの描画

Chonmageの特徴

テンプレートを取得するためのキーやテンプレートに渡した変数がインターフェースを満たしているかをコンパイル時にチェックできる

これは「特殊化されたオーバーロードと文脈依存型推論の合わせ技(http://b.hatena.ne.jp/entry/qiita.com/progre/items/6b85993af5b4a7f1e792)」をつかっている。

Chonmageではライブラリが

interface ITemplateStore{
    get(key: string): void;
}

というテンプレートをどんなキーで取得しても常にvoidを返すインターフェースを提供していて、

各テンプレートをコンパイルするごとにキー名とコンパイルされたインスタンスを紐づける形でinterfaceを拡張している。

interface ITemplateStore{
    get(key: "test"): new Chonmage.Compiled<IRenderingContext>(/*..*/);
}

これによって正しいキーでテンプレートを取得しているかをコンパイル時にチェックできるようになる。 また、renderメソッドに渡すデータが指定したinterfaceを満たしているかのチェックもできる。

出力する変数にstringかnumber型以外を渡すとコンパイルエラーにする

これには賛否両論だと思うけど、自分はデータを表示用に加工する処理をテンプレートのレンダリング時に行うのは良くないと思っている。(あと実装を楽にするためでもある…。)

Chonmageができないこと

  • テンプレートに渡すinterfaceに配列を除くジェネリクス型を定義できない
  • 変数名が存在しない場合は空文字列を出力する
  • ラムダ(mustacheの仕様にのっとってラムダをサポートすると動的にテンプレートを作って挿入できてしまうので諦めた。)
  • グローバル変数の参照
  • Partial (これは時間がいずれやりたい)

そのほか

  • TypeScriptが提供しているLanguageServiceはあくまでエディタ向けを想定して作られているようだ。そのせいか型情報を取得してもほぼ文字列として帰ってきてしまう(TypeScript.Services.MemberInfo)。なのでもっとリッチな型情報が欲しいならコンパイラソースコードからいろいろ引っ張ってくる必要がありそう。
  • なぜかclassのプロパティは型情報の取得(getTypeAtPosition)ができなかった。なぜ?
  • TypeScriptの型情報は実行時には失われてしまうので、_.templateみたいにその場でテンプレートをコンパイルをして出力するのは難しそう。
  • IntelliJのTypeScriptサポートはかなり残念。
  • Mustacheのサブセットではなく、Razor Syntaxのようなもっとプログラム側に寄った記法のほうがTypeScriptを活かせたかもしれない。
  • TypeScriptの勉強のために書いたけど、型空間と変数空間の違いとか外部モジュールと内部モジュールの使い分けとか、クラサバ両方で使うためのコードをどう書くかみたいな知見が足りていないのでTypeScriptリファレンスを読むことにする。