pig's diary

何でも忘れるので万年初心者ね

Sencha Touch 2.1 のコードの雰囲気

この記事は、Sencha Advent Calendar 2012の12月08日の記事です。

残念ながら僕はSencha Touch のことを良く知らないので、初期化の流れとコードの雰囲気をぐだぐだと探索していきたいとおもいます。

ファイルを書き出します。

$ pwd
/Users/nomorerolling/git/sencha-touch-2.1

$ sencha generate app GS ../GS/public/
Sencha Cmd v3.0.0.250
[INF]		init-properties:
[INF]		init-sencha-command:
[INF]		init:
...
[INF]		generate-app:

$ cd ../GS/public
$ ls -a
drwxr-xr-x  11 nomorerolling  nomorerolling   374 12  8 14:54 .
drwxr-xr-x   8 nomorerolling  nomorerolling   272 12  8 14:56 ..
drwxr-xr-x   4 nomorerolling  nomorerolling   136 12  8 14:54 .sencha
drwxr-xr-x   7 nomorerolling  nomorerolling   238 12  8 14:54 app
-rw-r--r--   1 nomorerolling  nomorerolling  1368 12  8 14:54 app.js
-rw-r--r--   1 nomorerolling  nomorerolling  4972 12  8 14:54 app.json
-rw-r--r--   1 nomorerolling  nomorerolling  1234 12  8 14:54 build.xml
-rw-r--r--   1 nomorerolling  nomorerolling  1740 12  8 14:54 index.html
-rw-r--r--   1 nomorerolling  nomorerolling  5040 12  8 14:54 packager.json
drwxr-xr-x   7 nomorerolling  nomorerolling   238 12  8 14:54 resources
drwxr-xr-x  11 nomorerolling  nomorerolling   374 12  8 14:54 touch

サーバを起動してindex.html にアクセスし、ページが表示されることを確認。

ですが、何も分からないです。何がどうなってこのページが表示されてるのか。

ページのソースを見てみたら

  <head>
    ...
    <!-- The line below must be kept intact for Sencha Command to build your application -->
    <script id="microloader" type="text/javascript" src="touch/microloader/development.js"></script>
  </head>

スクリプトはこれだけ。あー、分かってきました。開発時モードなわけで、development.jsさんが、ファイルのパスを読み込んで、scriptタグを書き出すパターンですね。

じゃあ、ファイルパスはどこに書いてあるんでしょうか?

// touch/microloader/development.js

    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'app.json', false);
    xhr.send(null);

    var options = eval("(" + xhr.responseText + ")"),
        scripts = options.js || [],
        styleSheets = options.css || [],
        i, ln, path;

app.json に書いてあるみたいですね。同期通信、見慣れないな。

で、app.json を見てみると

    "js": [
        {
            "path": "touch/sencha-touch.js"
        },
        {
            "path": "app.js",
            "bundle": true,  /* Indicates that all class dependencies are concatenated into this file when build */
            "update": "delta"
        }
    ],
    ...
    "css": [
        {
            "path": "resources/css/app.css",
            "update": "delta"
        }
    ],

ありました。js[index].path。

ちょっと分かって来ました。現在開発モードで、読むファイルはこの順番。
1. development.js
2. app.json
3. touch/sencha-touch.js(15,000行)
4. app.js(50行)
次は、と。短い方から読もう。

app.js はExt.application 関数を実行しているだけ。巨大なオブジェクトを引数に与える。このobject ばっかりの感じがSencha っぽいイメージ。どこもかしこもオブジェクトリテラル

