Kengo's blog

Technical articles about original projects, JVM, Static Analysis and TypeScript.

RequireJSによるコードの分割と最適化 for enchant.js

enchant.jsプログラミングでRequireJSを使うとなにが嬉しいの?どうやって使うの?という話。最初はenchant.jsをCommonJS/Modulesに合うように書き変えなきゃダメだと思っていたんですが、そんなことはないんですね。これは便利。

何が嬉しいの?

開発時にはゲームを複数のJSファイルに分割できるというメリットを、公開時にはゲームプレイに必要なダウンロード回数を減らせるというメリットを得られます。

複雑なゲームじゃないしべつにgame.jsひとつで充分ですという場合には、はっきり言って不要なモノです。しかし長々と開発してたらクラス数が10を超えてきて超長いスクリプトになっちまったぜウォォという場合には、遠慮なくコードを分割統治できるというメリットは大きいでしょう。

enchant.jsとgame.jsを読み込む

まずはrequire.jsをダウンロードしておきましょう。
あとはほんとに簡単で、HTMLに

<script data-main="path/to/game" src="path/to/require.js" type="text/javascript"></script>

と書いておくだけで自動的にgame.jsを読み込んで実行してくれます。game.jsには以下のようなdefine関数呼び出しを書いておきましょう。

define(['enchant'], function () {
    // 同じディレクトリにあるenchant.jsが読み込まれた後で実行される
    enchant();
    var game = new Game(320, 320);
    game.onload = function () {
        game.rootScene.addChild(new Label('Hello, world!'));
    };
    game.start();
});

プラグインやモジュールを読み込む

game.jsの1行目に書いた

define(['enchant'], function () {

の第1引数に注目です。ここに書いてあるのがgame.jsの依存するスクリプトになります。ので、

define(['enchant', 'plugins/nineleap.enchant'], function () {

などと書いておけばプラグインやモジュールを読み込むことができます。ただしenchant.jsよりもモジュールが先に読み込まれたときにエラーが出てしまいますので、モジュールを読む順番を指定する必要があります。order.jsをダウンロードしておき、以下のようにスクリプトを書き換えましょう。

define(['order!enchant', 'order!plugins/nineleap.enchant'], function () {

コードを最適化してひとまとめに

このままだと公開時に通信回数が増えてしまい、モジュール分割のデメリットが強く出たゲームになってしまいます。公開前にすべてのスクリプトをひとまとめにして(compress)、ついでに圧縮(minify)しておきましょう。ちなみにこの一連の処理のことを最適化(optimize)と呼んでいるようです。
この処理にはJRE 1.6以降かNode 0.4以降が必要です。ここでは推奨されているNodeを使った方法をまとめておきます。Nodeの最新バージョンはWindowsでも問題なく動作しますし、npmも標準で付属しているのでとても簡単に使えるはずです。


まずRequireJSの最適化ツールをインストールします。個人的には-gオプションを付与してグローバルにインストールするのが面倒が少なくオススメです。Linux環境ではsudoが必要かもしれません。

npm install -g requirejs

その後game.jsがあるディレクトリに移動し、以下のコマンドを実行します。r.jsなんてファイル無いよと心配になるかもしれませんが、上記コマンドでのインストールに成功していればPATHに追加されていますので大丈夫です。

r.js -o name=game out=game-built.js baseUrl=.

これでgame-built.jsというファイルが作成されます。index.htmlのscriptタグを以下のように書き換えて、ブラウザで開いてみましょう。

<script data-main="path/to/game-built" src="path/to/require.js" type="text/javascript"></script>

index.htmlとrequire.jsとgame-built.jsだけでゲームが問題なく動作していることが確認できると思います。これで通信回数を減らすことができました。

プロジェクトをまるごと最適化する

でもindex.htmlの書き換えとか面倒ですよね。作業漏れとかも怖いですし、あまりやりたくはありません。RequireJSの中の人もそう思ったのか、プロジェクトをまるごと最適化してあとは公開するだけという状態にするという機能を用意してくれています。

この機能を利用するためには、ビルドプロファイルと呼ばれるファイルを用意する必要があります。app.build.jsという名前でプロジェクトのルートディレクトリに保存するといいでしょう。例えば以下のようなプロジェクト構成なら、

my-project/
    + js/
        + plugins/
            - nineleap.enchant.js
        - game.js
        - require.js
        - enchant.js
    + css/
        - style.css
    - index.html    <-- css/style.cssとjs/require.jsを読み込む

app.build.jsは以下のように書きます。

({
    appDir: ".",
    baseUrl: "js",
    dir: "../optimized-project",
    modules: [
        {
            name: "game"
        }
    ]
})

その後に以下のコマンドを実行すると、../optimized-projectディレクトリに最適化されたプロジェクトが保存されます。

r.js -o app.build.js

こいつの嬉しいところは、JavaScriptだけではなくCSSも最適化してくれる(@importとかをひとまとめにしてくれる)というところですね。ゲーム開発だけでなくセミシングルページアーキテクチャのようなウェブアプリの開発でもきっと活躍してくれるでしょう。今度試してみたいと思います。