2019年1月26日 (土)

Windows 10がSleep状態から回復しない問題の対応

最近WindowsがSleepしている状態から、何をやっても反応がなく、電源を長押しして強制シャットダウンするしかない状態が頻発していたのですが、Sleepしても毎回発生する訳ではなく、原因がわからず困っていたのですが、起きないようにする方法がわかりました。
環境
PC: Thinkpad E480 SSD 256G
OS: Windows 10 Pro
対応手順
1. Windows+rでファイル名を指定して実行を起動
2. devmgmt.mscを入力してOKをクリックしてデバイスマネージャを起動
3. システムデバイス->Intel Management Engine Interfaceをクリック
4. 電源の管理のタブを選択
5. 電力の節約のために、コンピュータでこのデバイスの電源をオフにできるようにするのチェックをはずす
以上
Intel_management_engine_interface
参考記事:

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

2014年12月 7日 (日)

第2回Apache Cordova勉強会の感想 & AngularJSとBackbonejsの利用シーンの違いについて

IIJ様の本社で第2回Apache Cordova勉強会 が開催されました。
勉強会の最初にアシアルの田中さんが"みんなで気軽に声を掛け合いましょう"と声がけをされて、発表の間に軽食時間を設けて、和気あいあいとなるような雰囲気を作ってくださり、本当に心地いい時間を過ごすことができました。
会場の提供、準備、さらには軽食までご用意してくださった、IIJ 様、アシアル 様ありがとうございました。

CordovaでANGULARJSアプリ開発


ここで紹介されているOnsen UI はすごいと思います。
AngularJSの独自タグの仕組みを使って、"ons-list"のようなタグを使えばそれだけでネイティブ風のリストができてしまうのは、まさにAngularJSのベストな使い方と言ってもいいと思います。
しかも公式サイトのドキュメントが本当にすごくしっかりしていて、日本語で読める!英語版も用意されているので、オフショアでの開発も問題ないです。
これが無料で使えるなんて本当に有り難いです。

~新しい着回しと出会おう~ 『XZ(クローゼット)』 を支える技術 -Cordova編-


AngularJSとionicというMobile App向けのUIフレームワークを使って短期間で完成させたそうです。
今風のアプリが短期間にできるのは、やっぱりCordova便利だなと思います。
そして驚いたのはCrosswalkを使ったデモです。
CrosswalkとはAndroidアプリ内にWebブラウザごと入れてしまうことで、端末ごとの差異をなくし、最新のブラウザの技術が使える仕組みです。
実際にCrosswalkありとなしのデモを見て、パフォーマンスの差にこんなに差があるのかと驚きました。
スタジオ・アルカナ様は、最新の技術の活用がかなり上手だと感じました。

smartFXにおけるApache Cordovaの活用について


最後が私が発表させていただきました。
解説はqiitaにSPAのサイトをアプリにするメリットと開発での問題点について として書きましたのでそちらをご覧ください。

全体の感想

やはりAngularJSの勢いがすごいですね。

AngularJSに対して感じたことは、サクっとアプリを作るにはもってこいだと思います。
もちろんその前にSPAとAngularJSに慣れる必要がありますが。。
特に「OnsenUI」のようにUIまでうまく組み込んだフレームワークを使えば、本当に手軽にアプリを作れちゃうと思います。
アプリのUIがちゃんとしてないとAppleにリジェクトされるという心配もなくなり、サービスの実装に集中できると思います。
それに対して、Backbonejsはどうか?
やはり手軽に開発するのではないアプリに向いていると思っています。
Backbonejsは、2010年から開始しているにも関わらず、いまだに空白行、コメント行もあわせて1500行ぐらいというシンプルさ。
名前の通り背骨だけで、アプリのほとんどを自分で作り込んでいくことになります。
複雑でない分、smartFX のようにwebsocketの対応もすぐですし、localStorageを使ったキャッシュも素直に実装できました。
smartFXはFXの情報サイトとしてはかなり後発な上、他のサイトにないサービスがある訳ではなかったので、勝負をかけることができるのが、スマホでの操作性という部分でした。
そのためSPAにも関わらず、初期読み込みをできるだけ速くするようにJSのサイズを小さくしたり、初期で使うデータは予めHTML内にJSONで書き出しておいたりなど色々を工夫をこらしています。
複雑なことをしようとしても、フレームワークへの影響を考えなくてすむのがすごくいいです。
ライブラリが育ってしまえば、AngularJSでもBackbonejsでも開発効率は変わらなくなっていきますし。
なので今後も必要に応じてBackbonejsを使っていくことになると思います。
第3回Apache Cordova勉強会も開催されるので、興味のあるかたは是非。

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