Ext.application({
    name: 'GS',

    requires: [
        'Ext.MessageBox'
    ],

    views: ['Main'],

ところで、全然関係ないけど、オプションを渡すたびに毎回オブジェクトを生成するのって遅くないんですかね?つまり、コンストラクタに渡すオプションオブジェクトを、毎回生成するよりも、どこか外に定義しておいて、使う時に参照するほうが速いんじゃないですかね?と思って。調べてみました。

https://gist.github.com/4239149

毎回オブジェクトを生成する方が、2倍速かった。なんで!?予想外。検証何か間違ってる?Node.js 使ったからかな?スコープを辿るのにもたついてるからか?・・・コンパイラがproductionコードをどうしているかは知らないが、とにかく、どうもSenchaのオブジェクトを逐次生成するスタイルは結構速そうだ。

まあいいや。app.jsで実行してる、Ext.application を見てみる。/touch/sencha-touch.js の9596行目。短い関数に、長いコメント。中で、Ext.app.Application をnew してるなぁ。

    application: function(config) {

        ...

        requires = Ext.Array.from(config.requires);
        config.requires = ['Ext.app.Application'];

        onReady = config.onReady;
        scope = config.scope;

        config.onReady = function() {
            config.requires = requires;
            new Ext.app.Application(config);

            if (onReady) {
                onReady.call(scope);
            }
        };

        Ext.setup(config);
    },

引数でわたって来たconfig.requires を、後で使うように細工してる。onReady とかscopre とかも。引数を渡したとたん、これだもんな。これがSenchaスタイルってわけですか。使い回すオブジェクトの値が、みるみるうちに書き換えられていく。で、新しいonReady のリスナらしき関数がセットされてる。この、config.onReady ってだれがcall するんだろう? というわけで、Ext.setup を見る。/touch/sencha-touch.js 9226行目。

Ext.setup長い・・200行くらいある・・とりあえず、コメントから。

    /**
     * Ext.setup() is the entry-point to initialize a Sencha Touch application. Note that if your application makes
     * use of MVC architecture, use {@link Ext#application} instead.

Ext.setup は、Ext.application と同じアプリケーションの初期化関数で、ユーザーはExt.setup を使ってもいいけど、MVCしたいならExt.application しちゃえば?と言ってるみたい。Ext.application はExt.setupをラップしてたのか。だからさっき引数オブジェクトに変な細工を。

で、今知りたいのは、Ext.setup が onReady をcall してるかどうか。どこかでcallしてるよね?

    setup: function(config) {
        var ...
            emptyFn = Ext.emptyFn,
            onReady = config.onReady || emptyFn,
            onUpdated = config.onUpdated || emptyFn,
            scope = config.scope,
            requires = Ext.Array.from(config.requires),

        ...

        delete config.requires;
        delete config.onReady;
        delete config.onUpdated;
        delete config.scope;

・・・ん、ちょっと関係無い所が気になる。delete してますよね。何でいちいち消してるんでしょう? きっと、変数config が参照するオブジェクトは存続し続けるので(new Ext.app.Applicationの引数にも使い回されてたし)オブジェクトのキーの中でも、生きてると分かりにくくなるキーや、生きてるとまずいキーを、不要になったそばから消してるんでしょうね。

管理を怠ると、すぐに分かりにくくなってしまいそうですね。1つのオブジェクトが、あらゆる初期化関数の引数に内部で使い回されています。デバッグで死ねそう。「この値、どこで入った!?」「この値、知らない間に書き変わってる!」などなど。ただSencha がカオスなのではなく、Javascriptがそうなんでしょうが。で、オブジェクトを使い回すなら、上のコードのように明示的に不要なキーをdelete していくのが、生き残るために非常に大事そう。そういうセルフサービス感がJavascript、面白いです。

で。onReady を探していたんだった。

Ext.onReady の値(関数)が魔術のごとく入れ替わっていくのは見て見ぬ振りをしておきつつ、onDocumentReady を待ち、Ext.require がスクリプトを読み込み(Ext.factoryConfig の中身は重要そうだな)、viewport 関連のセットアップを終えて、Loader#onReady、8383行目。

        // duplicate definition (documented above)
        onReady: function(fn, scope, withDomReady, options) {
            var oldFn;

ここで、Ext.setup にわたってきたonReadyが、documentReady と相談しながら、どちらもOKだったらようやくcall されるようだ。
このあと、new Ext.app.Application(config) され、app.js で Ext.application に渡した onReady が実行される。初期化の流れと、Sencha の雰囲気がちょっとだけ分かった気がする。

実は、最初はExt.define のコードを読んでいたのですが、最後まで良くわからないまま終わってしまいました。。Sencha 、奥が深そうです。Sencha Advent Calendar はまだまだ空きがあるようですので、興味のあるかたはぜひ執筆してください!