AngularJSなどJavascriptのMVCフレームワークを試してみる環境について作ってみる

Ruby, RailsRubygemsの仕組みがある程度、わかっている人向けに書く。
あと、作ってみただけであって、実際に使ってはいないので、色々と問題あるかもしれない。

「HTMLやCSSJavascriptを1〜2年前くらいの知識ではどう書くのかな」の参考程度に見ても良いかもしれない。

前説

Backbone.jsやAngularJSなどJavascriptでもMVCフレームワークが出てきている。特にAngularJSはDIの仕組みを持つなど、ActionScript3のMVCフレームワークであるRobotlegsに感動した身としては興味深いライブラリである。

そのようなJavascirptライブラリを試す場合、色々な仕組みが欲しくなる。例えば以下のようなものだ。

  • HTMLのテンプレートエンジン
  • coffee scriptのコンパイラ
  • assetsエンジン

これを実現するにはRailsの新規プロジェクトを作成するのが手っ取り早い。早いのだが、Railsフルスタックなので色々と重い。軽量なシステムで試したい場合もある。

そんな時にはSinatraを試してみると良いと思う。

Sinatra公式サイトに書いてある通り、始めるには分かりやすい。

require 'sinatra'

get '/hi' do
  "Hello World!"
end

単純でいいが、しかしながらテンプレート(サイトを動作させるために最小限必要なディレクトリやファイル)を自動で作成してくれるRailsのような環境に慣れてしまうと、テンプレートを作成してくれないSinatraに不満を持つようになる。

そのような人のために、テンプレートや面倒な作業を肩代わりしてくれるPadrinoというライブラリがある。

$ padrino g project angular-test -e slim -c sass -s jquery -b
  • eの引数はHTMLテンプレートエンジンにslimを使い、-eの引数はcssのエンジンにsassを使う、という意味であり、これらのエンジンを使えるように準備してくれる。

HTMLテンプレートエンジン

slimは書く文字数を減らしてくれるエンジンであり、修正も楽だ。

doctype html
html
  head
    title Slim Examples
    meta name="keywords" content="template language"
    meta name="author" content=author
    javascript:
      alert('Slim supports embedded javascript!')

  body
    h1 Markup examples

    #content
      p This example shows you how a basic Slim file looks like.

このJavascriptMVCフレームワークを試す前はHamlをよく使っていた。

#profile
  .left.column
    #date= print_date
    #address= current_user.address
  .right.column
    #email= current_user.email
    #bio= current_user.bio

SlimはHamlからの派生であり、実際にHTMLを書くだけならSlimが必要というものでもない。

だが、AngularJSのようにHTML内に挙動を書き込むタイプと絡む場合はそうはいかない。

例えば、このようなコードを書くことになる。

<!doctype html>
<html ng-app>
  <head>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js"></script>
  </head>
  <body>
    <div>
      <label>Name:</label>
      <input type="text" ng-model="yourName" placeholder="Enter a name here">
      <hr>
      <h1>Hello {{yourName}}!</h1>
    </div>
  </body>
</html>

これをHamlで表現するとこうなる。html2hamlで変換すれば結果が得られる。

!!!
%html{"ng-app" => ""}
  %head
    %script{:src => "//ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js"}
  %body
    %div
      %label Name:
      %input{"ng-model" => "yourName", :placeholder => "Enter a name here", :type => "text"}/
      %hr/
      %h1 Hello {{yourName}}!

さらにSlimの結果を載せる。http://html2slim.herokuapp.com/ の結果の一部を修正した。

doctype
html ng-app=""
  head
    script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js"
  body
    div
      label Name:
      input ng-model="yourName" placeholder="Enter a name here" type="text"
      hr
      h1 Hello {{yourName}}!

この結果を見ると必要なタイプ数は減っていることが分かると思う。ともかくHamlを書いているとHTMLタグの属性を書くために{}が必要で、それを書くのが煩わしい。プラグイン書けばどうにかなるのだが、それを使うなら、もっとシンプルなエンジンを使ったほうが良いかと考えた。

CSSエンジン

CSSエンジンもHamlと同じく、改行によってクロージャーを表現する。

$blue: #3bbfce
$margin: 16px

.content-navigation
  border-color: $blue
  color: darken($blue, 9%)

.border
  padding: $margin / 2
  margin: $margin / 2
  border-color: $blue

とか

table.hl
  margin: 2em 0
  td.ln
    text-align: right

li
  font:
    family: serif
    weight: bold
    size: 1.2em

のような書き方ができる。sassの他にscssという書き方もある。

もっと先鋭な人はlessとか使っていると思う。

自分はクロージャーのために{}を書く思想が受け入れられないので使えない。(だったらpython使えよ、というツッコミはなしの方向で...なにとぞ...)

Javascriptのための言語

