backbone.js

2013年1月12日 (土)

Backbone.jsを使ったNode.jsアプリのCSRF対策

Ajaxはsame origin policyのため、AjaxのリクエストだったらCSRF対策いらないのでは?

残念。

FlashやJava appletを利用した場合にcsrfが起こる可能性があるそうです。
(参考: Rails 3.0.4と2.3.11からXHRリクエストの際もCSRFトークンの検証が必須になったので注意)

Backbone.jsを使っているとAjax部分は勝手に行われるので、CSRF対策のために拡張する必要がありそうです。

Stack Overflowを調べても、tokenを渡せばいいんじゃない?のそっけない返答しかなかったので、実装してみました。

概要

  • node.js側は、connect(ということはExpressも)にcsrf用のmiddlewareがあるのでそれを使用します。(app.js)
  • 初期ページ読み込み時にtokenをBackboneのメンバにセットします。(index.html)
  • Backbone.jsのAjaxの呼び出しを_csrfにBackboneのメンバに退避していたtokenをセットするようにラップします。(backbone-csrf.js)

実装

app.js

1 2 3 4 5 6
app.use(express.session());
app.use(express.csrf());
app.get('/', index);
index = function(req,res,next){
  res.render('index',{token: req.session._csrf});
}
view raw app.js This Gist brought to you by GitHub.
index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
<!DOCTYPE html>
<html>
  <head>
  <link rel='stylesheet' href='/stylesheets/style.css' />
  <script type="text/javascript" src="/javascript/jquery.min.js"></script>
  <script type="text/javascript" src="/javascript/underscore-min.js"></script>
  <script type="text/javascript" src="/javascript/backbone-min.js"></script>
  <script type="text/javascript" src="/javascript/backbone-csrf.js"></script>
  <script type="text/javascript">
  jQuery(function() {
  window.Backbone.CSRFToken = "<%- token %>";
  window.router = new MyRouter();
  Backbone.history.start();
  });
  </script>
  </head>
  <body>
  <div id="main">
  </div>
  </body>
</html>
view raw index.html This Gist brought to you by GitHub.
backbone-csrf.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Backbone.ajax = function() {
  var data = {};
  if (arguments[0].type && arguments[0].type !== "GET") {
    arguments[0].contentType = "application/json";
    if (arguments[0].data) {
      if (typeof arguments[0].data === "string") {
        data = JSON.parse(arguments[0].data);
      } else {
        data = arguments[0].data;
      }
    }
    data["_csrf"] = Backbone.CSRFToken;
    arguments[0].data = JSON.stringify(data);
  }
  return Backbone.$.ajax.apply(Backbone.$, arguments);
}

| | コメント (0) | トラックバック (0)

2012年12月31日 (月)

Backbone.js入門

はじめに

Backbone.jsのblog(Node.js + WebSocket + Backbone.jsのすすめ
Asset Pipelineのすすめ )を書いたのですが、あまり
反応がありませんでした。
理由を考えてみたのですが、blogの内容ではなく
そもそもBackbone.jsについてみんなよく知らない
のではないかと前向きにとらえることにしました。
そこでBackbone.jsを勉強しだして2ヶ月の私が、
JavaScriptはわかるけど、Backbone.jsはよく知ら
ないという人に向けてWebサーバをたてなくても
動作するサンプルを使って、手短に説明してみます。
そして次回はWebSocket(Socket.io)の連携について
説明してみます。

Backbone.jsが人気が出るようになった背景
Google Maps等、Ajaxを使い、クライアント側で動的
にページを更新することで、応答性が改善されました
が、サーバ側で部分的なHTMLを生成して、そのHTMLを
クライアント側でdivにAppendするといったことが行
われ、いつどこでこの部分を描画しているのかがわか
りにくくなりました。
また、スマートフォンの急速な拡大に伴い、モバイルも
重要なターゲットとなりました。

そこで、Ajaxによる複雑性を軽減し、さらにモバイル
でも使えるぐらい小さなライブラリが注目されること
になりました。

それがBackbone.jsです。

Backbone.jsはクライアント側にMVCを導入することで
プログラムの見通しをよくし、さらにコメントをあわ
せて1500行ぐらいの小ささのため、3G回線のモバイル
でも導入によるレスポンスの遅さをほとんど感じさせ
ません。※ただし、Backbone.jsは、jQuery(minimam
のzeptでも可)、underscore.js(コメント込みで1200
行程度)に依存します。

