JavaScript
CakePHP
AWS
rest
勉強会

RequireJSを使用したJavaScriptのモジュール化 - AWS上で構築するRESTfulアプリ勉強会~Web開発ワークショップ~【第4回】マニュアル

More than 3 years have passed since last update.

:large_blue_circle: はじめに

本投稿は、2015/4/24に行われた、RequireJSを使用したJavaScriptのモジュール化 AWS上で構築するRESTfulアプリ勉強会~Web開発ワークショップ~【第4回】 - connpassの内容についてまとめた資料です。

:warning:今後の予定
AWS上で構築するRESTfulアプリ勉強会~Web開発ワークショップ~ - connpass

:warning: 4/27追記
飲みDevの実装例(GitHub)と、当日使用したスライド(SlideShare)をアップしました。

今回は、Require.jsを使用します。
JavaScriptのモジュール化とはなんなのか、実際の動作を確認しつつ学んでいきましょう!

:large_blue_circle: JavaScriptのモジュール化とは

アカデミックな定義はいろいろあるでしょうが、実用上、下記のようなものと理解すればいいでしょう。

  • 他の部分と独立して機能する「かたまり」を定義したもの
  • そのかたまりを必要な時に簡単に読み込むことが出来る

前回までのプログラムは、各ファイルが下記のような形式でした。

app.js
var app = app || {};

//開始
(function(app) {
    app.Application = Backbone.Marionette.Application.extend({
        initialize : function(){
            new app.TodoRouter();
        },

        onStart : function(){
            Backbone.history.start();
        },

        regions : {
            mainRegion : '#main'
        }

    });
})(app);

グローバルなappオブジェクトの中に、Backbone.Marionette.Applicationを継承したオブジェクトをApplicationオブジェクトとして格納しています。
他にもファイルを作成しましたが、全部この形式です。
試しに、前回のプログラムにログを仕込んでグローバルなappオブジェクトがどうなっているか確認してみます。

main.js
var app = app || {};

//開始
(function(app) {
    app.application = new app.Application();
    app.application.start();
    console.log(app);   // ****** これでappをログ出力 ******
})(app);

main.jsでログ出力しました。全てのjsファイルが読み込まれた後です。実行結果は下記のとおりです。
各jsファイルで定義したオブジェクトがそれぞれ登録されているのがわかると思います。
app_module.png

ご覧のとおり、Model, Viewなどがオブジェクトとしてひとかたまりになっています。
上で挙げた「他の部分と独立して機能する「かたまり」を定義したもの」は既に前回までで実現してるといえるでしょう。
今回のテーマをもう少し具体的にするならば、上に挙げた2番目、「そのかたまりを必要な時に読み込むことが出来る」を「Require.js」を使用して実現する方法について学んでいくこと、と言い換えることが出来ます。

:white_check_mark: 上記ログを前回のvol/03ブランチのソースに仕込んで確認してみましょう。

では、もっと具体的にはどうするのか、順番に見て行きましょう。

:large_blue_circle:モジュール間の「依存関係」を制御する

モジュール間には「依存関係」があります。
例えば、viewはその中で扱うmodelやcollectionに依存しています。
前回まではこの「依存関係」をどのようにコントロールしていたのでしょうか。
前回のdefault.ctpを見てみましょう。

default.ctp(一部抜粋)
    <!-- js(library) -->
    <script src="js/lib/jquery-2.1.3.min.js" type="text/javascript"></script>
    <script src="js/lib/underscore-min.js" type="text/javascript"></script>
    <script src="js/lib/backbone-min.js" type="text/javascript"></script>
    <script src="js/lib/backbone.marionette.min.js" type="text/javascript"></script>

    <!-- js(application) -->
    <!--   model   -->
    <script src="js/models/todo-model.js" type="text/javascript"></script>
    <!--   collection   -->
    <script src="js/collections/todo-collection.js" type="text/javascript"></script>
    <!--   view   -->
    <script src="js/views/todo-item-view.js" type="text/javascript"></script>
    <script src="js/views/todo-detail-item-view.js" type="text/javascript"></script>
    <script src="js/views/todo-detail-layout-view.js" type="text/javascript"></script>
    <script src="js/views/todo-composite-view.js" type="text/javascript"></script>
    <script src="js/views/todo-layout-view.js" type="text/javascript"></script>
    <!--   controller   -->
    <script src="js/routers/controller.js" type="text/javascript"></script>
    <!--   router   -->
    <script src="js/routers/router.js" type="text/javascript"></script>
    <!--   application   -->
    <script src="js/app.js" type="text/javascript"></script>
    <!--   entry point   -->
    <script src="js/main.js" type="text/javascript"></script>