言語と呼べばいいのか何なのかわからないが、Javascriptを書く手数を減らしてくれる。

square = (x) -> x * x

cubes = (math.cube num for num in list)

square = function(x) {
  return x * x;
};

cubes = (function() {
  var _i, _len, _results;
  _results = [];
  for (_i = 0, _len = list.length; _i < _len; _i++) {
    num = list[_i];
    _results.push(math.cube(num));
  }
  return _results;
})();

になったりする。

例えばAngularJSでは、

function TodoCtrl($scope) {
  $scope.todos = [
    {text:'learn angular', done:true},
    {text:'build an angular app', done:false}];
 
  $scope.addTodo = function() {
    $scope.todos.push({text:$scope.todoText, done:false});
    $scope.todoText = '';
  };
 
  $scope.remaining = function() {
    var count = 0;
    angular.forEach($scope.todos, function(todo) {
      count += todo.done ? 0 : 1;
    });
    return count;
  };
 
  $scope.archive = function() {
    var oldTodos = $scope.todos;
    $scope.todos = [];
    angular.forEach(oldTodos, function(todo) {
      if (!todo.done) $scope.todos.push(todo);
    });
  };
}

のようなコードを書くが、これがcoffeeだと、

TodoCtrl = ($scope) ->
  $scope.todos = [
    text: "learn angular"
    done: true
  ,
    text: "build an angular app"
    done: false
  ]
  $scope.addTodo = ->
    $scope.todos.push
      text: $scope.todoText
      done: false

    $scope.todoText = ""

  $scope.remaining = ->
    count = 0
    angular.forEach $scope.todos, (todo) ->
      count += (if todo.done then 0 else 1)

    count

  $scope.archive = ->
    oldTodos = $scope.todos
    $scope.todos = []
    angular.forEach oldTodos, (todo) ->
      $scope.todos.push todo  unless todo.done

と表現できる。http://js2coffee.org/ を使わせてもらった。

assetsエンジン

JavascriptCSSは大体の場合、最終的には1つのファイルとして扱ったほうが良い。複数のファイルに分散されていると、それごとに取得しに行かなければならない。またminifyという縮小化の名を借りた難読化を行うことも多い。

そういった処理を行うためのエンジンとしてsprocketsというライブラリがある。

このsprocketsの導入に前もって、 app/assets/javascripts, app/assets/stylesheets, app/assets/images などのディレクトリを作成する。jsとcssは、application.cssとapplication.jsというファイルを作成し、それぞれ、

//= require common
//= require_tree .

などと書いておく。require_treeが書かれていれば、そのディレクトリ内の全てのファイルがcombineされるし、読み込む順番を指定したいのであれば、requireによって個別に指定できる。前述の例では、common.js(common.css)を呼び出す。

そしてHTML上でlinkタグやscriptタグで、assets/application.css、もしくはjsにアクセスするようにすれば、アクセスごとにcombineされる。

sinatraへのsprockets導入はpadrino-sprocketsの0.0.2がオススメである。0.0.1はバグる。

ちなみにexecjsによってruby上でJavascriptを動作させようとするので、therubyracer(Google V8)などのgemを入れておく必要がある。

結果としてのGemfile

こうなる。

source :rubygems

# Project requirements
gem 'rake'
gem 'sinatra-flash', :require => 'sinatra/flash'

# Component requirements
gem 'rack-coffee', :require => "rack/coffee"
gem 'therubyracer'
gem 'coffee-script'
gem 'sass'
gem 'slim'

# Test requirements

# Padrino Stable Gem
gem 'padrino', '0.10.7'

gem 'padrino-sprockets', :require => "padrino/sprockets", :git => "https://github.com/nightsailer/padrino-sprockets.git"

つまり

生でHTML書いたり、生でCSS書いたり、生でJavascript書いていることはもう無いですよ、ということです。

言いたいこと

Padrino(Sinatra) + slim(html) + sass(css) + coffee-script(js) 上でAngularJSなどのMVCフレームワーク使って遊ぶと楽しいかもよ、と。

で、理想としては、バックエンドのAPIサービスとしてRailsを使い、Rails側はHTMLを吐かずにJSONの出し入れのみを行うこと。

JS-MVC(Sinatra) <--JSON--> JSON-API(Rails) <--SQL(ActiveRecord)--> DB

こうすれば、JS-MVC部分はiPhone/AndroidアプリだろうとFlash(Adobe Air)だろうと、同じサービスを同じAPI(ロジック層)で実現できるはず。

JSアプリケーション部分とAPI部分のインターフェースの策定方法や自動化に良いアイディアが生まれるとすれば、今後は、GmailGoogle Readerのようなアプリケーションがぽっこぽこ生まれるような世の中になると予想する。

そういう世の中になるんかな、を実感するために、MVCフレームワークを試してみる環境を作る、という企画でした。