2014年11月15日 (土)

プログラマ35歳限界説と出口戦略について

先月35歳になった私は、人生で6度目の転職活動を行った。

今回初めてプログラマとしてではなく、違うキャリアを意識
して活動した。
具体的には、ECサイトやアクセスの解析を行う会社など、
マーケティングやユーザの行動解析のノウハウが身に付く
ような会社を選択した。

今まで、自分のプログラミングスキルを上げることが、自分
の付加価値の向上につながると思ってきたし、事実そうだった
と思う。

ただクラウド上にスケーリングするようにサーバ構築から、
DB設計、アプリ開発まで一通りできるようになった今、更に
プログラミングスキルを上げたり、扱える言語の数を増やす
ことが自分の付加価値をあげることかと考えた時に、そうで
はないと感じた。

私がプログラマとしてすごい奴なのかと言われたら、
全然そんなことはなく、大学生のTwitterやBlogを見ても、
あぁこの人には歯が立たないという人がたくさんいるし、
更に中学生でさえ、敵わないと思う人がいる。

逆にできないプログラマかと言うと、そうでもない。
このプロジェクトは、私でないと成功しなかったと言ってく
ださったプロジェクトが何個もあったり、20近くプロジェクト
を経験したが、私よりできるプログラマはそれほど多くなかった
ことを考えると、できるほうだと思う。

プログラマは、ピラミッド階層になっていて、高いところを
見ると果てしなく高く、下を見るとたくさんの人がいる。

大学時代、就職活動で初めてPCを触り、F2 SetUpの文字に
OSの起動はF2なのかと思い、押下してBIOSが表われ、
あたふたしたり、InternetExplorerの初期ページが大学の
ページになっていて、リンクを押しまくっても休講のページ
や施設案内のページにしかたどりつけなくて、どうやったら
皆のように、yahooのページに行けるのか悩んでいた私が、
気がついたら今の位置にいた。

ここで表題の業界でよく話題になるプログラマ35歳限界説
についてだが、35歳になって以前よりプログラミング能力が
落ちたかと言うと、まったくそんなことはない。ひらめきは
多少落ちたかも知れないが、以前より経験と知識がある分、
上手に書けるし、プログラミングも相変わらず楽しい。

それでも、35歳にしてプログラミングとは違うキャリアを
指向した理由は、一般的なシステムなら構築できるスキルを
得た状態で、さらに上を目指すよりも、他の知識を身につけ
るほうが役に立てると考えたから。

プログラミングだけで活躍できれば、話は楽なのだが、
y combinatorで混乱したり、モナドの説明を何度見ても
いっこうに理解できる気がしない私がそこを目指すのは
無理があるなぁと。

そこで最近どの企業に行っても、アクセスログ分析、ユーザ
特性・動向分析を行いたいが、できていないと聞き、その
分野はプログラミングが必要だが、大事な解析の部分は
経験・ノウハウによるところが大きいので、そこを身に
つけていくことにした。
また、今まで意識したことがなかったマーケティングも、
どの業界がどのぐらい全国にあるか、各業界の売上はどの
くらいか、業界内の各企業の割合、主要顧客の年齢・性別の
割合、よく使われているソフトなどを知ることで、より戦略的に
立ち回れるようになりたいと考えた。

もちろん、プログラミングが中途半端になると、ただ遠回り
しただけになってしまうので、今後も勉強していくし、そも
そも採用はプログラマで採用されているという。

社会人人生の折り返しとして、スタートを切ったつもりで
貪欲に新しいことに取り組んでいきたい。

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

2013年12月19日 (木)

Socket.IO用フレームワーク socket.io-reqev

この記事はNode.js Advent Calendar 2013 19日目の記事となります。

前書き

