TypeScriptでSencha Touch開発に挑戦

前回に引き続き、Sencha Advent Calendar 2012の20日目の記事です。

Sencha Touch(Ext JS)では、HTMLをあまり書かずにJavaScriptをバリバリ書く必要があります。
しかし、個人的には静的な型付けのないJavaScriptはあまり書きたくないんですよね。

コンパイルしてJavaScriptを生成する系の言語は、CoffeeScript、Haxe、Dart、JSXなどいろいろありますが、やっぱりC#の開発者であるヘルスバーグ先生が手がけたTypeScriptを触らないわけにはいきません。

TypeScriptはまだ発展途上ではありますが、周辺ツールも整備されているようですし、バージョン0.8.3ではGenerics対応もあるとのことで、今後が非常に楽しみな言語です。


というわけで、Sencha TouchのアプリをTypeScriptで書くための手順を紹介したいと思います。
なお、本記事の内容は実用にはもう一歩*1というところなので、その点はご注意ください。

TypeScript環境の構築

前回と同じ環境を利用します。追加で以下のものをインストールします。

  • Node.js : 0.8.16
  • TypeScript : 0.8.1
  • JSDuck : 4.5.1


まず、最新のNode.jsをインストールします。(Ubuntuのデフォルトのリポジトリだと0.6という古いバージョンしかないので、リポジトリを追加してあげましょう。)

sudo add-apt-reposiotry ppa:richarvey/nodejs
sudo apt-get update
sudo apt-get install nodejs npm


続いてTypeScriptのインストールを行います。

sudo npm install -g typescript


Ext JSAPIリファレンスを生成するためのツールであるJSDuckもインストールします。(Rubyとgemが入ってない場合はあわせてインストールしてください。)

sudo gem install jsduck

IntelliJ IDEAでTypeScript

必須ではないですが、下記の記事を参考にしてIntelliJ IDEAでTypeScript開発を行う準備をします。

シンタックスハイライト、コード補完、関数定義へのジャンプなど、ある程度の機能は揃っているようです。

Sencha TouchのTypeScript用定義ファイル

TypeScriptからJavaScriptを呼び出す場合、アンビエント宣言*2の定義ファイルを用意する必要があります。拡張子が.d.tsになっているファイルですね。

jQueryやNode.jsの定義ファイルはすでに用意されているのですが、Ext JSのものは見当たりません。

と思って探していると、Ext JS用の定義ファイルを生成してくれるツールを見つけました。

C#+WinFormsで作られていますが、MonoがインストールされていればUbuntuでも動きます。


さっそく定義ファイルを生成してみたのですが、残念ながら生成したままのファイルは使えないようです。

exportやdeclareを追加したり、ドキュメントのないクラスのダミーを追加したり、moduleの階層構造を解決したり、いろいろ書き換えないといけない模様。

ちまちま書き換えてみたのですが、途中で面倒になったので自分でツールを書くことにしました。
Sencha Touch/Ext JSの開発者層を考えるとC#はないだろーと思いPythonで書き始めたのですが、せっかくなので途中からTypeScriptに書き換えました。


Node.jsで動作します。書きなぐりのコードでテストもありません><(TypeScriptのテストフレームワークは何を使うのが定番なんでしょうか・・・?)

初TypeScriptでしたが、C#Javaに慣れてればすんなり使えるように思えました。
といっても、今回は簡単なテキスト処理をしただけなので、TypeScriptの機能は全然使えてないんですけどね。

ちなみにTypeScriptでのコレクションの操作がよく分からなかったのでlinq.jsを使いました。LINQは空気。勝手知ったるライブラリがあると非常に助かります。

ExtJS2TypeScript

さて、このツールの使い方です。
まず、ExtJS2TypeScript.tsをコンパイルしてJavaScriptのファイルを生成します。

tsc ExtJS2TypeScript.ts


次にJSDuckを使ってSencha TouchのJSON形式のドキュメントを生成します。最初のオプションにSencha Touchのソースコードのディレクトリを指定、最後のオプションに出力先のディレクトリを指定します。

jsduck sencha-touch/src --builtin-classes --warnings=-no_doc,-dup_member,-link_ambiguous \\
  --external XMLHttpRequest --ignore-global --pretty-json --export=full --output jsondocs


