「express」と一致するもの

以前の記事で紹介した、 connect-cache-manifestですが、 実は自分でちゃんと使ったことがありませんでした。

今回、一通り動くところまで使ってみたので、参考までに紹介します。 Expressと Jadeを使う前提です。

多くの場合、ディレクトリの構造は次のようになるのではないでしょうか。

|-- app.js
|-- public
|    |-- js
|    |    |-- foo.js
|    |    |-- bar.js
|    |-- css
|    |    |-- foo.css
|    |    |-- bar.css
|    |-- images
|         |-- foo.png
|         |-- bar.png
|-- views
     |-- xxx.jade
     |-- yyy.jade

このような場合での、connect-cache-manifestの設定は次のようになります。

var express = require('express');
var cacheManifest = require('connect-cache-manifest');
var app = express();
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(cacheManifest({
  manifestPath: '/application.manifest',
  files: [{
    dir: __dirname + '/public',
    prefix: '/static/'
  }, {
    file: __dirname + '/views/xxx.jade',
    path: '/html/xxx.html'
  }, {
    file: __dirname + '/views/yyy.jade',
    path: '/html/yyy.html'
  }],
  networks: ['*'],
  fallbacks: []
}));
app.use('/static', express.static(path.join(__dirname, 'public')));
app.get('/html/:name', function(req, res) {
  res.render(req.params.name);
});

これで、/application.manifestが生成されるようになります。 使ってみて不満が二つありました。一つは、emacsやvimで*~のバックアップファイルができた場合にそれもマニフェストに入ってしまうこと、もう一つは、jadeファイルが複数ある場合にリストアップしなければならないことです。そのうち気が向いたら改良しようかな。

これで、HTML5のマニフェストを使いつつ、ほとんど気にせずに普通にコーディングができます。

そうそう、ついでにもう一つ。ファイルを新規に追加したときは、nodeを再起動しないといけません。これは、node-supervisorやnodemonを使えば解決できるかもしれません。試していませんが。

AngularJS 1.2.0がリリースされたので、 今までangular-1.0.8を使っていたプロジェクトをアップデートしてみました。

そのときに困ったこと、修正したことをメモしておきます。

まず初めに、読み込むスクリプトを変えたのですが、エラーになりました。

Error: [$injector:modulerr] http://errors.angularjs.org/undefined/$injector/modulerr?...

のようなエラーです。何かが悪いのは分かりますが、エラーの内容が分かりません。ここが、一番はまったところです。実は、minifyしたスクリプトだとこのようなエラーになるようです。angular.min.jsの代わりにangular.jsを読み込むようにしたら、エラーの原因が分かるようになりました。

一つ目のエラーはngRouteが別ファイルに分かれていることでした。これは、angular-route.jsを読み込むようにして、

angular.module('myApp', ['ngRoute']);

のようにすることで解決しました。

二つ目のエラーはng-bind-html-unsafeが使えないことでした。ng-bind-htmlに統合されて賢くなっています。angular-sanitize.jsを読み込んで同様に設定するだけで、サニタイズされるようになりました。ついでに、linkyというフィルターも入ったのでこれまで自作していたフィルターが不要になりました。詳しくは、

http://code.angularjs.org/1.2.0/docs/api/ngSanitize

のドキュメントを読んでみてください。linkyはアンカーのtarget属性も指定可能で、例えば、

<p ng-bind-html="x | linky:'_blank'" />

のように使います。

今回のケースはこの二つだけの修正で移行が完了しました。 angular-1.2.0ではその他変更点も多数あるようなので、これだけでは済まないケースもあるとは思いますが、一例として参考になればと。

今後、1.2.0の新しい機能にも挑戦していきたいと思います。


11/13追記。

もう一点修正が必要なものがありました。

Error: [$parse:isecprv] Referencing private fields in Angular expressions is disallowed!

というエラーがでていました。 オブジェクトの中のフィールドにアクセスできなくなったようですね。private fieldってなんだろう。まさか、単にアンダーバーのprefixが付いているというだけかしら。いずれにしても、オブジェクトごと関数に渡すようにして解決しました。

http://docs.angularjs.org/error/$parse:isecprv

