ラベル Node の投稿を表示しています。 すべての投稿を表示
ラベル Node の投稿を表示しています。 すべての投稿を表示

2012-10-19

依存するモジュールも解決できる Node/AMD (サーバ/クライアント) 共通化モジュールを書く



このエントリは、 東京Node学園祭 2012 アドベントカレンダー 5日目の記事です。


■ 前置き - AMD とは


AMD (Asynchronous Module Definition) は、Javascript のコードをモジュールとして定義して、非同期ないし遅延ロードするための仕組みです。

http://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition (現在、接続が遅い模様)

CommonJS により提唱されたものですが、昨年あたりからクライアントサイド (ブラウザ) で JavaScript モジュールを構築する仕組みとして各所で一気に取り上げられ、現在ではクライアントサイドの主要なライブラリでもサポートされてきている(AMD によるモジュールとして利用できる)状態にあります。

モジュールに依存性を指定する仕組みも用意されています。遅延ロードされますので、依存モジュールの読み込みが完了次第、目的の(依存元の)モジュールの定義処理が実行されます。
比較的カオスになりがちなクライアントサイド Javascript が、このモジュール化によりすっきりしたものになります。

また処理は非同期で行われるため、ブラウザ上での読み込みの速度向上が見込まれます。実際に、Twitter の Web ページの高速化にも一役買ったとの話があります。

Twitter Engineering Blog: http://engineering.twitter.com/2012/05/improving-performance-on-twittercom.html
Publickey による日本語紹介: http://www.publickey1.jp/blog/12/twitter51.html

クライアントサイドで AMD を利用するには、AMD 機能を提供してくれるライブラリを用います。たとえば、 RequireJS があります。

http://requirejs.org/



■ サーバサイド/クライアントサイド Javascript のモジュール共通化


もし仮に Web アプリケーションのサーバ実装を Node.js で行っていて、かつある処理をサーバ/クライアントのどちらでも行いたいとするならば、その処理を行うコードを改変することなくサーバ/クライアントどちらでも実行できればという考えが出てきます。

そこで、その処理のコードを Node モジュールかつ AMD モジュールとして実装しておき、環境にあわせて適切にモジュールとして読み込まれるようにします。


今回は例として、「その年の 1/1 から今日までの日数の回数 hello world を出力する」というつまらない機能を、Node/AMD 共通モジュールとして作ります。