Push通知のフレームワークとしてSocket.IOがよく使われています。
Socket.IOは、Websocketが使えない環境でもPUSH通信を可能とすることで有名ですが、それ以外にも、namespace機能やroom機能、イベントを自由に使えるなど、直接websocketを使うよりも便利な機能が多くあります。
機能は豊富ですが、書き方にルールを決めずに気軽にあちらこちらでpushを使うと、見通しが悪く保守が大変なアプリになってしまいます。
そこでSocket.ioがベースの、簡単で保守しやすいプログラムが書けるsocket.io-reqevというフレームワークを紹介します。

socket.io-reqevの概要

socket.io-reqevは、pubsub機能 + requstごとの応答(HTTPのGETに該当)を簡単に行うために作られています。
クライアント(ブラウザ)とサーバ(node.js)側にそれぞれにライブラリが用意されています。

クライアント側

URL(socket.ioホスト名 + path)とコールバックを指定してsocket.io-reqevオブジェクトを作成し, watchメソッドに購読したいイベント名とrequestオブジェクトを指定するだけです。
サーバから応答があると指定したコールバックが呼び出されます。

サーバー側

socket.io-reqevオブジェクトを作成します。
eventsフィールドとrequestメソッドを持つオブジェクト(サービスと呼ぶ)をPathを指定してsocket.io-reqevオブジェクトに登録します。

クライアントからの通信にeventsフィールドがあると、eventsフィールドのeventごとにイベント名のroomにクライアントを登録し、サービスがイベントを発行すると、サービスが発行したイベント名と同じroomに登録されているクライアントにイベントの通知がいきます。

クライアントからの通信にrequestsフィールドがあると、requestsフィールドのrequestごとにサービスのrequestメソッドにそのrequestが渡され、requestメソッドからのcallbackを通じた応答をクライアントに返します。

インストール

npm install socket.io-reqev

デモ

socket.io-reqevを使ったデモ

概要を体感するために上記のリンク先のデモを実行してみてください。
Demoではサーバ側にタイマーサービスとして、5秒、10秒,30秒ごとに時間を通知するイベント(five,ten,thirty)と現在の時間を返すcurrentというリクエストが用意されています。
クライアント側には、それらごとにチェックボックスを用意して、sendボタンを押すごとに、チェックに応じたwatchが実行され、サーバからの応答をすべて画面の下部に追記していきます。

requestのcurrentにチェックがついている場合は、sendボタンを押したら即座に一度だけ、現在の時間が書き込まれます。
eventsの5秒、10秒、30秒は、秒がそれぞれ5、10、30で割り切れる時間になった時はずっと応答が返ってくるため、5秒、10秒にチェックが入っている場合は、00:00:05の時は1つだけ書きこまれ、00:00:10の時は5秒と10秒のイベント2つが応答を返すため、2つ書きこまれます。
sendは何度でも押せます。つまりwatchメソッドは何度でも呼び出し可能です。
sendを押すごとに前回購読していたイベントはとりやめられ、新たなイベントで上書きされるので、ブラウザのnetworkを見ると、イベントを減らすと通信も減ることがわかります。
スマホのWebサイトの場合は、通信量とバッテリ消費の結びつきは大きいため、購読だけでなくとりやめも簡単なのはすごく使い勝手がいいです。

Demoのソース解説

node.js側

socket.io-reqvのオブジェクトをsocket.ioオブジェクトを渡して初期化しています。
timerサービスを/timerで登録しています。
本質的なところは以上です。
temporary web server以下のソースは、index.htmlやjs配下のJavaScriptを返すためのWebサーバ機能です。

Timerサービスの実装になります。
serviceとしての要件であるeventsフィールドとrequestメソッドがあります。
socket.ioに依存がないので、テストがしやすそうですね!

this.eventsに5秒、10秒、30秒ごとに通知を行うためのfive,ten,thirtyのイベント名がセットされています。
サービスの初期処理で1秒ごとのタイマーを実行していて、その中で現在秒が割り切れた場合にイベントを発生させています。
また、requestメソッドも用意して、クライアントからcurrentという文字列がきた場合は現在時刻の文字列を返しています。
requestに引数がほしい場合は、requestとして文字列ではなく、オブジェクトを渡すことで実現できます。

ブラウザ側

backbone.jsのcollectionとviewをindex.htmlに埋めこんでしまっているためちょっと見づらいです。
すみません。
socket.io-reqevが直接関わっているのは18行目から30行目までです。

viewの初期処理

  1. timerCollectionというcollectionを作成
  2. timerCollectionのaddイベントに対して、画面下部に応答を追記する処理を登録