に書いてありました。アンダーバーで始まるか終わるかするとプライベートとみなされるみたいです。

nodeで、というかexpressで、より正確にはconnectで、Railsのアセットパイプラインのようなことをする方法についてです。

意外と知られてないようなのですが、 connect-assets というモジュールがあります。

npm install connect-assets

でインストールできます。 Railsに慣れている人はいいのかもしれませんが、ちょっとexpressっぽくないというかconventionがあって戸惑うかもしれません。 個人的にはわかりにくいと思いました。 connect-cachifyの方がAPIとしては分かりやすい気がします。

さて、簡単な使い方です。expressには慣れているものとします。

var assets = require('connect-assets');
...
app.use(assets());

このオプション無しデフォルトだと、assetsディレクトリにjsファイルやcssファイルを置くことになります。さらに、jadeファイルに

!= css('hoge')
!= js('hoge')

と書いておくと、それぞれ、assets/hoge.cssとassets/hoge.jsが読み込まれることになります。

一応これだけなのですが、concatenationは直接はサポートされていなくて、lessのimport機能やjsの snoketsを使う必要があります。ここがややこしい。

具体的には、assets/hoge.lessに

@import 'foo.css';
@import 'bar.less';

のように書いたり、assets/hoge.jsに

//= require foo.js

のように書いたりするようです。統一感ないですね。

NODE_ENV=productionの時はminifyしてくれます。これは便利。

現状不満があるconnect-assetsですが、v3 branchの開発が進められているようです。クリーンに書き直したり、依存ライブラリを減らす計画のようですので、期待できそうです。

backbone.jsのemulateHTTPというのは、PUTやDELETEをPOSTに変換するものです。なぜそんなことが必要かというと、PUTやDELETEが通らないHTTP Proxyがあるからです。

emulateHTTPでは、X-HTTP-Method-Overrideというヘッダーを使います。正確には、_methodというパラメータも追加されるのですが、それは対応しないものとします。X-HTTP-Method-Overrideはnode.jsのexpressやconnectでサポートされます。

app.use(express.methodOverride());

とすれば有効になります。

さて、こんな機能は当然angular.jsで用意されていて一行くらいの設定で使えるようになるだろうと思っていたら、そんなことはありませんでした。 検索すると、

http://manikanta.com/blog/2013/07/25/decorate-angularjs-http-service-to-convert-put/

がヒットしました。 これでできそうかと思ったのですが、実装が中途半端です。 具体的には、

$http.put({...});

のときは想定どおりに動くのですが、

$http({method: 'PUT', ...});

のような使い方ではうまく動きません。 そこで書き直してみることにします。

var App = angular.module('App');
App.config(['$provide', function($provide) {
  $provide.decorator('$http', ['$delegate', function($delegate) {
    var $http = function(config) {
      var method = config.method.toUpperCase();
      if (method === 'DELETE' || method === 'PUT') {
        config.headers || (config.headers = {});    
        config.headers['X-HTTP-Method-Override'] = method;
        config.method = 'POST';
      }
      return $delegate(config);
    };
    angular.extend($http, $delegate);
    $http.delete = function(url, config) {
      return $http(angular.extend(config || {}, {method: 'DELETE', url: url}));
    };
    $http.put = function(url, data, config) {
      return $http(angular.extend(config || {}, {method: 'PUT', url: url, data: data}));
    };
    return $http;
  }]);
}]);

こんな感じになりました。 もう少しスマートな方法はないかと、思ってしまいますね。 しかし、おかげで$provide.decoratorの使い方を覚えました。

上記コードは動作確認してないので、使用レポートをお待ちします。

初めに断っておきますが、今回の調査は、node v0.11.5 (Linux)で試したもので、他の環境では異なるかもしれません。さらに、今後のバージョンアップによっては全く異なる結果となることも十分ありえます。ご注意を。

ES6にgeneratorsが入るとのことで、JavaScriptでcontinuationが使えないかなぁと思っていた自分としては、興味を持ちました。

ちなみに、generator, coroutine, continuationの順に記述力が上がるそうです。こちらが参考になるかもしれません。

さて、generatorsの説明は他に任せるとして、いきなりコードです。

function* es6_generator() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  return 5;
}

