AngularJSなどJavascriptのMVCフレームワークを試してみる環境について作ってみる
Ruby, RailsやRubygemsの仕組みがある程度、わかっている人向けに書く。
あと、作ってみただけであって、実際に使ってはいないので、色々と問題あるかもしれない。
「HTMLやCSSやJavascriptを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.
このJavascriptのMVCフレームワークを試す前は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エンジン
JavascriptやCSSは大体の場合、最終的には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部分のインターフェースの策定方法や自動化に良いアイディアが生まれるとすれば、今後は、GmailやGoogle Readerのようなアプリケーションがぽっこぽこ生まれるような世の中になると予想する。