JavaScript で作るストアアプリにもBehaviorが!

この内容は Windows 8.1 Preview および Visual Studio 2013 Preview (の Blend) をもとに記述しています。

Windows 8 Dveloper Preview が出てから、 WinRT アプリ for XAML がでてから、ずーっと Behavior が欲しくって、でも DependencyObjectCollection がなく DataContext の伝搬ができないので、まっとうなのが作れないので、諦めてたのですが、今回からやっとこさ DependencyObjectCollection が乗りましたね。ちょっと Generic じゃないのが気になりますが、良しとしましょう。

で、MSDN ながめても Behavior らしきものはなかったので、作ろうかと思ってるところに乗ってるよって噂を聞いたので探してみました。結論的には XAML のほうはまだ見つかってません。 Build のビデオをまだ見切ってないのと、 Blend をいじり倒してないのであるかどうかは不明ですが。

上記で "XAML のほう" はって書いたのにはわけがあって、っていうかもうすでにタイトルにつけているのですが、なんと JavaScript のほうには乗ってました。予想外です。

以下のように Blend の「アセット」ペインをみるとしっかりと表示されています。

f:id:k_maru:20130630004354p:plain

ということで、さっそく使ってみました。使い方は簡単で、いつものように画面にドラッグドロップするだけ。今回はとりあえずボタンを置いて ToggleClassAction を貼り付けてみました。たぶん、何かのイベント(予想ではデフォルトは click で後から変更可能) をもとに指定した class 属性の値を設定、解除できると思われます。で、出来上がった HTML は以下のような感じ。

<button type="button" 
    data-blend-behavior="BehaviorActionTree_1">button</button>

data-blend-bahavior 属性に BehaviorActionTree_1 ってのが設定されていますが、詳細はなにも書かれてません。あれーっと思いながらプロジェクトの中を眺めてると actionList.json ってファイルが追加されてます。

f:id:k_maru:20130630005125p:plain

で、この中を見るとありました。

[
  {
    "name": "BehaviorActionTree_1",
    "behaviors": [
      {
        "type": "Blend.Behaviors.EventTriggerBehavior",
        "event": "click",
        "triggeredActions": [
          {
            "type": "Blend.Actions.ToggleClassAction"
          }
        ]
      }
    ]
  }
]

もう、この時点で「コレジャナイ」感が満載なわけですが、そこはめげずに見ていきましょう。

Silverlight/WPF では Behavior と一括りでいってますが、実際のところは "Behavior" と "Trigger/Action の組"のどちらかを設定します。が、こちらでは Trigger はなくなって TriggerBehavior って形になってますね。

読んでいくと、 最上位の name ってのが HTML の data-blend-behavior 属性の値と同じになってます。どうやら、この値を一致させて、同階層にある behaviors の内容を適用する模様。で、 behaviors の中は配列になっているので、同じ値で複数の behavior が設定できるのですね。

で、一番上の type が Blend.Behaviors.EventTriggerBehavior になってるので、「イベントを受けて何かを動かす振る舞い」ってことでしょう。 event の値は click になってます。なので、 「click を受けて何かを動かす振る舞い」ってことですね。

「何かを動かす振る舞い」の部分はその下の、triggerdActions の部分ですね。ここも配列なので複数の振る舞いを設定できるようですが、一つ目に画面で指定した Blend.Actions.ToggleClassActionが出てきます。ってことで、ToggleClassActionはそのままでクラスをトグルしてくれるので「イベントを受けて、クラスをトグルする振る舞い」って感じで指定されています。

さて、ここまで見てきてあれなのですが、指定の中に「何ていう」クラスをトグルするかは出てきてません。謎です。っていうか、どう考えても必ず指定が必要なのでどうにかして描けるはずです。で、HTML なのでどこかにソースがあるはずと思ってもう一回プロジェクトをよく見たらありました。"Blend.Runtime.1.0.js" ってのが、 js フォルダの直下に。再び「コレジャナイ」感が満載なわけですが、そこもめげずに見ていきましょう。

とりあえず、開いて「ToggleClassAction」を検索してみると、ありました。className ってプロパティを指定しろっぽいことが書かれてます。ほかには targetSelector ってのもありますね。

{
  // Property Meta-data (for JSON parsing)
  className: { type: String },
  targetSelector: { type: String }
}

っていうことで、className を追加してみました。値は "behavior-on" ってしてます。