viewのrender処理

  1. event(current)とreqest(five,ten,thirty)ごとのチェックボックスとsendボタンを作成

viewのイベント

  1. sendボタンを押下すると、events,requestそれぞれでチェックがついている値の配列を作ってtimerCollectionのwatchを呼び出し。

timerCollectionのwatchメソッド

  1. 初回呼び出しの場合は、socket.io-reqev-clientのオブジェクトをsocket.ioサーバのURL+PATHとサーバから応答があった時用のcallbackを渡して作成
  2. socket.io-reqev-clientオブジェクトのwatchメソッドにviewから渡されたeventsの配列とrequestsの配列を渡す。
  3. socket.io-reqev-clientがsocket.ioサーバにrequestsとeventsを送信
  4. socket.ioサーバから応答があると、 socket.io-reqev-clientがtimerCollectionから渡されたcallback実行
  5. callbackの中でtimerCollectionのaddメソッドに応答を渡すことで新たなTimeオブジェクトをcollectionに追加
  6. veiwのイベントリスナーが上記のaddに反応して、画面下部に応答を追記する処理を実行

viewのremoveメソッド

今回はどこにも呼び出し箇所がない。
ブラウザのjavascriptコンソール上でv.remove()と実行することで呼び出し可能。
Viewが破棄され画面が真っ白になる。
Backbone.jsの決まりとしてveiwの開放時にviewのremoveを呼び出すことでview内でlistenToで登録されたイベントハンドラが開放されるが、timerや今回のようなsocket.ioのリソースは自分で開放する必要がある。
viewの中でremoveをオーバーロードして、そこでsocket.ioのリソースを開放している。
それによりsocket.ioの通信がstopする。(socket.ioの死活監視の通信は残る)

まとめ

去年にNode.js + WebSocket + Backbone.jsのすすめ という記事を書いてその中で実装はちょっと大変だみたいなことを書きましたが、その後経験をつみ、このようなライブラリを使うことで、効率的にアプリを書けるようになりました。
よそよそしく紹介しましたが、socket.io-reqevは自作です。
server側のio-reqev.jsは57行、client側のio-reqev-client.jsは61行というソースの小ささのため、socket.ioを知っている人が見るとすぐ理解できると思います。
Backbone.jsもそうですが、ライブラリが小さくても、考え方の導入次第でプログラムの作りを大きく改善できます。
ライブラリが小さいと、ソースも読めて融通もきいていいですね。

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

2013年9月13日 (金)

JSONデータの圧縮について

はじめに
HTTPのContent-Encoding: gzipにより、アプリ側が意識しなくても
Webサーバ、ブラウザ間で自動的に圧縮、伸張が行われるため、JSON
データの冗長性に対してそれほど気にしないと思います。
しかし、socket.ioの場合、圧縮はサポートされていないため、気兼ねなく
JSONを使うとデータサイズが大きくなりがちです。またsocket.ioを使う
ようなリアルタイムの頻繁なやりとりでの巨大なデータサイズは、ネットワーク
に対する影響も大きいものとなります。
そこで、チャートのような似たようなデータの固まりのJSONのサイズを小さく
する仕組みを考えました。


前提
まず元のデータはドル円の1分足のデータで下記のようなものです。
20130913_215757
141本分の1分間のローソク足データに対して、 11901Byte!!


項目ごとの配列に変更
まずopen,closeなどの重複した項目名がが目に付きます。
そこで項目名を一度だけ出力するように、各項目ごとの配列に変更してみます。
20130913_215904
6720Byte。これにより元のデータサイズの56%になりました。


配列の値を差分に変更
チャートの数値データは似たような値のため、配列の値を一つ前の値の差分を
格納してみます。
時間は1970 年 1 月 1 日 00:00:00 UTC からの経過ミリ秒に置き換えます。
20130913_215929
2291Byte。1/3のサイズ。元のデータからは約20%になりました。
ただ、1フレームですむ狙いの1400Byte以下まではまだ削減する
必要があります。
さらなるデータサイズ縮小を狙って、圧縮アルゴリズムについて考察
してみます。

圧縮のアルゴリズムについて
一般的な可逆圧縮方法は大きく分類すると次の2つ。
  1. データを元よりも短い記号に置き換える(符号化)
  2. 既に出てきたデータと同じ部分を繰り返し情報におきかえる
 
