Yeomanでフロントエンドとバックエンドサーバを一緒に開発する

先月、HTML5など勉強会に参加した時、雑談タイムで

「 yeoman を使ってバックエンドの開発とかできないですか? 」

という内容に対して、フォローとして

「 gruntでリバースプロキシ使えますよ 」

っていうお話をした。

そのまま記事に書かずじまいで申し訳ないなーと思っていたら、@bathtimefish さんがブログをアップされていた。

その記事、
YeomanでフロントエンドとREST APIサーバーを同時に開発する方法
でリバースプロキシを使った手法が挙がっていたのですが、gruntのモジュールを使うともう少し楽ができそう。

私は、easymock を知らなかったですし、やはりyeoman 使ってる人が周りにあまりおらず、前回の勉強会は非常に勉強になったので、ここは一つ御礼の意味を込めてブログを書こうと思います。

ということで、yeoman(grunt) を使ったバックエンド開発のもう一つの手法を書いておきます。

さてここからが本題。

ベースとなる手順は元の記事を見ていただきつつ、途中まではだいたい同じです。

Yeomanは使える環境が整っているのが前提。
使えない場合はYeoman 導入編を参照してみてください。

まず REST APIサーバのモックが簡単に作れる easymock をインストール

$ npm install -g easymock

Node.jsのv0.10.1 ではインストールできなかったので今回はv0.8.14で。

次にyeomanでフロントエンドの環境を構成

$ mkdir /path/to/test
$ cd /path/to/test
$ yo webapp

     _-----_
    |       |
    |--(o)--|   .--------------------------.
   `---------´  |    Welcome to Yeoman,    |
    ( _´U`_ )   |   ladies and gentlemen!  |
    /___A___   '__________________________'
     |  ~  |
   __'.___.'__
 ´   `  |° ´ Y `

Out of the box I include HTML5 Boilerplate, jQuery and Modernizr.
Would you like to include Twitter Bootstrap for Sass? (Y/n) y
Would you like to include RequireJS (for AMD support)? (Y/n) y
...(省略)

それから、easymock 用のフォルダを作成。

$ mkdir -p server/api-server/api/items

次に easymock の設定ファイルを作成

$ vim server/api-server/api/config.json
{
    "simulated-lag": 1000,
    "cors": false,
    "jsonp": false,
    "proxy": {
        "server": "http://api.realserver.com",
        "default": false
    },
    "variables": {
        "server": "http://api.realserver.com"
    },
    "routes": [
        "/items/:itemid"
    ]
}

モックとなるjsonを作成

$ vim server/api-server/api/items/_get.json
[
  { "id": 1, "user": "john", "message": "hello" },
  { "id": 2, "user": "bob", "message": "Hi!" },
  { "id": 3, "user": "mike", "message": "good bye." }
]

APIモック用のサーバーをたちあげてみます。

$ cd server/api-server
$ easymock
Server running on http://localhost:3000
Server running on http://localhost:3000/_documentation/

http://localhost:3000/api/items/
にアクセスすると配置したjsonファイルが出力されています。

[
  {
    "id": 1,
    "user": "john",
    "message": "hello"
  },
  {
    "id": 2,
    "user": "bob",
    "message": "Hi!"
  },
  {
    "id": 3,
    "user": "mike",
    "message": "good bye."
  }
]    

では、フロント側を構成していきます。

$ cd /path/to/test/

まず、index.htmlのコンテナ部分を書き換えます。

$ vim /app/index.html
<div class="container">
  <div class="hero-unit">
    <h1>API Data</h1>
    <ul>
    </ul>
    <h3>Enjoy coding! - Yeoman</h3>
  </div>
</div>

次にapp.coffeeファイルを作成します。

$ vim /app/scripts/app.coffee
define(['jquery'], ($)->
    'use strict'
    $.getJSON('api/items/', (data)->
      ul = document.querySelector('div.hero-unit > ul')
      for row in data
        li = document.createElement 'li'
        txt = document.createTextNode "#{row.id}, #{row.user}, #{row.message}"
        li.appendChild txt
        ul.appendChild li
    )
    'loading api data.'
)