前回までは、「jsファイルを読み込む順番」によって依存関係を解決していました。
例えば、router.jsは下記の通り、TodoControllerオブジェクトが先に定義されている必要があるので、それが定義されているcontroller.jsを先に読み込む必要があるため、単純に上に書くことによって先に読み込まれるようにしていました。

router.js
var app = app || {};

//router
(function(app) {
    app.TodoRouter = Backbone.Marionette.AppRouter.extend({
        //コントローラをインスタンス化
        controller: new app.TodoController(),    //***** controller.jsに依存している
        //ルーティング設定
        appRoutes : {
            ''                  : 'todoLists',
            'todo-lists'        : 'todoLists',
            'todo-lists/:id'    : 'todoDetail'
        },
    });
})(app);

jquery, underscore, backboneなどは更に上に書かれています。
jsファイルの数が増えてくると依存関係の把握そのものが困難になりますし、実際にどのモジュールがどのモジュールに依存しているかがわかりにくくなります。
「依存ファイルは上に書く」という単純な方法では管理が困難になって来ます。

:large_blue_circle: Require.jsとは

AMDという形式でjavascriptプログラムをモジュール化し、定義済みの依存関係を元に「実行時に、その時必要なモジュールを読み込む」仕組みを提供してくれるjavascriptライブラリです。
「今必要な"hogeモジュール"」だけ指定すれば、「"hogeモジュール"が必要とする"fugaモジュール"、"fugaモジュール"が必要とする"piyoモジュール"」....、は、定義済みの依存関係を元に自動的に読み込む機能を提供してくれます。

AMD?

ざっくり言うと、 「モジュール自身が、自身の実行に必要な外部jsファイルを自分で読み込みに行く」 ということを実現するための仕組みや仕様です。
(Asynchronous Module Definitionの略)。
require.jsは、このAMDの実装です。
:warning: 「Asynchronous = 非同期」ですが、「非同期」と言うより「実行時に」と言ったほうがイメージしやすいかと思います(依存するモジュールの読み込みが完了してから実行される、と言う意味で同期的に動くので)。

AMDによるモジュール定義は下記サンプルのようになります。詳しくは後述します。

AMDサンプル(todo-collection.js)
//Todo一覧表示用コレクション
define(function(require) {
    var TodoModel = require('models/todo-model');

    var TodoCollection = Backbone.Collection.extend({
        url : '/rest-study/todo_lists.json',
        model : TodoModel,

        parse : function(response) {
            //コレクションをパース
            console.log("コレクションをパース");
            return response;
        }
    });

    return TodoCollection;
});

Require.jsがやってくれること

  • AMD形式で記述したjsのモジュールを読み込んでオブジェクトを生成してくれる。
  • 必要なモジュールの読み込みを指定すると、依存関係のあるファイルも読み込んでくれる

細かいところは実装しながら見て行きましょう。

:large_blue_circle: 今回の内容

前回作成したプログラムを、AMD形式に修正し、Require.jsで読み込めるようにします。
:warning:画面は何も変わりません。内部の実装だけが変わります。

ではまず、準備から!

:large_blue_circle: 事前準備

SSHでログイン

sshログインから始めましょう!

  • :white_check_mark: ssh -i [秘密鍵のパス] study@[サーバのPublicIP]

ディレクトリを移動しておきます。

  • :white_check_mark: cd /var/www/study/rest-study

gitのブランチを整えておく

前回は、vol/03ブランチで作業をしました。前回のマニュアルでは、vol/03ブランチをpushしてGitHubで確認まで、としていましたので、まず前回までの内容をmasterブランチにマージします。
前回が作業途中の方や、今回から参加の方は、新たにディレクトリを作成します。

:warning:今回から参加される方、第1回の内容は終えていることが条件ですのでご了承ください。

今回分から始められる方、前回が途中の方、終わっているかたでやり方が違うのでご注意ください。
やりたいことは、

  • masterブランチを前回の終了状態にする
  • masterブランチを元に、今回の作業用である、「vol/04」ブランチを作成する

前回が作業途中の方