これを複数回実行して時間を計測するコードはこちら。

console.time('es6_generator');
for (i = 0; i < 1000000; i++) {
  iterator = es6_generator();
  obj = {};
  while (!obj.done) {
    obj = iterator.next();
  }
}
console.timeEnd('es6_generator');

実行結果は、

es6_generator: 360ms

でした。うちのマシンは非力なので、最近のマシンならもっと速いかもしれません。

これと同等のコードをgeneratorsを使わずに書くとどうなるでしょうか。おそらく、

function hand_generator1() {
  var iterator = {};
  iterator.next = function() {
    iterator.next = function() {
      iterator.next = function() {
        iterator.next = function() {
          iterator.next = function() {
            return {value: 5, done: true};
          };
          return {value: 4, done: false};
        };
        return {value: 3, done: false};
      };
      return {value: 2, done: false};
    };
    return {value: 1, done: false};
  };
  return iterator;
}

こんな感じになるのでしょう。 これを同じように実行したら、

hand_generator1: 4529ms

となりました。これが等価なコードなのかは問題ですが、 少なくともhand_generator1のように書くよりは、 es6_generatorの方が速いとうことですね。

しかし、この単純な例だとfunctionをイテレーション毎に生成する必要はなくて、もっと効率的に書けます。

function hand_generator2() {
  var array = [1, 2, 3, 4, 5];
  var index = 0;
  var iterator = {
    next: function() {
      return {value: array[index++], done: array.length <= index};
    }
  };
  return iterator;
}

この場合の実行結果は、

hand_generator2: 265ms

となりました。es6_generatorと比べてどちらが読みやすいと思うかは人次第でしょうか。できることが違うので比較の対象にならないですが。

さて、少し話がややこしくなりますが、もともとは末尾再帰をやりたかったのです。末尾再帰の説明は他に任せるとして、factorialをgeneratorsを使って書いてみました。

function fact_tail_loop1(x) {
  var fact_tail_sub = function* (x, r) {
    if (x === 0) {
      return r;
    } else {
      yield fact_tail_sub(x - 1, x * r);
    }
  };
  var c = fact_tail_sub(x, 1);
  var d = {};
  while (!d.done) {
    d = c.next();
    c = d.value;
  }
  return d.value;
}

これを見て、ふーん、とか、おー、とか思わない場合は、この先を読んでもつまらないかもしれません。

実行コードはこちら。

console.time('fact_tail_loop1');
console.log(fact_tail_loop1(3));
console.log(fact_tail_loop1(5));
console.log(fact_tail_loop1(10));
console.log(fact_tail_loop1(100));
console.log(fact_tail_loop1(1000));
console.log(fact_tail_loop1(10000));
console.log(fact_tail_loop1(100000));
console.log(fact_tail_loop1(1000000));
console.timeEnd('fact_tail_loop1');

実行すると次のようになりました。

6
120
3628800
9.332621544394418e+157
Infinity
Infinity
Infinity
Infinity
fact_tail_loop1: 217ms

Infinityになってますが、ちゃんと計算はしているようです。ちなみに普通の再帰やproperでない末尾再帰の場合は、最後の方は計算できずにエラーになります。

さて、generatorsを使わないで書き直してみました。

function fact_tail_loop2(x) {
  var fact_tail_sub = function(x, r) {
    return {
      next: function() {
        if (x === 0) {
          return {
            value: r,
            done: true
          };
        } else {
          return {
            value: fact_tail_sub(x - 1, x * r),
            done: false
          };
        }
      }
    };
  };
  var c = fact_tail_sub(x, 1);
  var d = {};
  while (!d.done) {
    d = c.next();
    c = d.value;
  }
  return d.value;
}

同じように実行すると、

6
120
3628800
9.332621544394418e+157
Infinity
Infinity
Infinity
Infinity
fact_tail_loop2: 796ms

になりました。しかし、このコードはオブジェクトの生成が多すぎます。改良版はこちら。

var LoopCont = function(f) {
  this.f = f;
};