データを元よりも短い記号に置き換える(符号化)
JSONの数値は各桁の数字を1Byteで表現しているため、1Byteで表現できる
256種類のうちたった10個しか使っていません。
0-9以外の印字可能なascii文字も使うことで、1Byteで95個の表現を使用できます。
95という数字は 00000000-01011111まで。各bitの組み合わせごとにascii
文字を一意に決定します。
今回の数値を表現するbyteのフォーマットは3種類あります。
共通事項として、先頭Bitはascciiでは使わないため必ず0
になり2Bit目が0の場合はそのByteで数値が終わり、1の場合
は続くByteも数値に含まれることを表わします。
先頭Byte(数値1つにつき出現回数 1回)
  先頭Bit: 必ず0
  2Bit目: 1Byteで表現できない場合(数値が-32〜31の範囲外) ON
  3Bit目:
    数値が-32〜31の場合
      数値に+32したものを5bitで数値化(0〜63)
      例 -32の場合 00000000 1の場合 00100001
    数値が-32〜31の範囲外
      3Bit目: 必ず0
      4Bit目: 数値がマイナスの場合は1、0以上の場合は0
      5Bit目以降:
        10bit以上で5で割れるbit数になるよう先頭を0でpaddingさせる。
 
中間Byte(数値1つにつき出現回数0〜複数回)
  先頭Bit: 必ず0
  2Bit目: 必ず1
  3Bit目: 必ず0
  4Bit目以降:
    前のByteのbit並びの続き
 
最終Byte(0〜1回)
  先頭Bit: 必ず0
  2Bit目: 必ず0
  3Bit目以降:
    前のByteのbit並びの続き
    例
     -33の場合  01010000  00100001
     1024の場合  01000001  01000000  00000000
さらに上記の符号化により、JSONの配列の各データの区切りの,を
使用しなくて
すむため、さらなるデータ表現の効率化が見込めます。
20130913_220008
1379Byte。約60%のサイズ。元のデータからは12%以下になりました。


既に出てきたデータと同じ部分を繰り返し情報におきかえる。
1つ前のデータの差分ごとにした数値配列にし、同じデータが
  連続した場合はエスケープ値の後を繰り返し数とすることで
  同値、もしくは等差数列の場合に圧縮します。
  さらに同じ値の連続は起こりやすいと考えられるため、差分が0の
  連続の場合は0をセットせず、直接エスケープ値の後に繰り返し
  数をセットし、繰り返しの差分が0以外の場合はその数値をセット
  した後にエスケープ値をセットし、その後に(繰り返し数 x -1)を
  セットする。
  ここでは-32をエスケープ値とし、-32自体を表現したい場合は
  -32の後に0を置く(繰り返し0はありえないため)
  例 [100,101,101,101,101]の場合
    [100,1,-32,3]
    まず先頭は0からの差分である100、次に101-100の1がきて、
 その後差分0が3回続くため-32(エスケープ値)の後、繰り返しの3
 が入る。
  例 [101,102,103,104,105]の場合
    [101,1,-32,-3]
 まず先頭は0からの差分である101、次に102-101の1がきて、
 その後差分1が3回続くため-32(エスケープ値)の後、繰り返し数3
 x -1 の-3が入る。
20130913_220030
上記対応により625Byte!! 元のデータからは6%以下。
この圧縮が有効に効いた理由は、元データに1分足の時刻データが含まれて
おり、時刻情報全体が等差数列になっていたためです。
それだったら最初の時刻だけ送れば後は自明という気もしますが、休日を
挟んだ場合など途中に欠落がある可能性があるため、それを考慮する必要が
ない利点があります。
上記をJavaScriptで実装したものを、num_converter.js として公開しました。
残念ながら少数には使えませんが、小数点以下の桁数を決めておいて、
圧縮前に整数化し、展開後に少数に戻せばいいと思います。

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

2013年6月 1日 (土)

JavaScriptをminifyしてgzipしてS3へのアクセスにする

最近Require.jsが取り上げられることが多いのですが、私は使っていません。

Backbone.jsを使ったアプリには、非同期ローディングよりも、Javascriptをひとつのファイルにまとめてminifyして、gzip圧縮して最初に読み込んでしまうほうがいいと思ってます。