SPAについて
Backbone.jsを導入するとSPA(Single Page Application)
を意識してアプリを作成することになります。

SPAとは、その名の通り、一つ画面のみで構成されている
アプリのことです。

Webの場合は、最初のアクセスのみHTMLを取得して、以降
はAjaxやWebSocket等で必要なデータのみをやりとりし、
ページ遷移に対しては、HTMLをロードするのではなく、
JavaScriptでHTMLの内容を書き換えることで、素早い
ページの切り替えを行います。

Backbone.jsでは、SPAを支えるために大きく言って
RouterとModelとViewという3つに役割を導入しています。

Routerについて
RouterはPATHを監視し、PATHが変更されたら、その
PATHに登録された処理を呼び出します。
PATHの変更は、#(アンカー)が使われます。
アンカーはもともとページ内遷移に使うためのものですが、
Webサーバにアクセスしないという性質と、ブックマークが
できるという性質により、Backbone.jsにおいて、ページを
切り替える場合は、#以降のPATHを変更させます。
ブラウザがHTML5のPushStateが対応の場合は、Backbone.js
でPushState機能をOnにすれば、#ではなく変わりに実PATH
を使うこともできます。
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8"/>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
    <script type="text/javascript" src="http://underscorejs.org/underscore-min.js"></script>
    <script type="text/javascript" src="https://raw.github.com/LearnBoost/socket.io-client/master/dist/socket.io.min.js"></script>
    <script type="text/javascript" src="http://backbonejs.org/backbone-min.js"></script>
    <script type="text/javascript">
     var ExampleRouter = Backbone.Router.extend({
        routes: {
          "greeting/:language":   "greeting",
          ".*": "selecting"
        },
        greeting: function(language) {
          var msg = ""
          switch(language){
          case "spanish":
            msg = "!hola!";
            break;
          case "english":
            msg = "Hello!";
            break;
          default:
            msg = "ごめんやしておくれやしてごめんやっしゃー";
            break;
          }
          $('#contents').html(msg + '<br/><a href="#">back</a>');
        },
        selecting: function(){
          $('#contents').html(
          '<a href="#/greeting/spanish">Spanish</a><br/>' +
          '<a href="#/greeting/english">English</a><br/>' +
          '<a href="#/greeting/japanese">Japanese</a><br/>'
          );
        }
      });
      jQuery(function() {
        window.router = new ExampleRouter();
        Backbone.history.start();
       });
    </script>
  </head>
  <body>
    <div id="contents"></div>
  </body>
</html>
view raw route.html This Gist brought to you by GitHub.

上記のサンプルを任意の場所のファイルにコピーして
ブラウザでそのファイルにアクセスしてみてください。
(例) Macで/Users/takeshy/work/route.htmlという
名前でコピーした場合のURL。
file:///Users/takeshy/work/route.html

Routerのsampleについて
Routerは、Backbone.Routerを拡張します。routesの
プロパティに#以降のPATHをキーに、呼び出すメソッド
名の文字列を値としたHashを設定します。
そうすることで、Backbone.history.startのメソッドを
実行すると、#以降のPATHに応じて登録したメソッドを
実行してくれるようになります。
Pathに:名前を含めれば、(Sampleでは/greeting/:language
の部分)登録メソッドに対して:名前にあたる部分を引数
として渡すことができます。
また.*のように単に一致を判定したい場合は正規表現を
使えます。
Webサーバなしでページが遷移できることや、ブラウザの
戻る、進むボタンの対応や、特定のページをブックマーク
すると、ブックマークしたページの内容が表示されること
が確認できます。
※ここでは説明のためにRouteクラスをhtml内に記述して
いますが、普通は別jsに切り出します。

Modelについて
アプリの中心であるビジネスロジックのオブジェクト。
例えば、UserのModelだったら、名前やE-mail等の属性
を持ちます。
Modelのオブジェクトの集合を表わすCollectionと呼ば
れるクラスもあります。
Backbone.ModelやBackbone.Collectionを拡張することで
たくさんの機能が提供されます。

提供される機能

イベントのトリガー(Model & Collection)