ひとまずyeoman単独で動かしてみます。

$ grunt server

$.getJSONの取得先がないのでscriptで取得しようとしてるjsonの値は表示されていません。

で、ここからが元記事と違うところですね。

grunt にリバースプロキシ用のモジュールがあるのでそれを使います。

内部的にhttp-proxyを呼んでいるのですが、Gruntfile.js で管理できるので非常に便利です。

$ npm install -D grunt-connect-proxy

-D (--save-dev) は インストールしたモジュールをpackage.jsonに書き込むためにオプションで指定します。

次にリバースプロキシの設定を行います。

  • 1, プロキシ用スニペットのロード
  • 2, プロキシの設定(/api 対象)
  • 3, configureProxies タスクの追加
$ vim Gruntfile.js
'use strict';
var lrSnippet = require('grunt-contrib-livereload/lib/utils').livereloadSnippet;
//(proxy用スニペットを追加)・・・1
var proxySnippet = require('grunt-connect-proxy/lib/utils').proxyRequest;
var mountFolder = function (connect, dir) {
    return connect.static(require('path').resolve(dir));
};

//...(省略)

        connect: {
            options: {
                port: 9000,
                // change this to '0.0.0.0' to access the server from outside
                hostname: 'localhost'
            },
            livereload: {
                options: {
                    middleware: function (connect) {
                        return [
                            lrSnippet,
                            proxySnippet,	//スニペット追加・・・1
                            mountFolder(connect, '.tmp'),
                            mountFolder(connect, 'app')
                        ];
                    }
                }
            },
            //プロキシ設定・・・2
            proxies: [{
                context: '/api',
                host: 'localhost',
                port: '3000',
                https: false,
                changeOrigin: false
            }],
            test: {
//...(省略)
        }
    });

    grunt.registerTask('server', function (target) {
        if (target === 'dist') {
            return grunt.task.run(['build', 'open', 'connect:dist:keepalive']);
        }

        grunt.task.run([
            'clean:server',
            'concurrent:server',
            'configureProxies',		//タスク追加・・・3
            'livereload-start',
            'connect:livereload',
            'open',
            'watch'
        ]);
    });

};

yeomanのフロントサーバをたちあげてみます。
(裏でeasymockを起動するのを忘れずに)

$ grunt server 

...(省略)

Running "configureProxies" task
Proxy created for: /api

Running "livereload-start" task
... Starting Livereload server on 35729 ...

Running "connect:livereload" (connect) task
Starting connect web server on localhost:9000.

...(省略)

yeoman側のフロントサーバが起動していますが、/api 以下のアクセスに対しては 3000ポート(easymock側)のAPIサーバを叩いています。

http://localhost:9000/api/items
へアクセスすると、
http://localhost:3000/api/items
の内容が表示されていることがわかります。

ちなみにGruntfile.jsのプロキシ設定のcontextの値を「/」にすると9000番ポートへのすべてのアクセスが3000番ポートへと流れます。

            proxies: [{
                context: '/',
                host: 'localhost',
                port: '3000',
                https: false,
                changeOrigin: false
            }],

この場合、フロントで起動するyeoman(grunt server)の役目はLiveReloadだけになり、バックエンドのサーバの内容が表示されます。
あとはwatchするファイルを適宜修正すればOKです。

もう少しYeomanでの管理構成として踏み込むとするなら easymock を別で起動するのが面倒なので、grunt から node の child_process を使って一括で起動管理したいところです。
ここで書くと設定が見辛くなるのでまた次の機会に。

□□□□□□

リバースプロキシを使うことによって、バックエンドがApacheやnginx、Node.jsでもなんでも使えます。
ですので、今回はeasymockをバックエンドサーバとして起動していましたが、WordPressなんかも動かしつつテーマのCSSを監視してライブリロードかけたりできますし、私はNode.jsを使うので、Expressの構成でcoffeeとSassのライブリロードしたりするテンプレートをつくったりして非常に楽しく開発ができてはかどります。

ということで一度使ってみてはいかがでしょう。