とにかくコミットしましょう!
途中でもとにかく!

次は全員同じ。 fork元をリモートリポジトリとして追加する

第一回勉強会でforkした元のリポジトリをリモートリポジトリとして追加します。

:white_check_mark: git remote
とコマンドを打って、

origin
upstream

と表示される場合はこの作業は終わっています。次へ行きましょう!
upstreamが表示されない人は次のコマンド。

:white_check_mark: git remote add upstream https://github.com/suzukishouten-study/rest-study.git

これでOKです!

次も全員同じ。 upstreamリポジトリから最新を取得する

:white_check_mark: git fetch upstream

これでOK!

次は、

  • 前回の内容を完了した方
  • 前回の内容を途中までやった方と今回から参加の方

で少し違うのでご注意!

前回の内容を完了した方

masterブランチを前回分を終了した状態にします。
前回を完了した方は、vol/03ブランチをマージです。

:white_check_mark:git checkout master

でmasterブランチをチェックアウト後、

:white_check_mark:git merge vol/03

でマージ。これでOK!

前回の内容を途中までやった方と今回から参加の方

masterブランチを前回分を終了した状態にします。
前回の内容を途中までやった方と今回から参加の方は、upstreamリポジトリのvol/03-finishブランチをマージです。

:white_check_mark: git checkout master

でmasterブランチをチェックアウト後、

:white_check_mark: git reset --hard upstream/vol/03-finish

で強制的にマージ。これでOK!

今回の作業用のbranchを作成

全員同じ手順です。

今回の作業用ブランチとして、vol/04ブランチを作り、チェックアウトします。
:warning:masterブランチから分岐してvol/04ブランチを作成するので、まずmasterブランチにいることを確認して下さい。
これまでの作業で、既にmasterブランチにいるはずですが。

  • :white_check_mark: git branchmasterにいることを確認
  • :white_check_mark: masterにいなかった場合は、git checkout mastermasterをチェックアウト

以下コマンドでvol/04ブランチを作成します。

  • :white_check_mark: git branch vol/04 でブランチを作って
  • :white_check_mark: git checkout vol/04 でそのブランチに変更 または、
  • :white_check_mark: git checkout -b vol/04 で作成とブランチ変更を一度に実行

確認

念のため確認します。

  • :white_check_mark: git log でコミットログを確認してみましょう。

コミットログが出力されます。
前回の最後のコミットが表示されていればOKです。

これでブランチの準備は整いました。

:warning: git branch -a と、全ブランチを表示すると、vol/04-finishブランチが見えると思いますが、これは今回の内容が全て終えた状態のソースが入っています。このソースは、下記URLから各Lesson毎のコミットが確認できますので参考にしてください。

Commits · suzukishouten-study/rest-study

:large_blue_circle: Require.jsライブラリダウンロード

Require.jsの公式サイトから取得します。
ダウンロードURLはhttp://requirejs.org/docs/release/2.1.17/minified/require.jsです。サーバにSSHでログインし、wgetコマンドで取得します。
このあたりの手順は第2回の資料にも記載がありますので参考にしてください。

  • :white_check_mark:/var/www/study/rest-study/app/webroot/js/libにダウンロードします。
cd /var/www/study/rest-study/app/webroot/js/lib
wget http://requirejs.org/docs/release/2.1.17/minified/require.js

これで準備完了です!

では、さっそくlesson1です。

その前に...

app/Controller/AppController.phpに、DebugKitというコンポーネントが登録してありますが、外しましょう。
これがあると、DebugKit用の別バージョンのJqueryが先に読み込まれてしまいます。

app/Controller/AppController.php
 class AppController extends Controller {
     public $components = array(
-        'DebugKit.Toolbar',
         'RequestHandler'
     );
 }
  • :white_check_mark: app/Controller/AppController.php を上記の通り修正。
  • :white_check_mark: 動作確認!
  • :white_check_mark: Gitにコミット ("DebugKit削除"とかメッセージ入れておきましょう)。

完成ソース(GitHub)へのリンク

:large_blue_circle: lesson1 application.start()までの流れを追う

このファイルにはこう書く、ここにこれを書く、等を覚えるだけでも十分実用的ですが、require.jsを使用した場合の処理の流れを追いながら、仕組みをきちんと理解していきましょう。

  • 依存関係の定義
    • require-config.jsファイルを新たに作成し、依存関係を定義します。
  • エントリポイントの指定
    • エントリポイントとなるjsファイルは、require.jsに教えてあげることで実行されます。
  • Controllerが実行されるまでの流れを追う
    • 手始めに、Controllerが実行されるまでの流れを理解します。