Collectionの場合、内容を置き換えた場合はreset,
Modelのオブジェクトを追加した場合はadd、削除
した場合はremoveイベント等が発行されます。
Modelの場合、内容を変更した場合はchange,削除
した場合はdestoroyイベント等が発行されます。

データのSetter,Getter,validate(Modelのみ)

Modelオブジェクトに対して、defaults属性もしくは、
initialize({属性名:値})、もしくはset({属性名:値})
メソッドでセットした値は直接Modelオブジェクトの
属性にセットされるのではなく、attributesとよばれる
属性の中にセットされて、取得する場合は、get(属性名)
で取得できるようになります。
間接化することで、setメソッド実行によるchangeイベント
の発行やAjaxの送信にはattributesの内容のみを送信する
ので、Webサーバに送信しない属性を持つことができます。
validateメソッドを定義すれば、set時に値の妥当性を
チェックすることができ、不正時は、戻り値をセットする
ことで、errorイベントが発行され不正な値のセットを
防ぐことができます。

Ajax(Model & Collection)

RESTを前提として、save()やfetch()メソッドの実行で自動
的にAjaxでWebサーバにアクセスします。
ただし予めurl属性がセットされている必要があります。
Collectionのオブジェクトに対してfetchメソッドを呼びだ
すと、GET urlが実行されAjaxの戻り値の配列のJSONをmodel
の属性にもつModelオブジェクトに変換して、保持します。
IDがセットされたModelのオブジェクトに対してfetch()を
実行すると、GET urlRoot/IDの戻り値のJSONをModelの属性
にセットしします。またIDが未セットのModelのオブジェクト
にsave()を実行した場合、POST /urlRootのAjaxが実行され、
IDがセットされている状態でsaveするとPUT /urlRoot/IDの
Ajaxが実行され、destroy()を実行するとDELETE /urlRoot/ID
のAjaxが実行されます。

集合操作メソッド(Collectionのみ)

Collectionのオブジェクトに対して、each,map,findなど
underscore.js由来の便利メソッドがCollectionに対して
実行できるようになっています。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8"/>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
    <script type="text/javascript" src="http://underscorejs.org/underscore-min.js"></script>
    <script type="text/javascript" src="https://raw.github.com/LearnBoost/socket.io-client/master/dist/socket.io.min.js"></script>
    <script type="text/javascript" src="http://backbonejs.org/backbone-min.js"></script>
    <script type="text/javascript">
      var ExampleModel = Backbone.Model.extend({
        defaults:{ name: "anonimous"},
        validate: function(attrs){
          if(attrs.age < 0){
            return "age must be positive";
          }
        }
      });
      var ExampleCollection = Backbone.Collection.extend({
        url: "/examples",
        model: ExampleModel
      });
      jQuery.ajax = function(params){
        console.log(params);
        params.success(
          [{id: 1,name: "takeshy",age: 36},{id: 2,name: "hoge",age: 22}]
        );
      };
      jQuery(function() {
        var sample = new ExampleModel({age: 0});
        console.log(sample);
        sample.on("error",function(obj,err){ console.log(err);});
        sample.set({age: -1})
        console.log(sample);
        var collection = new ExampleCollection();
        collection.fetch();
        console.log(collection.toArray());
        var model = collection.get(1)
        model.on("change",function(){ $("#contents").html(model.get("age"))});
        model.set("age",model.get("age")+1);
      });
    </script>
  </head>
  <body>
    <div id="contents"></div>
  </body>
</html>
view raw model.html This Gist brought to you by GitHub.

上記のサンプルを任意の場所のファイルにコピーして
ブラウザでそのファイルにアクセスしてみてください。
(例) Macで/Users/takeshy/work/model.htmlという
名前でコピーした場合のURL。
file:///Users/takeshy/work/model.html