例えば今開発しているアプリのJavaScriptのそのままのSizeは651KBですがminifyすると517KBになり、gzip圧縮すると117KBとなります。

Node.jsを使っている私の一押しはやっぱりmincer です。

mincerの解説はAsset Pipelineのすすめ を参照ください。

mincerは、minify,gzip圧縮の機能はないので、別に対応する必要があります。 さらにそのファイルをS3にアップロードして、S3にJavaScriptをホスティングしてもらうまでのNode.jsのscriptを作成しました。

#!/usr/bin/env node
var fs = require('fs');
var gzip = require('gzip');
 
var http = require('http');
var Mincer = require('mincer');
var exec = require('child_process').exec
var querystring = require("querystring");
var AWS = require('aws-sdk');
AWS.config.update({accessKeyId: ''access_key_id'',
secretAccessKey: ''secret_access_key'',
region: "ap-northeast-1"});
var s3 = new AWS.S3();
 
var environment = new Mincer.Environment();
environment.appendPath('app/assets/javascripts');
 
environment.findAsset('application.js').compile(function (err, asset) {
if(err){
console.log(err);
process.exit(1);
}
var params = [
'compilation_level=SIMPLE_OPTIMIZATIONS',
'output_format=json',
'output_info=compiled_code',
'output_info=warnings',
'output_info=errors',
'output_info=statistics'
]
params.push(querystring.stringify({js_code: asset.toString()}));
var param = params.join("&")
var request_header = {
'user-agent': 'node.js',
'content-length': param.length,
'content-type': 'application/x-www-form-urlencoded'
}
var options = {
host : "closure-compiler.appspot.com",
path : "/compile",
method : "POST",
headers : request_header
}
var msg = ""
var req = http.request(options,function(res){
res.setEncoding('utf8');
res.on('data', function (chunk) {
msg += chunk
});
res.on('end', function (chunk) {
try{
var result = JSON.parse(msg);
}catch(e){
console.log(msg);
console.log(e.stack);
process.exit(1);
}
if(result.errors){
console.log(result.errors);
console.log("cancelled above error!");
process.exit(1);
}
var jsFile = __dirname + "/lib/compiled.js";
gzip(result.compiledCode, function(err, compData) {
if (err) throw err;
var params = {
Bucket: ''bucket_name'',
Key: asset.digestPath + ".jgz",
Body: compData,
ACL:'public-read',
ContentEncoding: "gzip",
ContentType: "application/x-javascript" };
s3.client.putObject(params,function(err,data) {
if (err) throw err;
var url = "https://s3-ap-northeast-1.amazonaws.com/" +
''bucket_name'' + "/" + asset.digestPath + ".jgz";
console.log("It\\'s post! " + url);
fs.writeFile(jsFile, "ASSET_JS_PATH='" + url + "';", function (err) {
if (err) throw err;
console.log('SUCCESS');
});
});
});
});
});
req.on('error', function(e) {
console.log('problem with request: ' + e.message);
});
req.write(param);
req.end();
});

上記スクリプトの'' ''で囲まれている箇所は適切な値に置き変えてください。 また、mincerのmanifestファイルはapp/assets/javascripts/application.js だと仮定しています。適切に書き換えてください。

minifyには、googleのclosure-compilerのWebサービスを使って行なっています。

closure-compilerはjarとして、提供されているのでそれを使うのもいいと思います。Webサービスは極まれに落ちていることがあるし、今後もサービスが続くとは限らないので。ただ、Javaをinstallしていない環境を考慮したり、jarがそこそこ大きいので配布するのも面倒だったため、Webサービス利用にしました。

利用方法
npm install mincer
npm install aws-sdk
node mincer_s3upload.js

上記コマンドを実行すると、lib/compiled.jsに下記のようにs3のURLの定数が定義されます。(下記URLは適当)

ASSET_JS_PATH='https://s3-ap-northeast-1.amazonaws.com/bucketname/application-27c2c991a5dc8291254e23f14bc21829.js.jgz';

app.jsを下記のようにすることで、開発の場合は動的compileによりファイルの変更を即座に変更するようにしています。

require('./lib/compiled');
app.configure('development', function(){
var Mincer = require('mincer');
var environment = new Mincer.Environment();
environment.appendPath(__dirname + '/app/assets/javascripts');
app.use('/assets', Mincer.createServer(environment));
ASSET_JS_PATH = null
});
 