編集するファイル一覧

編集 file 役割
修正 app/View/Layouts/default.ctp HTMLテンプレート
追加 app/webroot/js/require-config.js 依存関係等の設定
修正 app/webroot/js/main.js アプリケーションの開始ポイント
修正 app/webroot/js/app.js アプリケーション(Marionettte.Application)
修正 app/webroot/js/routers/router.js ルータ(Marionette.AppRouter)
修正 app/webroot/js/routers/controller.js コントローラ

default.ctp

各jsファイルの読み込み部分が大きく変わります。

default.ctp

 <html>

〜 中略 〜 

    <!-- 詳細画面の表示内容テンプレート -->
    <script type="text/template" id="todo-detail-item-template">
    <h2>Todo #<%- id %></h2>
    <div>
    <textarea style="width:300px;height:50px" id="edit-todo" autofocus placeholder="Todo?"><%- todo %></textarea>
    <input type="button" id="updateTodo" value="更新"></input>
    <input type="button" id="updateCancel" value="キャンセル"></input>
    </div>
    </script>

-   <!-- js(library) -->
-   <script src="js/lib/jquery-2.1.3.min.js" type="text/javascript"></script>
-   <script src="js/lib/underscore-min.js" type="text/javascript"></script>
-   <script src="js/lib/backbone-min.js" type="text/javascript"></script>
-   <script src="js/lib/backbone.marionette.min.js" type="text/javascript"></script>
-
-   <!-- js(application) -->
-   <!--   model   -->
-   <script src="js/models/todo-model.js" type="text/javascript"></script>
-   <!--   collection   -->
-   <script src="js/collections/todo-collection.js" type="text/javascript"></script>
-   <!--   view   -->
-   <script src="js/views/todo-item-view.js" type="text/javascript"></script>
-   <script src="js/views/todo-detail-item-view.js" type="text/javascript"></script>
-   <script src="js/views/todo-detail-layout-view.js" type="text/javascript"></script>
-   <script src="js/views/todo-composite-view.js" type="text/javascript"></script>
-   <script src="js/views/todo-layout-view.js" type="text/javascript"></script>
-   <!--   controller   -->
-   <script src="js/routers/controller.js" type="text/javascript"></script>
-   <!--   router   -->
-   <script src="js/routers/router.js" type="text/javascript"></script>
-   <!--   application   -->
-   <script src="js/app.js" type="text/javascript"></script>
-   <!--   entry point   -->
-   <script src="js/main.js" type="text/javascript"></script>
+   <!-- require -->
+   <script type="text/javascript" src="js/require-config.js"></script>
+   <script type="text/javascript" src="js/lib/require.js" data-main="main.js"></script>

 </body>
 </html>
  • jsファイルの読み込み部分を全て削除
  • require-config.jsの読み込み
    • 依存関係の設定が読み込まれます。
  • require.jsの読み込み
    • require.js本体を読み込みます。読み込み後、require.jsにより、data-main属性で指定したファイルが実行されます。これがプログラムのエントリポイントとなります。

require-config.js

各jsファイル間の依存関係を設定するファイルです。

require-config.js
// require設定
var require = {

    // キャッシュ防止
    urlArgs: "v=" + (new Date()).getTime(),

    // モジュール読み込みのbaseUrlを指定
    baseUrl: '/rest-study/js/',

    // ファイルのpathを指定
    paths : {
        'jquery' : 'lib/jquery-2.1.3.min',
        'underscore' : 'lib/underscore-min',
        'backbone' : 'lib/backbone-min',
        'marionette' : 'lib/backbone.marionette.min',
    },

    // ファイルの依存関係を指定
    shim : {
        'jquery' : {
            exports : '$'
        },
        'underscore' : {
            deps : ['jquery'],
            exports : '_'
        },
        'backbone' : {
            deps : ['jquery', 'underscore'],
            exports : 'Backbone'
        },
        'marionette' : {
            deps : ['backbone'],
            exports : 'Marionette'
        },
    }
};
  • baseUrl変数
    • 各モジュールのjsファイルのパスを指定していきますが、その際にここで指定したパスからの相対パスで書けるようになります。今回は、'/rest-study/js/'と指定します。
  • paths変数
    • 各モジュールのjsファイルのパスを指定します。名前 : パスの形式で記述します。パスは拡張子(.js)は省略します。これでパスをここで指定した名前で指定できるようになります。
  • shim変数
    • AMD対応でないライブラリについての依存関係を定義します。
    • deps変数に、依存するライブラリのパス(path変数で指定した名前)を指定します。
    • exports変数に、ライブラリが定義しているオブジェクトの名前を指定します。
  • urlArgs変数
    • キャッシュを無効にするために使用します。モジュールの読み込み時のURLに、v=XXXというパラメータが付加されます。ここでは、タイムスタンプを付加することでURLが変わるようにしてキャッシュから古いファイルを読み込まれることを防いでいます。