Modelのsampleについて
Modelのnameに対してdefaultsの属性をセットしています。
このためブラウザのJavaScriptのコンソールを開くと、
new ExampleModel({age: 0})で作成したオブジェクト
のattributesのnameにデフォルト値がセットされています。
validateメソッドにageが0以上かどうかをチェックして
いるため、set({age: -1})の実行をするとerrorのイベント
に登録したconsole.log(err)により、コンソールに
"age must be positive."が表示されます。
また、console.log(sample)でageが0のままであること
が確認できます。
ExampleCollectionにはurl属性に/examplesをセットし
model属性にExampleModelをセットしているので、
ExampleCollectionのオブジェクトに対しfetch()を実行
すると、Backbone.jsの機能により、Get /examplesが
実行され、戻り値のJSONの配列をExampleModelの
オブジェクトの集合に変換するはずです。
今回はWebサーバなしのローカルファイルで実行できる
よう、jQueryのAjaxをダミー関数で上書きして、パラメタ
をconsole.logに出力しているので、type: GET,
url: /examplesが実行され、ダミーで返したJSONの配列
に基づいてExampleModelのオブジェクトが2個格納されて
いることを確認できます。
collection.get(ID)でcollectionに格納されているIDが
1のModelのオブジェクトを取り出し、そのModelに対して
changeのイベントをObserveして変更があれば変更になった
値を画面に表示するような関数を渡しているので、その後の
model.setによる変更により、画面上に37が出力されている
ことも確認できます。

Viewについて
RouterでPATHごとに処理を登録しますが、その処理の
中でViewオブジェクトを生成し、Viewに処理を移譲する
ことが、Backbone.jsでは一般的です。

Viewオブジェクトに対しては、下記を行います。

events属性に、HTML内のイベントに対して処理を登録。
(Form内で属性の値を変更した場合に変更内容をModel
に反映するetc)

Modelの更新イベントに対して処理を登録(Modelが変更
されたらrenderメソッドを呼ぶことで再描画するetc)

renderメソッドを呼び出されると、templateを
展開し、elと呼ばれるプロパティに描画すべきHTMLの
要素をセットし、自身を返す。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8"/>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
    <script type="text/javascript" src="http://underscorejs.org/underscore-min.js"></script>
    <script type="text/javascript" src="https://raw.github.com/LearnBoost/socket.io-client/master/dist/socket.io.min.js"></script>
    <script type="text/javascript" src="http://backbonejs.org/backbone-min.js"></script>
    <script type="text/javascript">
      jQuery(function() {
        //Model
        var CounterModel = Backbone.Model.extend({
          defaults: {counter: 0}
        });
 
        //View
        var ExampleView = Backbone.View.extend({
          //el: "<div/>", Backbone.jsのデフォルト
          template: _.template($("#sample_template").html()),
          events: {
            "click #ignore": "ignore"
          },
          initialize: function(){
            this.model = new CounterModel();
            //this.model.on("change",this.render,this);
            this.listenTo(this.model,"change",this.render);
            var self = this;
            window.setTimeout(
              function(){
                var counter = self.model.get("counter") + 1;
                self.model.set("counter",counter);
                console.log(counter);
                if(counter < 100){
                  window.setTimeout(arguments.callee,1000);
                }
              },1000);
          },
          render: function(){
            this.$el.html(this.template(this.model.toJSON()));
            return this;
          },
          ignore: function(){
            this.model.off("change");
          }
        });
 
        //Route
        var ExampleRouter = Backbone.Router.extend({
          routes: {
            ".*": "show"
          },
          show: function(){
            var view = new ExampleView();
            $("#contents").html(view.render().el);
          }
        });
 
        window.router = new ExampleRouter();
        Backbone.history.start();
      });
    </script>
  </head>
  <body>
    <script type="text/html" id="sample_template">
      <input type="button" id="ignore" value="無視" />
      <p> Counter is <%= counter %></p>
    </script>
    <div id="contents"></div>
  </body>
</html>
view raw view.html This Gist brought to you by GitHub.

上記のサンプルを任意の場所のファイルにコピーして
ブラウザでそのファイルにアクセスしてみてください。
(例) Macで/Users/takeshy/work/view.htmlという
名前でコピーした場合のURL。
file:///Users/takeshy/work/view.html

Viewのsampleについて
ViewはModelオブジェクトと連携するのが基本なので
counterという属性のみをもつModelを定義しています。

Viewの一番重要なelという属性は、backbone.jsが
デフォルトとして<div/>がセットしています。
もちろんそれ以外がよければ、el: '<ul id="hoge"></ul>'
のように値をセットします。
単にtag種別だけでよければ、elをセットせず、
tagName: "ul"とすれば、el: "<ul/>"とした場合と
同じことができます。