app.get('/', index);
 
function index(req, res, next){
res.render('index', { js_path: ASSET_JS_PATH });
}
view raw app.js This Gist brought to you by GitHub.

index.ejsは下記

<% if(js_path){ %>
  <script type="text/javascript" src="<%- js_path %>"></script>
<% }else{ %>
  <script type="text/javascript" src="/assets/application.js"></script>
<% } %>

Happy Backbone!!

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

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)

2012年12月17日 (月)

Node.js + WebSocket + Backbone.jsのすすめ

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

新しい技術は興味はあるけど、主流にならない技術も多くて全部覚えるにはお腹いっぱいという皆さん。

私もこれはという技術を身につけて、一生食べて行けれたらと常々思っています。

そしてようやくこれはと思える技術に出会いました。

それが表題のNode.js+WebSocket+Backbone.jsです。

それを使った簡単なサンプルアプリ(backbone-railsのサンプルをrailsではなく

Node.jsに置き換え、DBをRedis、データの同期にSocket.ioを使ったもの)のソースは

ここにあります。アプリの解説はまた今度します。

今回はなぜこの組み合わせに賭ける気になったのか思ったを述べます。


利点
  1. デバイスを選ばない
  2. HTML5の利点でよく言われているようにPCはもちろん iPhone、Androidでも動作します。

  3. ネイティブ並に動作が速い
  4. Backbone.jsを用いてSingle Page Applicationとして作成するため、 ページ遷移ごとにServerにアクセスせず、JavaScriptによるページの 切り替えのため、一瞬で画面が遷移します。

  5. リアルタイム更新
  6. WebSocketを用いてBackbone.jsのModelを同期させれば、 Backbone.jsのView側でイベントを検知して表示を更新 してくれるので、実装側としては、勝手にリアルタイム 更新ができている感じです。

  7. Serverへの負荷が低い
  8. linkedInのモバイルの事例で見られるように、Node.jsは マシン効率がものすごくいいです。またWebSocketにより、 表示更新のための無駄なアクセスが必要がなくなります。

  9. 覚える言語がJavaScriptだけでいい
  10. 複数の言語を覚えるより、一つの言語に習熟してライブラリ、 関連ツールを覚えるほうが、開発効率は高いはず。 もちろん、CoffeeScriptやDart、TypeScriptなどJavaScriptに 変換できる言語も使えます。私はBackbone.jsと同じ作者が開発 したCoffeeScriptを愛用しています。

欠点
開発効率は現時点ではRailsのほうが圧倒的に速いです。
Node.jsの場合、DBに複数回アクセスする場合などcallbackが入れ子に
なり、ややこしくなります。
Backbone.jsの場合、Rest前提のため、複数のデータを一度に更新したい
場合や確認画面をはさみたい場合などにかなり頭を悩ませます。
テストの書きやすさやRepl機能の充実度もRailsのほうが上です。
Node.jsを勉強したてということもあり、実感としては1.5倍~倍近く
工数がかかっています。
開発工数も考えると、RESTサーバはRails等で開発したり、Single Page
Applicationにする箇所をよく使う機能に限定する等もありかもしれません。

まとめ

今までのプラットフォームは、言語こそ違えどできることはほとんど
変わらない中、Node.jsでは、他のプラットフォームでは真似できない
リアルタイム性やマシン性能を有効に活用できるほぼ唯一のプラットフォーム
だというのは今後も生き残る可能性大だと思います。
開発が難しいと言う点は、これから情報が増え、ツールが充実したり
することで徐々に改善されていくと思います。
そしてプログラマとして希少価値をあげるためには、難しいことも習得する
必要があることを考えたら、丁度いいと思いませんか?


追記

せっかくなので上記のサンプルアプリを公開してみました。

複数ブラウザでアクセスすると、瞬時にお互いの画面が更新されるのが
わかると思います。(WebSocketのおかげ)
ページの遷移もWebサーバにアクセスしないので一瞬です。
それにも関わらず、ブラウザの戻るに対応していたり、どの画面
もお気に入り登録して、その画面に直接飛べます。(Backbone.jsのおかげ)

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

«第1回 渋谷Edge Rails勉強会×株式会社ドリコム事例発表 + heroku de rails事例発表