function fact_tail_loop3(x) {
  var fact_tail_sub = function(x, r) {
    if (x === 0) {
      return r;
    } else {
      return new LoopCont(function() {
        return fact_tail_sub(x - 1, x * r);
      });
    }
  };
  var c = fact_tail_sub(x, 1);
  while (c instanceof LoopCont) {
    c = c.f();
  }
  return c;
}

これを実行すると、

6
120
3628800
9.332621544394418e+157
Infinity
Infinity
Infinity
Infinity
fact_tail_loop3: 63ms

となりました。いかがでしょう。

私見ですが、generatorsのパフォーマンスは状況次第といったところでしょうか。v8の最適化が進めば、様々なケースで改善されるかと思われます。

しかし、generatorsを使って末尾再帰やcontinuationを実装できないかなぁ、と期待したのでちょっと残念でした。やっぱり言語レベルでサポートしてほしいですね。

個人的には、yield expressionは構造が分かりにくく感じ、好きになれそうにないので、直接使うことはないかと思います。うまく隠蔽してくれるライブラリがあればいいのかもしれません。

ここまで読んでくれた方は、 https://github.com/dai-shi/continuation.js にも興味を持っていただける可能性がありますので、 よろしければご覧ください。

これまでnode.jsのアプリのデプロイにはHerokuを使っていたのですが、 無料アカウントでは上限があるらしいのと、そもそも一つのサービスに依存するのもよろしくないので、別の候補を探しました。

いくつかあるようなのですが、昔は無料でも今は終わっていたり、 今も無料だけどベータサービスなので期限があったりと、 あまりぱっとしません。ビジネスモデルとしてフリーミアムがちゃんと まわってないと持続性がないでしょう。

一番、有望かなと思ったのがRedHatの OpenShift です。

正式サービスしてからはまだ間もないですが、無料のアカウントがサポートなしという形で残っているので、今後も継続することが期待できます。 ただ、無料でホストできるアプリは2つだけのようで、厳しいですが。

さて、 NodeとAngularを使ってTwitterクローンを15分で作るスクリーンキャスト で作ったTwitterクローンをOpenShiftの上にのせることにしました。

まず、OpenShiftのアカウント登録。簡単でした。次に、sshの公開鍵の登録。これもコピペするだけ、簡単。

続いて、アプリの作成。 domainが空いてないと登録できないので、何度もトライしました。 動的にチェックしてくれればいいのに。このあたり、まだまだな感じですね。

続いて、ソースのアップロード。gitのURLが表示されるので、それを、リモートリポジトリとして登録します。

$ git remote add openshift <URL>

で、PUSHします。

$ git push openshift master

ここまで大きな問題なし。このあと大変でした。

まず、今回のアプリはFacebookと連携するのでその情報を入れないといけないのですが、それはリポジトリにはいれるものではありません。 Herokuでは環境変数を使っていました。 openshiftでも環境変数を使えるようで、

$ vi ~/app-root/data/.bash_profile

で追記すれば環境変数を設定できます。 しかし、このままだとnodeを起動するときにその設定は読み込まれないようです。

openshiftにはpre_startというスクリプトがあって、それを使って、 source .bash_profileするようにしたのですが、うまくいきませんでした。

調べているうちに別の方法を見つけました。nodeでしか使えないようなのですが、

$ vi ~/nodejs/configuration/node.env

で環境変数の設定をすることができます。中身は、.bash_profileでの環境変数の設定と同じです。 これは、うまく行きました。

次の問題は、EACCESエラーです。なぜか、webサーバの起動でエラーになるのです。これは、どんぴしゃの記事がありました。

https://www.openshift.com/forums/openshift/other-threads-didnt-help-503-service-unavailable

OpenShiftではINADDR_ANYでlistenできないようです。 app.jsを次のように変えて対処しました。

app.set('port', process.env.OPENSHIFT_NODEJS_PORT || process.env.PORT || 3000);
app.set('ipaddr', process.env.OPENSHIFT_NODEJS_IP);
....
http.createServer(app).listen(app.get('port'), app.get('ipaddr'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});

これで、アプリが正常に動作しました。

