JSX で生成されたコードを動的ロードしてみた

JSX は静的言語なので、JavaScript のように必要になったら動的にスクリプトをロードして、そのスクリプトに含まれるであろうオブジェクトにアクセスするのには不向きです。
ですが、そういうことをしたいねという話があったのでデモを作ってみました。
Sample for dynamic loading JavaScript generated by JSX

上記デモは3つの JavaScript から成っており、それぞれメインとなる JavaScript を実行するもの、Programmer というクラスを定義するもの、Designer というクラスを定義するものとなっています。
ボタンを押すとメインとなる JavaScript がそれぞれのクラスが定義された JavaScript をロードし、JSX で定義したメソッド (Human#introduce) を実行します。

どうなっているか?

それぞれの JSX のコードの繋ぎとなっているのが Human.jsx という次のようなコードです。

import "js/web.jsx";

class Human {
    var name : string;

    // called from constructor of sub class
    // unless sub class calls the constructor of super class explicitly
    function constructor() {}

    function constructor(params : Map.<variant>) {
        this.name = params["name"] as string;
    }

    function introduce() : void {
        this._say("My name is " + this.name);
    }

    function _say(msg : string) : void {
        dom.window.alert(msg);
    }
}

main.jsx も Programmer.jsx も Designer.jsx もこのクラスをインポートします。
Human のメソッドはどの JSX ファイルでも同じ変換結果になるので、「この結果は Human を返す」という前提でコーディングすればちゃんとビルドできるし Human のメソッドも呼ぶことができます。よって、Programmer も Designer も Human を継承することで main.jsx から override したメソッドを呼び出すことができます。

そしてこのデモの肝になっているのは main.jsx で定義されている _Main.loadClass という次のようなメソッドです。

    static function loadClass(className : string, callback : function(:function(:Map.<variant>) : Human) : void) : void {
        var script = dom.createElement("script") as HTMLScriptElement;
        script.addEventListener("load", function(e : Event) : void {
            _Main.saveJSX(className);
            var eval = js.global["eval"] as function(:string) : Human;
            var factory = function(params : Map.<variant>) : Human {
                var code = "(function() {"
                         + "  var Human = JSX.require('" + className + ".jsx')." + className + "$HX;"
                         + "  return new Human(" + JSON.stringify(params) + ");"
                         + "})()";
                return eval(code);
            };
            callback(factory);
        });
        script.type = "text/javascript";
        script.src = className + ".js";
        dom.document.body.appendChild(script);
    }

何をやっているかというと、まず JSX で生成された他の JavaScript をロードします。
ロードが完了したら、次のような JavaScript を実行することで callback 関数にはあたかもロードしたクラスのコンストラクタが渡ったような挙動になります。

callback(function(params) {
    var Human = JSX.require(className + ".jsx")[className + "$HX"];
    return new Human(params);
});

Human のコンストラクタは Map. の引数を1つだけ取るので、コンストラクタはクラス名 + "$HX" になります。

callback 関数では JSX で次のようなコードを実行します。factory が callback 関数に渡される関数です。

var abicky = factory({
    name: "a_bicky",
    language: "R"
} : Map.<variant>);
abicky.introduce();

このコードは JSX から JavaScript に変換されると次のようなコードになります。

var abicky;
abicky = factory({ name: "a_bicky", language: "R" });
abicky.introduce$();

Programmer や Designer の変換結果は次のようになっているので、abicky.introduce$() がこれらのメソッドを呼び出すことがわかります。

Programmer.prototype.introduce$ = function () {
	this._say$S("My name is " + this.name + "\n" + "My favorite programming language is " + this.getLanguage$());
};
Designer.prototype.introduce$ = function () {
	this._say$S("My name is " + this.name + "\n" + "My favorite drawing tool is " + this.getTool$());
};

以上、簡単な解説でした!
今の書き方は気持ち悪いですが、そのうち静的言語の良い所と動的言語の良い所を手軽に利用できるようになれば夢が広がりますね!!

広告
Gist のコードの表示がおかしくなっていたので修正した ピュア R で MessagePack for R を作ってみた(unpack のみ)
※このエントリーははてなダイアリーから移行したものです。過去のコメントなどはそちらを参照してください