template属性には、templateメソッドを定義するのが
よくあるパターンです。
今回はunderscore.jsのtemplateメソッドを使用して
いますが、view内にHTML文字列を書いたり、元HTML
にtemplateを仕込むのは、保守を考えるとイケていない
ので、Assets Piplineを使うのがいいと思います。

events属性には、このview内のHTMLの要素に対する
イベントをキーにメソッド名の文字列を値にセット
しています。

Backbone.jsのオブジェクトは、初期時にinitialize
メソッドが呼び出されるので、initializeの中で、
Modelオブジェクトを生成し、Modelオブジェクトの
変更イベントに対して、renderメソッドを登録
しています。
これにより、modelの値をviewを意識することなく
変更することができ、変更によってrenderが呼ばれて
表示が更新されます。
今回はTimerを使ってmodelのcounter属性を1秒ごと
に100まで+1しています。
これにより1秒おきにviewが更新されているのが確認
できます。
また、今まではmodelの変更をmodel.onで登録していた
のですが、その場合、Viewが廃棄される前にmodel.off
を使ってイベントハンドラを解除しないとメモリリーク
してしまうという問題がありました。
最近ViewにListenToというメソッドが追加され、ListenTo
経由でイベントハンドラをセットするとView破棄時に
自動的にイベントハンドラが解除され、メモリリークが
防げるので、これからはListenToを使いましょう。

お決まりのrenderメソッドの中でtemplateメソッドに
template内で使う変数名と値が組になったHashを
渡します。Backbone.jsのModelオブジェクトに対して
toJSON()を呼びだすと、attributesの値のコピーが
渡されるのでそれを使うのがよくあるパターンです。
templateメソッドにより、elにhtmlの要素をセットし、
メソッドの最後でviewオブジェクト自身を返すのが
決まりとなっています。

無視のボタンを押すとignoreが呼ばれるようにevents
属性にセットしたのは、modelに対するイベントハンドラ
を削除することで、modelの値が変わっても、画面が
更新されないことを確認するためです。
consoleを見れば、modelの値は更新されつづけている
ことが確認できます。

最後のrouterですが、viewのrender()メソッドにより
viewのel属性に生成されたHTMLを実HTMLに反映する
のはRouterで行うことがidiomとなっています。

終りに
最後のサンプルでは、model,router,viewと組み合わせた
ので、このviewのサンプルが理解できたら、backbone.js
の基礎は理解できたことになります。

もしわかりにくかったり、間違っている箇所があれば、
教えてください。

Happy backbone!

| | コメント (0) | トラックバック (0)

2012年12月20日 (木)

Asset Pipelineのすすめ

Backbone.js Advent Calendarの20日目です。

Railsを使っている人なら知っているけど、他の言語の
フレームワークを使っている人は知らないかもしれない
Asset Pipelineについて書きます。

Railsは3.1からSprocketsと呼ばれるライブラリを使って、
Asset Pipelineという仕組みを導入しました。
初めは仕組みがややこしく、デフォルトで導入しなくても
よかったんじゃ?と思っていましたが、Backbone.jsを使う
ようになって、Backbone.jsユーザにとっては、神機能だと
いうことがわかるようになりました。
Node.jsにもmincerというSprocketsのクローンがあります。
(説明はまた後で)

それではここからAsset Pipelineって何、Backbone.jsに
とって何がうれしいの?を述べます。
Asset PipelineはManifestに従ってJavaScriptもしくはCSS
を生成するためのツールです。
Manifestとは下記のようにファイルの先頭にどの順番でどの
ファイルを展開するかを指定することです。
blog.js.coffee
#= require_self             <-自分自身を展開
#= require backbone  <- パス直下のbackbone.jsを展開
#= require_tree ./templates <- templates,blogsディレクトリ配下を
#= require_tree ./blogs   <- サブディレクトリも含めて再帰的に展開
window.Blog =
  Models: {}
  Collections: {}
  Routers: {}
  Views: {}

Manifestで指定されたファイル or ディレクトリ配下の
ファイルを展開し、ひとつのファイルにまとめます。
ひとつのファイルにまとめることで、Webブラウザから
アクセスするパスはひとつだけですむようになり、描画まで
の時間が大幅に短縮されます。
さらにSprocketsの場合は、minifyといって変数名を短縮
したり、余計な空白を削除することでファイルを小さくし、
さらに圧縮可能なブラウザ用に圧縮済みのファイルも用意
します。
Backbone.jsでSingle Page Applicationを作成した場合は、
開始時に大量のJavaScriptを読み込むため時間がかかりやすい
ですが、それを大幅に軽減してくれます。