慣れてしまえば、OpenShiftでもHerokuと同様の使用感でnode.jsのアプリをデプロイできそうです。ただ、困りそうなことが一つだけあります。OpenShiftで用意されているのは、node v0.6なのです。現在の安定版がv0.10なので2世代も前です。Herokuではv0.10もv0.8も使えます。開発スピードが速いnode.jsエコシステムでは致命的かもしれません。npmとかパッケージによっては動かないケースがでてきそうです。

今回は試しませんでしたが、node.jsカートリッジとは別に、DIYカートリッジというのがあるらしく、それを使えば好きなバージョンを入れることができるそうです。そもそも、Herokuではできないsshができるのでいろいろ自由度は高そうですね。

もう一つ、Herokuと比べて不便を感じたのは、デプロイの遅さです。pushしてから待つこと数分かかったような気がします。Herokuってすごいんだなぁ、と思った瞬間。

実際に今回デプロイしたアプリはこちらです。

http://twitterclonesample-nodeangularapp.rhcloud.com/

なんにしてもPaaSの選択肢が増えることはよいことです。 興味ある方は試してみると良いでしょう。


9/10追記。 ~/nodejs/configuration/node.envのファイルですが、いつのまにか勝手に戻っていて、プロセスが8月後半から動いていなかったことが判明しました。とりあえず、再度書いておきましたが、また、勝手に上書きされてしまうかもしれません。もっといい方法があるのでしょうか。

昨日、 NodeとAngularを使ってTwitterクローンを15分で作るスクリーンキャスト を書いたのですが、ライブラリの紹介をし損ねたので紹介します。

そもそもの動機は、Twitterクローンを簡単に作りたいと思ったことが発端です。 そこで、なにかいいライブラリはないかと色々調べたら、Railsもどきのフルスタックフレームワークがいくつか見つかりました。

他にもいっぱいあるのですが、気になったのがこのあたりです。

ところが、なんと言うかAngularJSを使おうとするとこれらのライブラリはオーバースペックな感じがするのです。Ruby on Railsのようにデファクトが一つある状態であればいいですが、もどきがいっぱいある状態ではコンベンションも統一感がなさそうです。

Angularを前提とするのであれば、フレームワークではなくもっとシンプルなライブラリがあればいいのではないかと思いました。それでもって、面倒なことはそのライブラリが引き受けてくれるような。

そこで、TwitterやFacebookのようなSNSのサイトを作ることに限定したライブラリを作りました。node.jsではexpress.jsがほぼデファクトなのでexpressのmiddlewareとして作りました。middlewareにすることで、単一のフレームワークに依存することなく、他のアプリにアドオンする形で導入できます。

面倒な、認証や権限管理の機能はライブラリが面倒みてくれます。 現状では、Facebook認証と、標準的な権限管理(ownership based)の機能が用意されているだけですが、プラグインとして他の認証や権限管理の機能を追加できるようになっています。 また、通知(メールアラートなど)の機能も今後追加してきたいと考えています。

このライブラリを使うと、フロントエンドを作るだけですぐサービスできます。Railsのようにデフォルトがないので作らないとなにもできないのですが、Angularに慣れていればコンベンションを意識してコーディングするよりもずっと楽かもしれません。

まずは、 こちら からスクリーンキャストを見てみると雰囲気が分かるかもしれません。 もしかしたら、分からないかもしれません。

ソースコードは、

https://github.com/dai-shi/social-cms-backend

にあります。


8/7追記。

最近知ったのですが、http://sproute.io/というフレームワークもあるようですね。 こちらは比較的SNSに特化したようなフレームワークであるようです。 ほとんどコーディングなしでTwitterクローンが作れるようです。 でも、$99って。

みなさん、AngularJS使ってますか?

Angularを使うと、お手軽にWebアプリ(と言っていいのかな)が作れます。

そのお手軽さをさらに助けるのが、Node.jsとそのライブラリ達です。(いや、Nodeに限った話でもないですが。) 細かい説明は他にお任せしますが、Connect/Expressやその様々なmiddlewareを使うと機能を色々追加できます。 今回、そのmiddlewareの一つとして、SNSのバックエンドライブラリを作りました。 これを使うと、フロントエンドを作るだけで簡単にSNSのサイトができあがります。

試しに、Twitterクローンを作ってみました。今回はフォロー機能などなしの単機能版です。ちなみに、フォローやグループの機能はライブラリには入ってます。