これで、jquery, underscore, backbone, marionetteは、これらに依存する必要になった際に読み込まれるようになります。

:warning: 実は、このshim設定はなくても動きます。backbone, marionetteとも、使用しているバージョンはAMD対応されているため、paths変数さえ設定されていれば動きます。

main.js

エントリポイントです。require関数から全てが始まります。
このファイルから、後でrequire.jsによるファイルの読み込み、実行の流れを確認するため、適宜ログ(console.log())を仕込んでいきます。
変更前と変更後を両方載せます。

main.js(変更前)
var app = app || {};

(function(app) {
    app.application = new app.Application();
    app.application.start();
})(app);
main.js(変更後)
//開始
console.log('load main');
require([
    'marionette'
], 
function(){
    console.log('run main');
    require(['app'], function(Application){
        console.log('run main2');
        window.application = new Application();
        window.application.start();
        console.log('app start');
    });
});
  • まず、require関数によりmarionetteと指定することで、lib/backbone.marionette.min.jsを読み込みます。
  • 読み込み完了後のコールバック関数で、さらにrequire関数を実行し、app.jsの読み込みを行います。
  • さらに読み込み完了後のコールバック関数で、application.start()を実行します。
  • 後で流れを追うため、ファイルの先頭とコールバック関数の先頭と最後にログを入れました。

require関数は下記のように動作します。

  • 第1引数に、読み込むjavascriptソースを指定します。下記の指定が可能。
    • require-config.jspaths変数で定義した名前。marionetteの指定で'lib/backbone.marionette.min.js'が読み込まれる。
    • require-config.jsbaseUrlで指定したパスからの相対パス。 appと指定(拡張子.jsは省略)で、app.jsが読み込まれる。
  • 読み込み完了後、第2引数で指定したfunctionが実行されます。functionの引数は、app.jsで定義したオブジェクトになります。

修正前のプログラムは、グローバルスコープのappオブジェクトに、生成したオブジェクトを全て突っ込んでおいて、必要なときにapp.使用するオブジェクトとしてアクセスしていました。
修正後は、グローバルスコープに突っ込んでおきたいオブジェクトは、applicationだけですので、windowオブジェクトに直接突っ込んでいます。

app.js

define関数によりオブジェクトを定義します。
以降、全てのファイルでこのdefine関数によりrouterやcontroller, view等のオブジェクトを定義します。

app.js(変更前)
var app = app || {};

//開始
(function(app) {
    app.Application = Backbone.Marionette.Application.extend({
        initialize : function(){
            new app.TodoRouter();
        },

        onStart : function(){
            Backbone.history.start();
        },

        regions : {
            mainRegion : '#main'
        }
    });
})(app);
app.js(変更後)
//Application
console.log('load app');
define(function(require){
    console.log('run app');
    var Router = require('routers/router');
    var Application = Marionette.Application.extend({
        initialize : function(){
            console.log('app.initialize');
            new Router();
        },

        onStart : function(){
            Backbone.history.start();
        },

        regions : {
            mainRegion : '#main'
        }

    });
    return Application;
});
  • define関数に指定したfunction内で、下記の処理を行っています。
    • require関数で、routers/router.jsを読み込み、Routerオブジェクト取得。
    • 変更前の処理と同じく、Applicationオブジェクトの生成。
    • 生成したApplicationオブジェクトをreturn。
    • main.js同様、グローバルスコープのappオブジェクトは使用しません。returnすることにより、呼び出し元から使用されます。

他のファイルも、同じ方法で修正していきます。ポイントは、

  • define関数を使用してオブジェクトを定義する。
    • functionの先頭でrequire関数を使用して、必要なモジュールを読み込んでおく。
    • そのモジュールを使用しつつ、新しいモジュールを生成してreturn。