この問題に対して、Lo-Dash (http://lodash.com/) と Moment.js (http://momentjs.com/) の2種類のライブラリを使おうと考えました。
Lo-Dash, moment ともに Node モジュールとして npm で導入できると共に、AMD モジュールとしても利用できるもので提供されています。

実際に実装したコードが次のものです。

(function(define) {
    define('hellodays', ['lodash', 'moment'], function (_, moment) {
        function hellodays() {
            var days = Number( moment().utc().format('DDD') );
            _.times(days, function (d) {
                console.log('Hello, world ! (' + (d + 1) + ' days)');
            });
        }

        return hellodays;
    });
})(
   // 1: AMD
   typeof define === 'function'  ? define :

   // 2: Node
   typeof module !== 'undefined' ? function(id, deps, factory) {
       module.exports = factory.apply(this, deps.map(require));
   } :

   // 3: Plain (window object)
   function(id, deps, factory) {
       var dependencies = [ this._, this.moment ];
       this[id] = factory.apply(this, dependencies);
   });


2行目で実行している define は、AMD API の仕様に基づき Javascript モジュールを定義するための関数です。引数として AMD 内モジュール名 (以後、id と呼びます)、依存モジュールのリスト (以後、deps と呼びます)、およびモジュールの構築を行う関数 (以後、factory と呼びます) を指定します。
モジュール構築関数 factory 内でオブジェクトあるいは関数を返すと、それがモジュールとして利用できるようになります。ここでは、実際に 1/1 から現在日までの日数を算出し、その回数分 hello, world を出力する処理を記述しています。

実際に Node/AMD モジュールのためのコントロールを行っているのは、1行目から定義している即時関数の引数として与えている、13 行目からのコードです。若干分かりやすくなるよう、コメントを明記しています。

もしグローバル空間に define 関数が定義されているのであれば、AMD によるモジュール機構を備えているため、そのまま define を即時関数に与えて実行させるようにします。これで、AMD モジュールとして hellodays が利用できるようになります。

AMD define 関数はないものの、module が定義されているのであれば Node のモジュール機構と見なし、define のような振る舞いをする関数を与えます。
define への引数はモジュール名 id、依存するモジュール名のリスト deps と、モジュール構築のための関数 factory が与えられるわけなので、Node の require を使って deps で指定されている依存モジュールを読み込み、得られたインスタンスを factory の引数として与えます。factory を実行すると構築された関数が返されるので、それを module.exports にセットします。これで、Node でこの hellodays モジュールを require すると、factory で作られた関数が得られます。

最後は、AMD でも Node でもないパターンです。これまでのブラウザ用 Javascript ライブラリであれば、window オブジェクトへ割り当てるくらいはしていることと思います。lodash, moment もそれぞれ _, moment という名称で参照可能になっていることでしょうから、それらを factory の引数に与えてモジュール構築を行います。名称として id が define の引数で渡ってくるので、その値で window オブジェクトの要素の1つとして定義しています。


以上、AMD > Node > plain という優先度でモジュール定義を行うことができるようになりました。
状況にあわせて依存モジュールの実体であるインスタンスが適切に factory に渡っている、factory の結果が利用されている、あたりがポイントになります。




■ 共通モジュールを利用する


サーバサイドである Node ならば次のように利用します。
普通の Node モジュールの利用となんら違いはありません。(require で読み込めるようモジュールパスが解決されている場合)
var hellodays = require('hellodays');
hellodays();

一方クライアントサイドの場合は、AMD が利用できるよう RequireJS を使います。
上記で作成したモジュールが読み込まれ、hellodays 変数として参照可能になってやってきます。
<script src="./scripts/require.js"></script>
<script>
  requirejs.config({
      paths: {
          "lodash": "./scripts/lodash.min",
          "moment": "./scripts/moment.min",

          "hellodays": "./scripts/hellodays"
      }
  });
  requirejs(['hellodays'], function (hellodays) {
      hellodays();
  });
</script>

Jam (http://jamjs.org/) などのクライアントサイドパッケージマネージャを使うと、依存先となるモジュールの導入や、AMD (RequireJS) で読み込むパスの管理などを一手に引き受けてくれるので、もう少し楽に利用できるようになります。
$ jam install lodash
$ jam install moment
<script src="./jam/require.js"></script>
<script>
  requirejs.config({
      paths: {
          "hellodays": "./scripts/hellodays"
      }
  });
  requirejs(['hellodays'], function (hellodays) {
      hellodays();
  });
</script>

最後に、クライアントサイドで AMD を利用しない場合です。必要なファイルをすべて読み込んだ後、直接 hellodays 変数の関数を実行します。利用の機会はほとんどないと思いますが、利用すること自体は問題はありません。
<script src="./scripts/lodash.min.js"></script>
<script src="./scripts/moment.min.js"></script>
<script src="./scripts/hellodays.js"></script>
<script>
  hellodays();
</script>



■ 共通モジュールとして用意する対象について


今回依存モジュールとして利用した Lo-Dash や Moment.js のような、サーバ/クライアント関係なく汎用的に利用できるライブラリなどは、共通するモジュールとして利用できると大きな価値がありそうです。他にもフローコントロールやイベント管理などは同じように共通モジュール化の価値があると考えられます。

一方、自前で提供するサービスのためのコードではと考えると、データの正規化 / 妥当性確認 (Filter / Validator) ルールの共通モジュール化に一定の価値がありそうです。
例えばユーザ情報の更新処理において、あるルールに基づいた妥当性確認をサーバサイドだけでなくクライアントサイドでも行って確認結果をいち早く掲示したい、といった要件がある場合。これまでなら同一ルールながらも異なるコードで実装したり、妥当性確認処理を非同期で逐一サーバと通信して行ったりしていたところです。
共通モジュール化されているならば、妥当性確認処理をクライアントで行い、サーバへ POST されてきた情報も同じモジュールで再度確認する、といったことができます。




■ 備考. テストについて


共通モジュールとして実装されたコードのテストをどうするか?

現在の私の場合はですが、まずは使い慣れた環境による、提供されうる機能のテストを書きます。
すなわち、Node モジュールとして提供されて、Node 上でなんら問題なく利用できるである確信のためのテストを用意します。
その上で、AMD モジュールとして読み込んで利用可能な状態になるかのテストを用意しています。

Node モジュールの amdefine (https://github.com/jrburke/amdefine) を使うことで、Node 上で define を使ったコードが一時的に利用可能になります。
これを用いてモジュールをロードし、読み込んだモジュールが不自由なく(そこまでの機能テストは省略しつつ。機能の妥当性は前述のテストコードで満たされているはずなので。)利用できれば、良しとしています。




■ 備考2. Node の RequireJS モジュールと AMD 採用の是非


実は Node 上でも RequireJS モジュールが用意され、利用することができます。

http://requirejs.org/docs/node.html

非同期にモジュールを読み込む AMD のための RequireJS ですが、Node の場合は立ち上がったサーバインスタンスが基本的に永続した上でモジュールインスタンスの永続化がコードの書き方次第で容易だったり、Node 本体の require 時におけるインスタンスの再利用性が悪くなかったりといった理由から、その Node モジュールが RequireJS に依存する、というのは必要ないのではないかと個人的には思っています。


2012-10-13

ランダム文字列生成 gachar


https://github.com/kumatch/gachar

ただのランダム文字列生成です。
最近の個人的なブームに沿って、一応フロントエンドでも使えるようにしときました。使うかどうかは別にして。

var gachar = require('gachar');

var string1 = gachar.run(128);

gacha.run(256, function (string2) {
  ....
});


実は最初 crypt なにがし系を使って適当な文字列を作る処理で書いてたんですが、C モジュールを持ってしてもただの Math.random() に速度で勝てなかったという。やっぱ暗号化処理って重いんですねー(当たり前)。

ランダム性能的にはそこまで良くなさそうですが、使い捨てなトークン値などではこれで十分かなと思ってます。
あと、名前は結構気に入ってます。

2012-10-03

runie.js

https://github.com/kumatch/runie

Node で用意された任意変数を、フロント javascript で参照、利用するためのライブラリです。
変数を一旦 DOM 要素として埋め込んで(描写して)、フロントスクリプトで改めて読み込みます。

Jade template
div
  p= title

!{ runie.tag('items', items) }

Front javascript
runie.read('items', function (items) {
    if (items.length) {
     ....
    }
});

これ以前はフロントスクリプト上から ajax で JSON を受け取って読み込んでいたりしてたので遅かったんですが、これを使うようになってから処理が圧倒的に早くなりました(当たり前)。

2012-09-19

フロントエンドパッケージマネージャ bower のメモ


Twitter が先日リリースした、Javascript や CSSなどのフロントエンド向けパッケージマネージャ Bower (http://twitter.github.com/bower/) についての簡単な下調べを行ったときのメモ。


■ Git リポジトリを指定しての導入


bower install コマンドで指定できるのは名前だけじゃなくて、git リポジトリの URL を指定することができる。

  $ bower install git://github.com/kumatch/asyncall.git

末尾にシャープ付きでバージョンを指定すると、ちゃんと git タグのバージョンが使われる(シェルによっては # をバックスラッシュでエスケープすること)。みんなでちゃんとバージョン付けをしよう。

  $ bower install git://github.com/kumatch/asyncall.git#0.1.1


これら方法で導入したパッケージも、bower update で最新のものに更新されることを確認した。



■ component.jsonを使ってのパッケージ管理


component.json ファイルを自身のプロジェクトリポジトリに置いて、そのプロジェクト内で利用しているパッケージ群を定義することができる。

{
  "dependencies": {
    "jquery": "~1.7.2",
    "asyncall": "git://github.com/kumatch/asyncall.git"
  }
}

component.json のあるディレクトリ(あるいは bower 側で辿ることができる場所。マニュアル参照。)で bower install を実行すれば、定義しているパッケージ群を一気に導入できる。


先ほどのように、git リポジトリ URL でのバージョン指定も OK。
注意点としては、dependencies に定義するキーの値は、導入パッケージ側の "name" 要素で指定されている名前とあわせること。そうでないと、導入後の list コマンドでおかしなことになった。



■ bower で導入されることを想定しているコンポーネントの依存性定義


例として、次のような component.json を含んだ git リポジトリがあるとする。
(ここでは git://github.com/kumatch/bower-samples-nop.git とする)

{
  "name": "sampels-nop",
  "version": "0.1.0",
  "main": "./lib/index.js",
  "dependencies": {
    "asyncall": "git://github.com/kumatch/asyncall.git"
  }
}


一方、自身のプロジェクト側の component.json で、このパッケージを利用するよう定義。

{
  "dependencies": {
    "sampels-nop": "git://github.com/kumatch/bower-samples-nop.git"
  }
}


bower install を実行すると、samples-nop パッケージと一緒に、依存している asyncall が導入される。

$ bower list
/path/to/bower-test
├── asyncall#0.1.2
└─┬ sampels-nop#0.1.0
  └── asyncall#0.1.2


$ bower list --paths
{
  "asyncall": "components/asyncall/asyncall-0.1.2.min.js",
  "sampels-nop": "components/sampels-nop/lib/index.js"
}


bower-samples-nop.git リポジトリの component.json を package.json としていても、問題なく(依存性を解決して)導入することができた。従って、npm パッケージとして用意されていて、かつブラウザでも利用可能なもの (Underscore.js とか) は普通に依存パッケージとして指定することができて、導入時にも問題なく解決される。



■ 導入パッケージのエントリポイントをスクリプトで得る


次のようなコードで paths にオブジェクトで受け取ることができた。まだ何も試していないけども、色々と自動化したいならば必要になってくると思う。

var bower = require('bower');

bower.commands.list({ paths: true }).on('data', function (paths) {
    // console.log(paths);
    // …
});

2011-12-09

Node Ninja に挑戦 2


前回初めて利用してみた Node Ninja に挑戦の第2弾でござる。

今回はオリジナルの Node プログラムを Git リポジトリに準備してそれをデプロイすると共に、リポジトリ更新によるサーバ自動更新までの動きを確認してみる。

Node Ninja の特徴の1つとして WebSocket に対応している……とは公式サイトには全く書いていないが、Twitter で @node_ninja のニンジャがそう言っているので問題はないだろう。そこで今回は Socket.IO を使って、WebSocket を利用するシンプルなサーバプログラムを準備してデプロイすることにする。


Socket.IO サンプルプログラム

今回の挑戦のために、上記リポジトリのようなつまらないサーバプログラムを準備した。
Socket.IO を使って断続的に通信が発生するため、そのままサーバを更新した際にどの程度ダウンするのかを確認することができる。


Node Ninja Machine へデプロイされるプログラムのルール

Machine 上で動作する Node サーバプログラムとして、現在のところいくつかのルールが設けられている。
  • (おそらくトップレベルに) server.js というファイル名でエントリポイントを準備する
  • config.json というファイル名で実行する Node バージョンを指定できる
  • package.json が機能するので、デプロイ時にモジュールを指定してインストールできる

個人的には、package.json が機能するのだから、それに従って (モジュールとして) サーバが動作するようにできるのではないかと思う。


Git リポジトリを指定してのデプロイ

さて、実際の Machine へのデプロイは以下の簡単なステップで完了する。
以下の例は、前述の Github 上のリポジトリを対象に行ったもの。Git リポジトリ URLは git://github.com/kumatch/sample-socketiopulse.git (読み込み専用) になる。
  1. Git Setup の Private タブで Add を押して、Git リポジトリURLを登録する
  2. 登録したアプリケーションが Private アプリケーション一覧に表示されるので、Install を押す
  3. Machine にデプロイが開始されて稼働開始

その上で、Git の Post-Receive Hooks を利用して、リポジトリが更新された (push された) 際にサーバを自動的を更新する仕組みが備わっている。
  1. Git Setup の AutoSync タブで、Git リポジトリの URL を入力して Create
  2. Post-Receive 用の専用 URL が発行される
  3. (例えば Github 上で) Post-Receive Hooks の設定として、先ほど発行された URL を指定する

これでデプロイ元となるリポジトリへ push すれば、Node サーバアプリケーションは自動更新される。
Git リポジトリ側の Post-Receive Hook がどのタイミングで行われるかにもよるが、push 直後に再デプロイが始まり、ほんのわずかなダウンタイムの後に新しいプログラムで稼働したことを確認できた。

デプロイした Machine の URL はこちら。

アクセス後、Chrome/Safari の開発ツール、ないし Firefox の Firebug のコンソール上で断続的に通信されているのが確認できる。IE は知らないが F12 を押そう。


どうやら現在のところ立ち上げることができる Machine は1つのアカウントで1つまでのようで、上記 URL を機能させ続けるならば、もう別の Node アプリケーションをデプロイすることができない。恐らく、後ほど上記 URL の Machine は停止させて頂くことになるだろう。



2011-11-30

Node Ninja に挑戦

この前の Node 勉強会で Node PaaS である Node Ninja (https://node-ninja.com/) のアカウントを頂いたにも関わらずさっぱり利用していなかったものの、それはさすがに勿体ないし、可能であればレポートを書いてねとも言われていたので、このたび挑戦してみることに。

実は PaaS 自体はこの Node Ninja が初体験。
稼働までの準備等は Web ブラウザによる設定コンソールにて行っていく。


Machine の作成

アプリケーションを稼働させるために、最初は Machine を準備する。正式サービスが展開されればおそらく性能を選択するようなことになるだろうけども、現在は固定になっている。今は指定項目としては Machine につける名前のみ。ここでは「kumasrv」とつけた。これで Machine の準備は完了。


アプリケーションのデプロイ

この作成した Machine に Node アプリケーションを設定する。現在のところ以下の4つの方法がある。

1. オフィシャルに提供されるアプリケーションから選択してインストールする。
現在はサンプルアプリケーションが準備されているが、今後は Node の有益なアプリケーションがボタン1つでデプロイできて Ready になるのだろうと予想できる。

2. 利用者による Shared アプリケーションから選択してインストールする。
オフィシャルではないものの、他のユーザによって提供されるアプリケーションパッケージの位置づけ。

3. プライベートアプリケーション。選択ではなく、自身で Git リポジトリを指定してでデプロイする。また、ここで追加したアプリケーションを Shared 化することができる(最初はちょっとわからなかった)

4. AutoSync。Git リポジトリから展開した上で、そのリポジトリが更新されると自動的に git pull してくれる。逆にいえば、これまでの3つの方法ではデプロイ後は基本的に「そのまま」となる。ただし、先に設定した SSH key で Machine に接続して、コンソール上でコードを変更したりサーバの起動をしなおしたりといったことは可能。


オフィシャルアプリケーションをデプロイ

今回は、オフィシャルなアプリケーションを展開してみる。hello-http という、おそらく hello world なアプリケーションがあったので、それを選択。Logs 画面に遷移した後、Machine が準備される様子(ログ)が追加されていく!

完了後、kumasrv.node-ninja.com へブラウザで繋いでみる。あれ、繋がらない。どういうことなの、ってことで Machine に SSH で接続する。プロセスを確認すると、Nodeプロセスは存在するものの httpd のようなものは存在していない。
そういうものなのかなーニンともカンともとか言いつつ、実行プログラムの中身を覗いてコードを確認。8080 番で動いていることがわかったので、 kumasrv.node-ninja.com:8080 で接続。無事「Hello World」が表示される。

その後アプリケーション選択ページを見てみると、説明文に「ポート 8080 でHTTPのリクエストを待ち受け」ってちゃんと書いていた。てめえがニンともカンともだった。


別のオフィシャルアプリケーション (express + giraffi) も試す。こちらは普通に80でアクセスすることができた。こちらもコンソールからソースコードを覗くと、たしかにそのまま 80 番で待ち受けるよう記述されていた。ユーザにはそういう権限がある模様。

Machine Logs ページ上ではこのアプリケーションのデモとして、アプリログの様子が記録されているのが分かる。どうやらこれがカラクリの中身らしい。



その他雑多なところ

  • Machine 上には mongodb が動いている。情報はないがデモでは普通に使っているし CLI でも接続可能。
  • 作成した Machine を削除して再度別名で作成した際、同じ IP が割当てられたにも関わらず名前割当ては古いままだった。切り替わるのに結構な時間を要したのでこの辺はまだまだ調整が必要そう。


今度は自前アプリケーションのデプロイに挑戦する予定。

2011-10-03

node.js のモジュール配置について 2011 秋

今のところこれでいこうという形ができたので、現時点のものを一旦まとめるというお話。


モジュールの読み込み優先度

http://nodejs.jp/nodejs.org_ja/docs/v0.4/api/modules.html#loading_from_node_modules_Folders

この項に書いている話ですべてではあるものの改めて書くと、
ある js ファイル上で require() を使ってモジュールを読み込もうとする際、指定した path が「/」「./」「../」といった指定の仕方ではないならば、その js ファイルからルートまでのすべてのパス上に存在する *node_modules* というディレクトリ内がモジュール読み込み対象となる。もちろん、深いレベル (読み込み元 js ファイルに近い) ほど優先度が高い。

/path/to/app/app.jp で require() したならば、次に示す順のディレクトリ内モジュールが検索される。

  1. /path/to/app/node_modules
  2. /path/to/node_modules
  3. /path/node_modules
  4. /node_modules

もちろん、require() で指定する path が階層構造でも問題なし。上の例で、require('my/app/foo_module.js') という指定をしたならば、モジュールとしてのファイルは以下のように検索される。

  1. /path/to/app/node_modules/my/app/foo_module.js
  2. /path/to/node_modules/my/app/foo_module.js
  3. /path/node_modules/my/app/foo_module.js
  4. /node_modules/my/app/foo_module.js

基本的にはこれさえ分かっていれば、容易に共有/固有のモジュール領域が作れる。


ソフトウェアごとのモジュール配置

私は現在のところ、Nodeで開発しているソフトウェア (ex: foo_project) のモジュール配置は次のように行っている。

- foo_project
    - app
        - app.js
        - node_modules
            - (ソフトウェア固有ドメインのモジュール)
    - node_modules
        - (npm 等で導入される共有モジュール)
- (node_modules)

foo_project として実装されたソースは app ディレクトリ以下に配置、実行するようにし、直下にそのソフトウェア固有ドメイン向けのモジュール領域を作成する。開発したドメインモデル等はすべてこの app/node_modules 以下に設置する。
一方、npm 等で配布されているパッケージや会社資産などのライブラリモジュール (これも package として社内配布するとよい) は、先ほどの固有ドメイン向けの領域より一段上に配置する。本質的には読み込み優先順位を下げる必要はないが、ツリー自体の可読性と固有ドメインとの分別説明のために明確な分離を行う。
なおこれ以上分離が必要な場合は、さらにその上位にモジュールディレクトリを準備する。分離したい数だけ、上位ディレクトリに node_modules ディレクトリを作成すればよい。制限はただ一つ、ルートディレクトリにたどり着くまで。



npm による任意モジュールディレクトへのパッケージ導入

目的の node_modules ディレクトリへ移動した後に npm install を行えば、そのディレクトリへパッケージを導入できる。
npm は現在のところ対象となる node_modules 内パッケージディレクトリの内容のみで管理しているように思われるため、目的のディレクトリ内におけるパッケージの追加や削除も容易に行うことができる。


「require.paths」の削除

以前は require.paths という組み込み配列に任意のパスを push することで、モジュールの読み込み対象ディレクトリを増やすことができた。しかし、現在ベータバージョンである v0.5 系では削除されている。元々 v0.4 系リリース時点のドキュメントで「いつか消すから止めとけ」という明記がされていて、いつ完全削除されるものかと思っていたものの、案外早く対処されたという印象。

コード上から容赦なくパスを操作できてしまうのはトラブルの元だし不安定な挙動を引き起こす可能性が高いということで、なくなって正解だったと思われる。何より、現在の仕組みはシンプルながらも強力で融通も利く。