Ruby on Railsのまねをして15分でコーディングするスクリーンキャストを作りました。NodeやAngularの知識を前提としているので、見るだけそれらが理解できるようになるわけではありませんが、どうぞご覧ください。音もテロップもなし、それどころかマウスポインタもなしです。シンプルに。

スクリーンキャストを別ウインドウで開く

コーディングした結果のソースコードはこちらに置いてあります。

https://github.com/dai-shi/twitter-clone-sample/tree/20130804_recorded

実は一文字だけ打ち間違いがあってあとから修正しました。

せっかくなので作ったTwitterクローンを動くようにアップしました。

http://twitterclonesample-nodeangularapp.rhcloud.com/

もしよろしければ試してみてください。

今回は、npm周りのTIPSです。特に新しい知見ではないのですが、メモのために書いておきます。

node.jsのパッケージマネージャであるnpmは依存パッケージの解決をしてくれます。依存パッケージは、package.jsonに書いておきます。

例えば、expressを使う場合、

"dependencies": {
  "express": "*"
}

のように書きます。バージョン番号を指定しておきたい場合もあります。

"dependencies": {
  "express": "3.1.0"
}

のように書きます。他にも不等号やチルダを使ってバージョン番号の範囲を書くこともできます。今回はそのあたりは詳しく書きませんが、package.jsonにはバージョン番号の範囲を書いておくことがおすすめです。node.js関連のパッケージはまだ発展途上で仕様が変わることがよくあるからです。そうでなくても、">=3.0.0"などと書いておけば、"3.0.0"では動いたのだということが分かってよいかもしれません。

さて、本題です。依存パッケージがnpmに登録されていない場合はどうすればよいでしょう? 一つの答えは、登録してしまえばよいのです。が、登録したくない場合もあります。今回そのようなケースがありました。それは、GitHubでforkした場合です。将来的にはPull Requestして取り込んでもらうつもりなら、npmに独自バージョンを登録するのはうまくありません。そこで、npmに登録されていないけれど、依存パッケージとして使いたいときにどうするかという話です。

GitHubを使うとしても方法は2通りあります。一つは、tarballで取得する方法、もう一つは、gitプロトコルで取得する方法です。

前者は、

"dependencies": {
  "jsdom": "https://github.com/dai-shi/jsdom/tarball/3bb5b24c5e"
}

のように書き、後者は、

"dependencies": {
  "jsdom": "git://github.com/dai-shi/jsdom.git#3bb5b24c5e"
}

のように書きます。

どちらがいいのでしょう? よく分かりません。 試しに一回だけ、npm installの時間を計測してみました。 結果、前者が11秒、後者が14秒でした。 gitプロトコルの方がオーバヘッドがあるのかもしれません。 httpプロキシしか使えない場合は、前者に決まりですね。 GitHubがtarballのURLを廃止したら(ZIPのURLはリンクがありますが、tarballってどこまでオフィシャルなのでしょう?)、後者に決まりですね。

今のところはどちらでもよいのかもしれません。


10/20追記。

"https://github.com/dai-shi/jsdom/tarball/3bb5b24c5e"

の代わりに、

"https://github.com/dai-shi/jsdom/archive/3bb5b24c5e.tar.gz"

と書く方法もあるようです。こっちの方がオフィシャルな感じがしますね。

AngularJSがnode.jsと通信するときは、RESTでJSONを返すのが便利です。標準的なやり方にしておくと、コードを書く量が減らせます。

node.jsとexpress.jsでRESTを書くときは、

res.json({foo: 'hoge', bar: 123});

のようにします。

これまで、真偽値を返したい場合は、

res.json(true);

としていました。node.jsはこれで正しいです。しかし、これだとAngular的にうまくありません。

Angularはプリミティブな値が返ってくることは想定していないようで、オブジェクトかオブジェクトの配列の形で返してあげる必要があります。文字列の配列もダメです。

今回の例では、

res.json({result: true});

とすることで解決できました。

実は、この話は半年くらい前にも気づいて調べたことがあったのですが、すっかり忘れていました。今日も全く同じことをして1時間ほどはまりました。忘れないように、ということでメモでした。