• このエントリーをはてなブックマークに追加

本エントリは LL/ML Advent Calendar の第8日目(担当2)です.

はじめに

「L」「L」とか「M」「L」とかがタイトルにつくような斬新なネタが見つからなかったので,昔作った JavaScript を,初心者なりに Haxe で書き換えて見るようなことをしてみました.(ML名古屋 のときもそんなような発表をしてた気がしますがw)

「昔作った JavaScript」については,以下あたりをご参照ください:

では,JavaScript で書いたロジックを Haxe へ移行する際の個人的なポイントなんかを交えて以下.

生成された JavaScript の実行

上部に表示されているマリオさんは,次のようなコードで召喚しています:

$(function () {
    var $e = $($('.entryheader')[0]);
    var mario = new Mario({
        x:     $e.offset().left + 50,
        y:     $e.offset().top + 2,
        scale: 2
    });
    var c = new Mario.Controller({ target: mario });
 
    mario.STATUS = 'SUPER';
 
    setTimeout(function () {
        mario.jump();
    }, 1000);
});

生成された JavaScript については,https://raw.github.com/issm/mario/master/haxe/bin/mario.js あたりをご覧ください.

参考

次のサイトとエディタを行ったり来たりしながら Haxe してました:

「Haxe/JavaScriptチュートリアル」を一読(少なくとも 5章あたりまで)すれば,それなりに Haxe を始められる感じになりますよ.

(フロントエンド向け) JavaScript 目的で Haxe するのに肝心な DOM 操作とかについては今後の充実が期待されますが,これだけのドキュメントを準備してくれた @terurou++ です!

構成

先のチュートリアルを参考に,次のような構成としました:

haxe
├── bin
│   ├── index.html -- ブラウザでの確認用ページ
│   ├── mario.js   -- 生成されるJavaScript
│   └── mario.js.map
├── build.hxml
└── src
    └── mario
        ├── Controller.hx -- マリオさんのキーボード操作に関するクラス
        ├── Env.hx        -- 重力加速度とかの「環境」に関するクラス
        ├── Image.hx      -- マリオさんの画像データを扱うクラス
        ├── Luigi.hx      -- 弟さんのクラス
        ├── Main.hx       -- エントリポイントなクラス
        ├── Mario.hx      -- マリオさん
        └── Util.hx       -- ユーティリティクラス

hxml ファイルはこんな感じ:

-js bin/mario.js
-cp src
-main mario.Main
--js-modern
-debug

ディレクトリ haxe に入って haxe build.hxml すればコンパイルされて,bin/mario.js が生成されます.

Haxe に移行するにあたっての個人的なポイント

JavaScript 脳な Haxe 初心者の目線から.

Window オブジェクトを取得する

こんなことも知らない初心者でスミマセン><

import js.Lib;
var w = Lib.window;

型指定がわからない

Dynamic 型を指定しておけば,任意の値をセットできるっぽいです.

var any : Dynamic;

移行の初期,まずは動作させたいとき,とりあえず多用してましたが,やはり可能な限り,型の範囲を狭めた方が安心できますね!

イベントリスナーをセットする

import js.Lib;
import js.Dom;
 
Lib.window.document.onkeydown = function ( ev : Event ) {
    ...
};

JavaScript での on... な方法に相当する感じですね.

HtmlDom クラスのドキュメント を見る限り見当たらないけど,addEventListener 的な方法はあるのかなぁ...

動的にプロパティを操作する

JavaScript ではあたり前に行っている myobj.foobar = 'hogefuga'; のような操作,Haxe では,

class MyClass {
     public var foobar : String;
}

のように予め定義しておかないと怒られます.

また,var foo = 'foobar'; var hoge = myobj[foo]; をそのまま Haxe でやろうとすると,これまた怒られます.

これらは,Reflect クラスを利用することで解決できます.

Reflect.setField( myobj, 'foobar', 'hogefuga' );
 
var foo : String = 'foobar';
var hoge : String = Reflect.field( myobj, foo );