"triggeredActions": [
  {
    "type": "Blend.Actions.ToggleClassAction",
    "className": "behavior-on"
  }
]

上記で指定した "behavior-on" って値が、ボタンをクリックするたびについたり消えたりするはずです。とりあえず、css で .behavior-on ってクラス名には背景章を赤で指定しておきました。

.behavior-on{
  background-color: red;
}

で、実行してボタンを押してみます。

f:id:k_maru:20130630011624p:plain

無事にクリックしたら、赤になって、もう一回クリックしたらもとに戻りました。

さて、ここまでで使い方は何となくわかりました。「コレジャナイ」感がもう絶頂なんですが、なんやかんや言うても作れもしないのに文句は言っちゃダメと思い、とりあえず作ってみました。もちろん作り方はソースをよみます(長くなっているので作り方はまた別途っていうことで)

作ったコードは以下のような感じ。ダイアログを上げるだけですね。

WinJS.Namespace.define("App.Actions", {
  AlertAction: WinJS.Class.derive(Blend.Actions.ActionBase,
    function () {
    }, {
      message: "",
      execute: function () {
        Windows.UI.Popups.MessageDialog(this.message).showAsync();
      }
    }, {
      message: { type: String }
    })
});

上記で作ったものを定義に追加すると以下のような感じになります。

"triggeredActions": [
  {
    "type": "Blend.Actions.ToggleClassAction",
    "className": "behavior-on"
  }, {
    "type": "App.Actions.AlertAction",
    "message": "Hello WinJS-Blend Behavior!"
  }
]

動かすと無事にダイアログが表示されました。

f:id:k_maru:20130630012637p:plain

まぁ、最後ちょっと飛ばしまくりでした、そのうちキレイにまとめます。


まぁ、何回も書いている通り「コレジャナイ」感でいっぱいなので、使うか使わないかは今後のなりゆき次第かなと。しかし、 XAML のほうの Behavior はどないのっとるのか・・・。

grunt-typescript を TypeScript 0.9 に対応

この内容は TypeScript 0.9.0 および grunt-typescript 0.2.0 をもとに記述しています。

つい先日、TypeScript の大きなアップデートであるバージョン 0.9.0 が公開されましたね。generic や enum 、Overload on Constantsなどなどいろんな新機能が入ってます。

grunt-typescript のほうも TypeScript 0.9.0 に早々に対応しています。まぁ、ちょこちょことリポジトリから最新のソースを取って手動コンパイルしてたので TypeScript がリリースされたら、そのまま grunt-typescript も公開って形だけだったのですが。

で、ちょこちょこと追いかけてた TypeScript のソースでが、0.8.3 のころから比べると劇的に変わってます。 grunt-typescript はもともとコンパイル後の tsc.js を解析して、必要な処理を抜き出して変更して作ってたのですが諦めてしまいました。今回からはコンパイル前の tsc.ts と io.ts を参考に TypeScript で記述するように変更しました。そうしたらこれがめっちゃ楽。手動コンパイルした時に同時に生成される tsc.d.ts を参照させればある程度ですが、 WebStorm でもインテリセンスが効くし、コンパイルも怒られないし、処理フローは合わせられるし、っていうかそもそもファイルIO絡みのところはほとんど変えなくてすむし。

初めからそうしとけばよかった。。。

但し、tsc の通常の動きにはない grunt-typescript 独自の動きのところは壊れないように結構神経使いました。独自の部分は以下。

js のファイル階層を維持して、別フォルダに出力できる(っていうかそうなる)

※実はこれウソでした。正しい内容は「tsc で out オプションを付けても階層維持されてた。」に書きました。

コマンドラインの tsc に out オプションにフォルダを指定した場合って、複数のフォルダ階層の ts ファイルは全部オプションで指定したフォルダにフラットで出力されます。ちょっとこれが気に食わなかったので grunt-typescript では dest オプションにフォルダが指定されていた場合は、カレントディレクトリから対象となるそれぞれの ts までのフォルダを出力フォルダに作成して、階層を維持したまま出力します。
例えば以下のような構成で ts があったとします。

src
 - core
    - a.ts
    - b.ts
 - base
    - c.ts 
build

こいつを tsc の out オプションで build フォルダに出した場合は以下のようになります。

src
 - core
    - a.ts
    - b.ts
 - base
    - c.ts 