生成されたJSONドキュメントのディレクトリを指定して、ExtJS2TypeScriptを実行します。出力結果はExt.d.tsというファイルになります。

node ExtJS2TypeScript.js ../jsondocs


生成したExt.d.tsをTypeScriptでコンパイルしてみると、プロパティと同じ名前の関数があるよとか怒られます。

Function or method 'getGroupString' already declared as a property
Duplicate identifier 'items'

とりあえずエラーがでなくなるまでコメントアウトしてしまいましょう。(本当はちゃんと対応しないといけないのですが・・・)

Sencha Touchアプリのビルド

いよいよSencha TouchアプリをTypeScriptでコンパイルします。

まず、前回の記事の中でSencha Cmdで生成したプロジェクトのディレクトリ(app.jsと同じ場所)に、Ext.d.tsをコピーします。

次にapp.jsをapp.tsというファイル名に変更し、先頭に下記の記述を追加します。

/// <reference path='Ext.d.ts'/>

JavaScriptのコードは、ほぼTypeScriptのコードとして動くので、コードの修正は不要です。


そして、app.tsをコンパイルしてみると・・・

Supplied parameters do not match any signature of call target

残念ながらエラー。

JavaScriptでは、関数で定義されている引数を渡さないことでオーバーロードのようなことを実現しています。

例えばExt.Loader.setPathという関数は、以下のように2種類の書き方ができます。

Ext.Loader.setPath('Advent','app');
Ext.Loader.setPath({
    'Ext': 'touch/src',
    'Advent': 'app'
});

しかし、Ext.d.tsでは次のような宣言しかしていません。

export interface LoaderStatic {
	:
    setPath(name:any /*String/Object*/, path:String) : Ext.LoaderStatic;
	:
}

TypeScriptにはオーバーロードがあるので、下記のように書き換えてあげます。

export interface LoaderStatic {
	:
    setPath(name:String, path:String) : Ext.LoaderStatic;
    setPath(name:any) : Ext.LoaderStatic;
	:
}

このあたりは自動化が難しいので手作業で修正することになります。つらい・・・(ドキュメントにはString/Objectと書いてあるので、これを考慮してオーバーロードすればよいかも?)

とりあえず、Sencha Cmdで生成したプロジェクトのひな形が動くように修正したExt.d.tsを置いておきます。(使っていない関数のオーバーロードは解決していません)

これでapp.tsをコンパイルするとapp.jsが生成されて、アプリが動くようになります!

TypeScriptのデバッグ

さて、JavaScriptを生成する系の言語で問題になるのが、生成後のJavaScriptを見てデバッグしないといけないことです。

TypeScriptから生成されたJavaScriptは比較的読みやすいとはいえ、できればTypeScriptのソースを見ながらデバッグしたい。

そこで、生成されたJavaScriptともとになったソースの関連付けを行うための技術としてSource Mapsというものがあります。

TypeScriptではコンパイルオプションに-sourcemapをつけることでapp.js.mapというファイルが生成されます。

tsc -sourcemap app.ts

生成されたjsファイルに下記のような記述が追加されていますね。

//@ sourceMappingURL=app.js.map


さぁ、IntelliJ IDEAでデバッグするぞ!と思ったのですが、現状のIntelliJ IDEAではSource Maps対応にまだ問題があるようです。
現在対応中のようなので楽しみにして待ちましょう。

おわりに

TypeScriptでSencha Touch開発する方法について紹介しました。
定義ファイルを用意するのはちょっと大変(もう全部anyって書いといてもいいんじゃないかという悪魔のささやきが・・・)ですが、今回作ったツールを使えば何とかなりそうな感じでもあります。

ただ、Sencha Touch(というかExt JS)は、引数に連想配列を渡すような関数が多いので、型を定義することでどれほどのメリットがあるのかまだ分かりません。もう少しコード書いてみないと何とも言えないです。


そして、Sencha Advent Calendarは無事に全枠が埋まりましたね!完走まであと少し!!

*1:生成した定義ファイルを手動で直す必要があるとか、関数のオーバーロードを解決してないとか

*2:C言語のプロトタイプ宣言のようなもの