これに関する情報は,次のエントリで得られました:

タイマーを使う

Timer クラスを利用します.

JavaScript での setTimeout は,次のように書き換えられます:

import haxe.Timer;
 
var timer : Timer = new Timer(1000);
timer.delay(
    function () {
        ....
    },
    1000
)

setInterval に相当する処理は,Timer クラスのサブクラスを準備して run メソッドをオーバライドして...みたいな感じで現状の設計を維持できない予感がしたので,後述する untyped を使って逃げましたw

...
var self = this;
untyped clearInterval(this.TIMER_ANIMATION);
this.TIMER_ANIMATION = untyped setInterval(
    function () {
        self.FRAME_ANIMATION = ++self.FRAME_ANIMATION % frames;
        self.set_bg_position( key, index, self.FRAME_ANIMATION );
    },
    this.INTERVAL_ANIMATION
);
...

エントリポイントでグローバルな名前空間に関連づける

以前書いたものは,var mario = new Mario() のように,Mario クラスをグローバルに利用できます.

Haxe の場合,--js-modern オプションをつけて JavaScript にコンパイルすると,(function () { ... })() で包まれてグローバルにならなくなります.とはいえ,同オプションを外すのもアレ.

なので,先の Reflect クラスを使って次のようにすることで解決しました:

package mario;
import js.Lib;
import js.Dom;
 
class Main {
    static function main () {
        var w : Window = Lib.window;
        Reflect.setField( w, 'Mario', mario.Mario );
        Reflect.setField( w, 'Luigi', mario.Luigi );
        Reflect.setField( mario.Mario, 'Controller', mario.Controller );
    }
}

最後の手段(?) untyped キーワード

キーワード untyped を使えば,JavaScript で書くようなコードをそれなりにそのまま使えるみたいです.

window.Mario = mario.Luigi;          // NG
untyped window.Mario = mario.Luigi;  // OK
 
window['document'].getElementsByTagName('body');          // NG
untyped window['document'].getElementsByTagName('body');  // OK

Haxe 脳ができていない私にとって非常に魅力的なキーワードではありますが,なんのために Haxe で書いてるの?みたいな感じになってきそうなので,乱用は避けたいところです.

これは何型?

class MyClass {
    private static var KEYCODE = {
        LEFT:  37,
        RIGHT: 39,
        UP:    38,
        DOWN:  40,
    };
}

みたいに書けたのだけど,この変数 KEYCODE が何型(クラス?)になるのかがわかりません>< どなたか教えてください!(JavaScript での typeof 的なものがあるのかどうかもわからない><)

ビルドとか確認用サーバとかは Perl で

このへんは使い慣れたツールをw

簡易的継続コンパイル

App::watcher を使えば簡単ですね!

% cd haxe
% watcher --dir src -- haxe build.hxml
[2012-12-08T07:59:43] watching: src
[2012-12-08T07:59:43] Forked process: haxe build.hxml

ソースコードが更新されれば haxe build.hxml が走ります:

[2012-12-08T07:59:58] -- /Users/issm/work/javascript/mario/haxe/src/mario/Main.hx
[2012-12-08T07:59:58] Killing the existing process by TERM (pid:48049)
[2012-12-08T07:59:58] Successfully killed! Restarting the new process.
[2012-12-08T07:59:58] Forked process: haxe build.hxml

確認用のサーバ

Static ミドルウェア を有効にして plackup すれば簡単ですね!

% plackup -e 'enable "Static", path => "/", root => "./haxe/bin/"'
HTTP::Server::PSGI: Accepting connections at http://0:5000/

http://localhost:5000/index.html にアクセスすれば,bin/index.html が読み込まれます:

ss-1354921031

おわりに

以上,昔書いた マリオさんを召喚する JavaScript を Haxe に移行してみたお話でした.

今回の作業は Haxe に触れるよいチュートリアルになったと思うので,引き続き Haxe 脳を育んでいきたいな,と.