build
 - a.js
 - b.js
 - c.js 

でも、grunt-typescript の場合は、dest オプションに build フォルダを指定した場合は以下のようにフォルダ階層は維持されます。

src
 - core
    - a.ts
    - b.ts
 - base
    - c.ts 
build
 - src
   - core
      - a.js
      - b.js
   - base
      - c.js 
ベースパスの指定

上述したようにフォルダ階層は維持されるのですが、出力フォルダに src フォルダまで作成されています。これはこれで気持ち悪いのでそれを出力しないようにするための base_path オプションがあります。こいつに "src" を指定すると、そのフォルダは出力時に飛ばすようになっています。なので以下のような感じに出力されます。

src
 - core
    - a.ts
    - b.ts
 - base
    - c.ts 
build
 - core
    - a.js
    - b.js
 - base
    - c.js 

まぁ、現状超手抜きで base_path に指定された文字列の長さを、出力するフォルダ名の前から削除してるだけなので、例えば base_path に AAA と指定しても動作するのですが、、、これは将来的には直します。

ソースマップの絶対パスでの出力

さて、そんなこんなで上記のように出力するフォルダをいじっていたら、ソースマップに指定されるパスがおかしいと指摘を受けました。そらそうです。そもそもコンパイル側が意図していないフォルダに出力するように処理を置き換えてるので。ってことで、ソースマップの出力が指定されている場合、コンパイル完了後に力技で出力されたファイルを書き換える処理が入ってます。結構豪快です。
あと、ついで指摘を貰った時に fullSourceMapPath オプションにも対応してほしいみたいな要望も含まれてたので、ついでに対応しました。 fullSourceMapPath って tsc のコマンドラインのヘルプからは見えない(experimental になっている) オプションで、ソースマップに出力される参照先のファイルのパスが絶対パスになるようにするオプションです。

型チェックの場合でもJSファイルが出力される(gruntはエラーにならない)

これは、tsc のコマンドでは普通の動きです。ただ、grunt-typescript はいままでその動きを考慮しておらず、とりあえずコンパイラがなにかエラーを返して来たら、 grunt もエラーになるように組んでいました。(というか、今まではタイプチェックのみエラーみたいな処理がどこに記述されているか読み解けなかったって落ちなのですが・・・。)
で、http://hatz48.hatenablog.com/entry/2013/06/05/140324#comment-11696248318754873269 で型チェックの場合も JS ファイルが出力されるようにしたい、してみた、って記事をあげていただいていたので、今回から勝手に(コメント欄で事後承諾は入れてます)採用させてもらいました。記事にあげていただいていた通り ignoreTypeCheck オプションに true を設定することでエラーにならずに JS ファイルを出力しているようにします。デフォルトは今までの動作を維持するために false です。

但し、

ってのを頂いているので、判定方法とかでバグがあるかも。。今度見てみよう。。


いまのところ本家の main ブランチや release-0.9.0 ブランチに動きがないのでお休み中かなって印象なので、こちらも不具合とかない限りお休みですかねぇ。でも develop ブランチはそれなりに動いてて、多分これは次バージョンの開発やと思うんやけど、またまた結構な量の変更が入ってるのでちょこちょこと確認はしないとですね。

あっ、あと何個かもらってる Issue に反応できてないや・・・。

TypeScriptのコンパイルに node も Visual Studio も必要ない(Windowsでは)

この内容は TypeScript 0.8.2 をもとに記述しています。

TypeScriptをコンパイルするための環境を作るのに Windows 環境でかつ Visual Studio を利用されている型は TypeScript for Visual Studio 2012 をインストールするか、またはそれ以外の環境では node をインストールして npm から typescript を取ってくる(npm install -g typescript)かします。

今回は実はその両方とも必要ないってお話です。メモ帳とIEさえあれば git も node も Visual Studio もいらないってお話です。但し Windows 限定。

TypeScript のコンパイラJavaScript で書かれているってのはそこらへんに書かれてる話ですが、じゃ、その JavaScript の実行環境は何を利用されているかっていうと、node は有名どころとして、実は WSH も実行環境として動作するように構築されています。

tsc.js という、 tsc コマンドの実装部分のファイルの 25084 行目あたりを確認すると以下のようになっています。

if(typeof ActiveXObject === "function") {
    return getWindowsScriptHostIO();
} else {
    if(typeof require === "function") {
        return getNodeIO();
    } else {
        return null;
    }
}