:warning: requiredefineの違い
動きはほとんど同じですが、下記の違いが有ります。

  • require: モジュールは定義せず、処理の実行のみ(main.jsでは、モジュールは定義しないのでrequireを使用しています)。
  • define: モジュールを定義する(app.jsでは、Marionette.Applicationを継承したオブジェクトを定義しています)。

:warning:後述しますが、require関数はviewを動的に切り替える際のviewファイルの読み込みにも使用します。

:warning: defineのもう一つの書き方
define関数は下記のように書くことも出来ます。

app.js
//Application
console.log('load app');
define([
    'routers/router'
],
function(Router){
    console.log('run app');
    var Application = Marionette.Application.extend({
        initialize : function(){
            console.log('app.initialize');
            new Router();
        },

        onStart : function(){
            Backbone.history.start();
        },

        regions : {
            mainRegion : '#main'
        }

    });
    return Application;
});
  • 第1引数のrouters/routerの指定(拡張子.jsは省略)で、routers/router.jsを読み込む。
  • 読み込み完了後、第2引数で指定したfunctionが実行されます。functionの引数は、routers/router.jsで定義したオブジェクトになります。

こちらの書式でもよいですが、最初に紹介した「functionの先頭でrequire関数を使用してファイルを読む」形式のほうが、読み込むファイル数が多い場合に可読性が高くなります。例えば下記のようなプログラムです。

サンプル(上記のやり方)
define([
    'foo',
    'bar',
    'baz',
    'hoge,
    'fuga',
    'piyo'
],
function(Foo,
    Bar,
    Baz,
    Hoge,
    Fuga,
    Piyo){
    var module = モジュールを定義;
    return module;
});

読み込むファイルのパスと、そのファイルで生成されたモジュールが格納された変数の対応が見づらいです。
最初に紹介したやり方ですと、

サンプル(最初に紹介したやり方)
define(function(require){
    var Foo = require('foo');
    var Bar = require('bar');
    var Baz = require('baz');
    var Hoge = require('hoge');
    var Fuga = require('fuga');
    var Piyo = require('piyo');

    var module = モジュールを定義;
    return module;
});

このように、関数の始めで1行づつrequireが書け、ファイルと変数の対応も見やすいです。
C言語の#includeやjavaのimportの様に、ファイルの先頭に綺麗に揃えることが出来ます。
今回はこの記述方法で統一します。

router.js

特筆すべき点はないので、app.js同様に修正しましょう。
ログも同じように入れます。

router.js
//router
console.log('load router');
define(function(require) {
    console.log('run router');
    var TodoController = require('routers/controller');
    var TodoRouter = Marionette.AppRouter.extend({
        //コントローラをインスタンス化
        controller: new TodoController(),
        //ルーティング設定
        appRoutes : {
            ''                  : 'todoLists',
            'todo-lists'        : 'todoLists',
            'todo-lists/:id'    : 'todoDetail'
        },
    });
    return TodoRouter;
});

controller.js

これも同じように修正しますが、viewの読み込み方法に変更があります。

controller.js
//controller
console.log('load controller');
define(function() {
    console.log('run controller');
    var TodoController = Marionette.Controller.extend({

        todoLists : function() {
            //Todoレイアウト用ビューにルーティング
            this.nextView('views/todo-layout-view');
        },

        todoDetail : function(id) {
            this.nextView('views/todo-detail-layout-view', {modelId : id});
        },

        nextView : function(viewPath, option) {
            require([viewPath], function(View){
                window.application.mainRegion.show(new View(option));
            });
        },

    });
    return TodoController;
});
  • Viewの読み込み部分の変更について説明します。
コントローラ関数変更点
        todoLists : function() {
            //Todoレイアウト用ビューにルーティング
-           this.nextView(app.TodoLayoutView);
+           this.nextView('views/todo-layout-view');
        },
  • 修正前は、appオブジェクトに格納済みのviewオブジェクトを指定していました。
  • 修正後は、viewのjsファイルのパスを指定しています。