まとめたファイル名にはフィンガープリントが末尾に付加
されており、内容が変わった場合は、ファイル名が変わる
ことでキャッシュがクリアされる仕組みになっています。

また開発時は、MiddleWare(RailsであればRack,Node.js
ではconnect)にマウントすれば、Webサーバを再起動せずに
ファイルの更新がクライアントへの応答に反映されます。
これは動的にコンパイルを行っているため負荷がかかりますが、
本番時にはその機能を無効にし、予めコンパイルした静的
ファイルを用意することでレスポンスの高速化を行うことが
できます。

JavaScriptのファイルを生成する場合は、元のファイルは
JavaScriptだけでなく、CoffeeScriptがあればそれを
コンパイルしてJavaScriptに変換したものを展開して
くれます。
CSSのファイルを生成する場合は、元のファイルがLESS、
SASS、Stylusだった場合はそれをコンパイルしてCSSを
生成してくれます。
ejsの拡張子があるものに関してはejsの評価結果をJavaScript
やCSSに反映してくれます。
これは環境変数によって設定を変えたJavaScriptやCSSを
用意したい場合に便利です。

さらにBackbone.jsユーザにとって嬉しいことは、JST
(JavaScriptテンプレート)に対応していることです。
これによりTemplateのHTMLを別ファイルに切り出す
ことができます。
11日目のAdvent CalendarでAjaxによって別ファイル
のTemplateを呼び出す方法が記載されていましたが、
Asset Piplelineを使えばコンパイルにより静的に
Viewファイルのtemplateが作成されます。
post_view.js
 Blog.Views.Posts ||= {}
class Blog.Views.Posts.PostView extends Backbone.View
  template: JST["templates/posts/post"]
  events:
    "click .destroy" : "destroy"
 
tagName: "tr"
 
destroy: () ->
   
@model.destroy()
   
return false
 
render: ->
    $
(@el).html(@template(@model.toJSON()))
   
return this
templates/post/post.jst.hamlc
%td= @title
%td= @content
%td
  %a{href: "#/#{@id}" ;} Show
%td
  %a{href: "#/#{@id}/edit"} Edit
%td
  %a{href: "#/#{ @id}/destroy< /b>",class: "destroy"} Destroy
JSTのTemplate元のフォーマットがSprocketsの場合
はEJSもしくはEcoですが、mincerの場合は Haml Coffee
もしくはJadeになります。
mincerのほうは癖があり、プログラではないデザイナさん
には大変ということと、Railsからの移行を考えるとEJSを
使いたいということもあり、拡張子がjst.ejsの場合はJST
展開する修正を行ったmincerを作りました。

SprocketsはRails標準ということもあり、ドキュメントが
たくさんあるのでここでは説明せず、あまり知られていない
mincerについて述べます。
以前紹介したサンプルアプリにもmincerが使われています。

本番環境(静的にコンパイルする)の場合

node_modules/mincer/bin/mincer.js -I app/assets/javascript -o public/assets   /絶対パス/app/assets/javascript/blogs.js.coffee

npm install mincerでnode_modules配下にmincerが
インストールされるのでそれを実行
-I で読み込む元のJavaScriptファイルがある場所
-o でできあがったファイルを置くディレクトリ
最後にコンパイルしたいManifestつきのファイル名を
絶対パスで指定します。
(私の環境では絶対パス以外なぜかうまくいかなかった。)

開発環境の場合
app.configure('development', function(){
  var Mincer  = require('mincer');
  var environment = new Mincer.Environment();
  environment.appendPath(__dirname + '/app/assets/javascript');
  app.use('/assets', Mincer.createServer(environment));
  app.use(express.errorHandler());
});
上記をメインのjs(app.js? or server.js)に記述すると、
/assets/ファイル(拡張子なし)で/app/assets/javascript配下の
ManifestつきのCoffeeScriptなりJavaScriptファイルを参照できる
ようになります。
Node.jsを再起動することなく、JavaScriptのファイルの変更が
反映されます。

Happy Backbone!!

| | コメント (0) | トラックバック (0)