ActiveXObject があると getWindowsScriptHostIO 関数を呼んでます。つまり、WSH を利用する IO 処理です。

ということで、コマンドラインから cscript でこの tsc.js を呼んでやると、 typeof ActiveXObject === "function" が true になって、 WSH を利用する IO が利用されます。

ということで、バッチファイルを作ってみましょう。

まずはブラウザで TypeScript のリポジトリを開いて、左のツリーから [bin] - [tsc.js] を選択します。以下のような感じ。
f:id:k_maru:20130203193755p:plain
で、右ペインに表示されている tsc.js のコードをコピーして、ローカルのファイルとして保存します。ファイル名はもちろん tsc.js ですね。

次にブラウザで tsc.js と同じ階層にある、 lib.d.ts も同じようにコードをコピーして、ローカルにファイルとして保存します。もちろん tsc.js と同じディレクトリです。

f:id:k_maru:20130203194235p:plain

で、同じディレクトリにバッチファイルを作ります。ここでは tscw.cmd としておきます。中は以下の一行で。

cscript "%~dp0\tsc.js" %*

あとは、このディレクトリに Path を通して、作った tscw.cmd がどこからでも叩けるようにしておきましょう。tscw をコマンドラインから叩いたスクリーンショットを載せておきます。

f:id:k_maru:20130203194940p:plain

node や TypeScript for Visual Studio 2012 のインストールが難しい方はご利用ください。

ちなみに TypeScript for Visual Studio 2012 で入る tsc.exe も内部的には WSH を利用して動かしてるみたいです。 tsc.exe と同じフォルダにある tsc.js の先に書いた JavaScript の判定部分を以下のようにして、 tsc.exe を実行すると見事に ActiveX のほうのコードが通っていることを確認できると思います。

if(typeof ActiveXObject === "function") {
    // 以下追加
    WScript.StdOut.Write("ActiveX");
    return getWindowsScriptHostIO();
} else {
    if(typeof require === "function") {
        //以下追加
        console.log("node");
        return getNodeIO();
    } else {
        return null;
    }
}

TypyJS の概要

前回、 TypyJS というライブラリをオープンにしましたと書きましたが、今回はそれの概要です。

前にも書いた通り TypyJS は prototype を拡張しません。 TypyJS の関数を利用するには、基本的には型ごとのオブジェクトでラップします。例えば、 String をラップするのは以下のような感じ。

var str = Typy.str("FooBar");

これで str 変数に TypyStr オブジェクトが設定されます。で、あとは TypyStr オブジェクトの関数を呼ぶだけ。

var str = Typy.str("FooBar");
str = str.lower();

基本的に各関数の戻り値の型が、ラップしているオブジェクトの対象としている型の場合(TypyStrオブジェクトの場合は String 型)は、関数を実行した結果の値が返るのではなく、自身のインスタンスを返します。つまり、チェーンして記述が可能です。

var str = Typy.str("FooBar");
// ↓ upper() 関数の戻り値の型は TypyStr
str = str.lower().upper();

じゃぁ、関数が適用された最終的な値はどうやって取るかというと、val() 関数を利用します。 val() 関数は基本的には組み込みの型の値を返します。

var str = Typy.str("FooBar");
// ↓ val() 関数の戻り値の型は String。この例では "FOOBAR" が返る。
str = str.lower().upper().val();

チェーンして書けて、val() 関数で元の型の値をとるということから、基本的にはラップしているオブジェクトを変数にとることはあまり想定していません。

var str = Typy.str("FooBar").lower().upper().val();

配列とかの操作には便利ですね。ちなみに配列の時は TypyArray を利用します。

var ary = Typy.array([1,2,3,4,[5,6,7],8,9]).
            flatten().
            filter(function(...){...}).
            map(function(...){...}).val();

関数の戻り値が対象としている型ではない場合(厳密には対象としている型を返さない可能性がある関数)は、元の値をそのまま返します。 TypyArray の reduce() 関数とかがそうですね。

var val = Typy.array([1,2,3,4,[5,6,7],8,9]).
            flatten().
            filter(function(...){...}).
            map(function(...){...}).
            reduce(function(...){...});

でも、実際には戻り値の型をもとに自動的にラップするオブジェクトを自動的に判定して、そのままチェーンしていきたい場合もあります。その場合には、各関数に設定されている x() 関数を利用します。