nextView関数変更点
-       nextView : function(View, option) {
-           app.application.mainRegion.show(new View(option));
+       nextView : function(viewPath, option) {
+           require([viewPath], function(View){
+               window.application.mainRegion.show(new View(option));
+           });
        },
  • 修正前は、viewをregionにshowするだけでした。
  • 修正後は、このタイミングでrequire関数を実行し、jsファイルを読み込み、読み込み完了後のコールバック関数で、引数として受け取ったviewをregionにshowしています。

ログを見て流れを確認

http:PublicIP/rest-study
にアクセスしてみましょう。
下記のようなログが出力されるはずです。

lesson1_log.png

ログ文字列だけ取り出すとこうです。

ログ 解説
load main main.jsの実行開始
run main main.js内のrequireで指定したfunction実行開始
load app app.jsの実行開始
load router route.jsの実行開始
load controller controller.jsの実行開始
run controller controller.js内のdefineで指定したfunction実行開始
run router router.js内のdefineで指定したfunction実行開始
run app app.js内のdefineで指定したfunction実行開始
run main2 main.js内の、内側のrequireで指定したfunction実行開始
app.initialize Marionette.Applicationオブジェクトのinitialize関数実行開始
app start application.start()の実行(main.jsの実行完了)

図にするとこのようなイメージです。

require_flow.png

それぞれのjsファイルでは、

  1. 自分がロードされる
  2. 依存しているファイル群をロードする
  3. ロード完了後function実行

という流れで実行されます。

実装

では、実際に修正してここまでの流れを見てみましょう。

  • :white_check_mark: app/View/Layouts/default.ctp を上記の通り修正。
  • :white_check_mark: app/webroot/js/require-config.js を上記の通り追加。
  • :white_check_mark: app/webroot/js/main.js を上記の通り修正。
  • :white_check_mark: app/webroot/js/app.js を上記の通り修正。
  • :white_check_mark: app/webroot/js/routers/router.js を上記の通り修正。
  • :white_check_mark: app/webroot/js/routers/controller.js を上記の通り修正。
  • :white_check_mark: 動作確認!
  • :white_check_mark: Gitにコミット

GitHubでのdiff表示へのリンク

1 application.start()までの流れを追う · suzukishouten-study/rest-study@acc0b1d

Lesson2へ!

:large_blue_circle: lesson2 残りを全部require.js対応する

各ファイルをrequire.jsに対応させます。前回までの内容で修正できますので、特に新しい説明はありません!
正解へのリンクだけ貼っていますので、参考にしつつ実装しましょう。

  • :white_check_mark: app/webroot/js/collections/todo-collection.js をlesson1に倣って修正。
  • :white_check_mark: app/webroot/js/models/todo-model.js をlesson1に倣って修正。
  • :white_check_mark: app/webroot/js/views/todo-layout-view.js をlesson1に倣って修正。
  • :white_check_mark: app/webroot/js/views/todo-composite-view.js をlesson1に倣って修正。
  • :white_check_mark: app/webroot/js/views/todo-item-view.js をlesson1に倣って修正。
  • :white_check_mark: app/webroot/js/views/todo-detail-item-view.js をlesson1に倣って修正。
  • :white_check_mark: app/webroot/js/views/todo-detail-layout-view.js をlesson1に倣って修正。
  • :white_check_mark: 動作確認!
  • :white_check_mark: Gitにコミット

GitHubでのdiff表示へのリンク

2 残りを全部require.js対応 · suzukishouten-study/rest-study@1e590d4

:beers:飲みDev:pizza:

前回から始まった勉強会終了後にお酒を飲みながら居残り開発をする、名づけて「飲みDev」。今回もやります!

テーマを挙げておきます。
いけるところまで行きましょう!
もちろん、他の機能をつけたり、なんでもやってみましょう!
require.jsと関係ないテーマでも何でもいいですよ!

今回のテーマ

「default.ctpに書いてあるtemplateを別ファイルにして、画面遷移時に動的に読みこむようにする。」

いい感じにできたら発表してください!

以上です!

:warning: 4/27追記 実装例をGitHubにアップしました!いろんな実装方法があると思いますが、ひとつの例として参考にしてみてください。
実装例 ⇢ 飲みDev templateをRequireJSで読み込む · suzukishouten-study/rest-study@28d2fb5

次回予告

次回テーマは、CakePHPで実装するログイン機能です。
CakePHPでのユーザ認証、セッション管理を学び、ログイン機能を実装します。
ぜひご参加ください!

コメント/フィードバックお待ちしております。

参加者の方も、そうでない方もお気づきの点があればお願い致します。