var val = Typy.array([1,2,3,4,[5,6,7],8,9]).
            flatten().
            filter(function(...){...}).
            map(function(...){...}).
            reduce.x(function(...){...}).  //←例えば文字列が返る
            lower().val();

実はここ結構悩ましいところで、実は x() 関数 を使わずに、常に自動的にラップするオブジェクトを自動的に判定したほうがいいのか難しいところです。日付である Date 型を対象とした TypyDate に year() 関数とかがあるのですが、これは素直に値をとりたいところなんで、時と場合によるんですね。そのうち変わってるかもしれません。

チェーンしたくない場合などは毎回ラップすのは結構ばかばかしいです。なので、各関数は基本的には素でよべるようにもしてあります。その場合は、第1引数に対象となる値を設定します。前述の lower() 関数とか upper() 関数とかを素で呼ぶと以下のような感じです。

var str = "FooBar";
str = Typy.str.lower(str);
str = Typy.str.upper(str);

多くの関数は 2 way で使えるようになってるので、いい感じに使い分けてやってください。


ここまでで、ざっくりと使い方を記述しましたが、Typy が今のところ対象としている型は以下があります。

  • TypyObj → Typy.obj()
  • TypyArray → Typy.array()
  • TypyStr → Typy.str()
  • TypyDate → Typy.date()
  • TypyFunc → Typy.func()
  • TypyNum → Typy.num()

これの他に独自の型として以下があります。

  • TypyUuid → Typy.uuid()
  • TypyUri → Typy.uri()
  • TypyTimeSpan → Typy.timeSpan()
  • TypyAny → Typy.any()

また、型ではありませんが、ユーティリティとして以下のようなものがあります。

  • Typy
  • Typy.lang
  • Typy.ext

そのうち、ちゃんとドキュメント書きたいなぁと思ってるのですが、いつになることやら。

最後に TypyJS で動作確認している実行環境は以下の通りです。現存する多くのブラウザで動作すると思われます。

  • IE 10
  • IE 10 の ドキュメントモードを変更して Quirks / 7 / 8 / 9 モード
  • Windows 版 Google chrome / Firefox / Opera の最新版
  • node.js の最新版
  • WinRT アプリ for JavaScript

JavaScript ライブラリの TypyJS をオープンにしました。

今日、お盆のころからちょこちょこ作ってた JavaScript ライブラリの TypyJS をオープンにしました。
今のところ、まだまだ粗いし、ガリガリ変更とかしたいので Fork は許可してないのですが、コードは見れるし、ダウンロードもできるようにしました。

https://bitbucket.org/k_maru/typyjs

自分の趣味で作ってて、個人用のプライベートなリポジトリが欲しいなと思い bitbucket を使っていたのですが、そのうちまとまったら GitHub に移すかもしれません。

さて、このライブラリは何をしてくれるライブラリかというと、値を便利に操作するユーティリティ群です。すでに世の中にいろんなライブラリが出回ってますね。思いっきり車輪の再発明です。

なぜ、こんなものを作ろうと思ったのかを順番に書いてみます。まぁ、とは言ってもある意味で「興味・趣味」ってのが一番大きな理由なのですが・・・。

世の中に出回っている JavaScript ライブラリを大きく分けると以下の二つに分かれます。

  • フルスタックのライブラリ
  • 機能特化のライブラリ

フルスタックのライブラリは Ext JS とか有名ですね。ここらへんのフルスタックのライブラリは、とっても便利で強力な機能とかを持ってるんですが、如何せんそれ以外が使えなくなってしまう。ちょっと外れたことをしたいと思ったらかなり頑張らないとダメ。なのでほとんど使ったことがないです。

機能特化のライブラリは有名なのが多いですね。古くは Prototype から始まって、デファクトスタンダードとなった jQuery とかとかとか。 Microjs とかでよく探したりします。

自分でアプリケーションを作るときは、多くの場合 jQuery は暗黙の了解な勢いで使ってます。ただ JavaScript で操作したい内容ってのは DOM と オブジェクトなのですよね。で、 jQuery は基本的には DOM の操作用のライブラリです。で、オブジェクトの操作は何を使うかなんですね。ここなんですね。悩ましいのは。

まぁ、とりあえずこちらもよく使われている underscore.js を使うとして、でもこれってコレクションの操作とかちょっとしたユーティリティが大半です。なので足りないので文字列操作に underscore.string とか、日付操作は  Moment.js 、あれ?数値操作どうしようとか。とかとか。

なので、以下のような形(右下のオブジェクト操作の部分がワラワラしている)になってるのが現状だと思ってます。

f:id:k_maru:20121208005636p:plain

この部分を満たしてくれるライブラリもあるにはあります。個人的にいい感じなのは以下ですかね。

ただ、これどちらも prototype を拡張するスタイルなんですよね。まぁ、いまのご時世そこまでプロトタイプ汚染がどうのとかって口うるさく言うのもどうかと思うのですが、まだちょっと早いかなぁって気もしてます。 Object.create とかの ES5 がほとんどの世界になったら気兼ねしないんですがね。早く来へんかなぁ。

まぁ、グダグダ書きましたが、オブジェクトの操作の部分を単一で、かつ prototype 拡張してないのが見つからなかったので作った。っていうのが理由です。


また、次回、簡単な使い方を書きます。そのうちドキュメントとかも書こうと思ってるのですが、いつになることやら。

Grunt.js で TypeScript をコンパイルする grunt-typescript を作りました。と、公開しました。

昨日、TypeScript が公開されて、かなりこれは来た感が満載なわけです。お盆のころからちょこちょこ作って、もうそろそろ公開しようかなと思ってた 2000 行近い JavaScript のライブラリを全部書き直そうかって衝動に駆られるくらいな勢いです。まぁ、どこがイケてるのか、どういう言語なのか、使ってみました的なことはいろんな所で書かれているので、ここでは違った角度の話をかこうと思います。

で、TypeScript をいろいろ試したいと思っても環境が VS2012 のアドインで、提供されているプロジェクトテンプレートが C# のプロジェクトを潰した奴だとか、普通の Web プロジェクトにアイテムテンプレートで ts ファイルを追加してもコンパイルされないとか、 Web Essentials の更新当てても Windows Store アプリのプロジェクトだと ts ファイル編集しにかかった瞬間に VS ごと落ちるとか、結局変更するたびにコマンドライン叩かなダメとか書いた後の実行までがめんどくさい。

ってことで、コンパイルを何とか Grunt.js で実行できないかと頑張ってみました。これで grunt watch しておけば変更するたんびにコンパイルされて確認も実行もお手軽です。で、結論としてコンパイルできて npm にも登録したので、どうやって実行しているかのお話を。

まず前提として、 Grunt.js で動かすので node.js で動かす必要があります。かつ、Grunt.js のカスタムタスクで npm からとってきた状態で動かしたかったので、 npm で公開されている TypeScript のコンパイラーが必要です。ちなみに TypeScript を npm でとってくるには以下のコマンドです。

npm install -g typescript

で、こいつでとってこれたフォルダの下の bin フォルダの下に typescript.js ってので使ってます。ただしこいつは、 node のモジュールとしてエクスポートされてないので require しても何も返ってきません。なので、ソースを直接呼んで取り込む必要がありました。以下のような感じ。

var fs = require('fs'),
    vm = require('vm'),
    code = fs.readFileSync('typescript.js'),
    ts;

vm.runInThisContext(code, 'typescript.js');
ts = TypeScript;

これで TypeScript オブジェクトは取得できるんで、あとはソースをゴリゴリ読んでコンパイルの実行を行うだけです。コンパイルを実行しているオブジェクトはすぐに見つかりました。 TypeScriptCompiler ってのがそれです。こいつを new して、ゴネゴネってすればコンパイルされました。

var outfile = {
  source: '',
  // どうやらコンパイル後のコードはこの関数に渡されるらしいので、連結。
  Write: function(s) {this.source += s;},
  WriteLine: function(s) {this.source += s + "\r\n";},
  Close: function(){}
};
var outerr = {
  Write: function(s) {},
  WriteLine: function(s) {},
  Close: function() {},
};

// TypeScriptCompiler をインスタンス
var compiler = new ts.TypeScriptCompiler(outfile, outerr);

// 第1引数に TypeScript コードを渡す。 第2引数にはファイル名。なんでもイイっぽい。
compiler.addUnit('var example = (a) => a;', 'example.ts');

// コンパイルを実行
compiler.emit(false, function(){ return outfile;});

// コンパイル後のソース
// var example = function(a) {
//   return a;
// }
var js = outfile.source;

こんな感じです。詳細はまだ TypeScript のコードを読み込んでないので読み込んであらかた理解できて、勢いがあればまた書きます。

さてさて、こんな感じでコンパイルできることが分かったのであとは Grunt.js のカスタムタスク書いて、 npm で公開するだけです。コードは GitHub に公開してますのでどうぞ。

git@github.com:k-maru/grunt-typescript.git

npm で取得するには以下のようにします。

npm install grunt-typescript

Grunt.js は以下のような感じで記述。こいつと watch タスク組み合わせればめちゃ便利。

grunt.initConfig({
    ...
    typescript: {
      base: {
        src: ['path/to/typescript/files/*.ts'],
        dest: 'where/you/want/your/js/files'
      }
    },
    ...
});

まずは、最低限動きますよって状態なので、オプションとか設定できないので追々更新していきます。たぶん、まずはタスクのコードを TypeScript で書くところからですかね。

ちなみ、node.js はあまり詳しくなく Grunt.js のカスタムタスクと npm への登録については今回初めてなので、いろいろイケてないところとか不具合とか、もっとこうすれば簡単などなどフィードバック募集中です。

JSON のルーズなパーサー作りました。

設定系とかで JSON を使うときにいちいちクォーテーション(またはダブルクォーテーション)で囲むのめんどくさいなと思って、いい感じに勝手に判断してくれるやつ作りました。以下の gist に貼り付けてます。

https://gist.github.com/3258401

これを作るときに本家の json_parse を大いにパク・・・参考にさせて頂きました。
ちなみに本家は以下。
https://github.com/douglascrockford/JSON-js/

今回作ったやつは、これが

{"param1":"value1","param":"value2"}

こんな風に書けます。

{param1: value1, param: value2}

数値にできそうな奴(1234 とか .5 とか)は number に、true / false は boolean に、 null は null に、 あと標準の JSON には無い NaN とか undefined とか Infinity にも対応してます。undefined が要るのかって話は置いときますが。。

途中まではオブジェクトのキーにオブジェクトとか設定できるようにしてたんですが、それはやっぱりやり過ぎかなと思って無しにしました。

で、速度的にはどうなんやろうと思って試してみました。対象は今回作ったやつと、元ネタと、JSON2.js の JSON.parse と IE9 のネイティブの JSON.parse の 4 種類です。

こんな感じのコードでチェック。QUnit なのはご愛嬌ということで。

var start = new Date().getTime(),
    i = 0, length = 100000;

for(i = 0; i < length; i++){
    JSON.parseLoose("{A: { key1:val1, key2: val2  foo, key3: val3}, B: [foo, bar, true, null, 200, 0.5]}");
}
ok(true, "parseLoose = " + ((new Date().getTime() - start) / 1000) + "(seconds)");

start = new Date().getTime();
for(i = 0; i < length; i++){
    json_parse("{\"A\": { \"key1\":\"val1\", \"key2\": \"val2  foo\", \"key3\": \"val3\"}, \"B\" : [\"foo\", \"bar\", true, null, 200, 0.5]}");
}
ok(true, "json_parse = " + ((new Date().getTime() - start) / 1000) + "(seconds)");

start = new Date().getTime();
for(i = 0; i < length; i++){
    JSON2.parse("{\"A\": { \"key1\":\"val1\", \"key2\": \"val2  foo\", \"key3\": \"val3\"}, \"B\" : [\"foo\", \"bar\", true, null, 200, 0.5]}");
}
ok(true, "JSON2 = " + ((new Date().getTime() - start) / 1000) + "(seconds)");

start = new Date().getTime();
for(i = 0; i < length; i++){
    JSON.parse("{\"A\": { \"key1\":\"val1\", \"key2\": \"val2  foo\", \"key3\": \"val3\"}, \"B\" : [\"foo\", \"bar\", true, null, 200, 0.5]}");
}
ok(true, "parse = " + ((new Date().getTime() - start) / 1000) + "(seconds)");

結果は以下のような感じ。

1.parseLoose = 2.995(seconds)
2.json_parse = 1.749(seconds)
3.JSON2 = 0.94(seconds)
4.parse = 0.265(seconds)

今回作ったのが、一番遅くて IE9 の JSON.parse の 10 倍遅いですね。。

html の data-* 属性使って設定を書くときとか結構便利やと思って作ったんですが、10 倍って数字見ると悩みますね。