0

Node用rocketio-clientを作ったのと、npmの作り方

Sinatra::RocketIOのNode.js用クライアントを作った、という事を書くのを忘れていた。
自作Nodeモジュールをnpmjs.orgへ登録する方法もまとめる。


https://github.com/shokai/node-rocketio-client
https://npmjs.org/package/rocketio-client


感想

npmに初めて登録したけど、rubygems.orgの良い所はそのままにより洗練されてる感じがした。
あとcoffee-script、昔触った時は何これすっげえキモイと思って無理だったんだけど、その頃よりも構文が充実してたし、
Scalaを少し勉強したお陰で目が慣れてて普通に書けるようになってた。

Rubyとscala書ける人はcoffee-scriptすんなり入れると思う。


インストール

npm install rocketio-client

使う


ごく普通にSinatra::RocketIOに接続できる。websocketとcometの使える方が選択される。
RocketIO = require 'rocketio-client'
io = new RocketIO('http://localhost:5000').connect()

io.on 'connect', (io)->
console.log "connect!! (#{io.type})"
io.push 'hello', 'hello world'

io.on 'echo', (data)->
console.log "echo> #{data}"


サーバー側はこんな感じ
main.rb
io = Sinatra::RocketIO

## receive "hello" from client
io.on :hello do |message, client|
puts "> receive '#{message}' from #{client.session} (#{client.type} #{client.address})"

## push "echo" to client
io.push :echo, message
end


自作nodeモジュールをnpmjs.orgに登録する方法

package.jsonを作る

% npm init
対話形式でライブラリの名前やauthor、ライセンス形態を質問されるので答えるとpackage.jsonが生成される

package.jsonを直接編集して、必要あれば項目を追加する。
  • “dependencies” に実行時の依存ライブラリを書く
  • “devDependencies” に開発時に必要なライブラリを書く
  • 実行可能コマンドも配布する場合、 “bin” にpathを書く
  • “main” で指定されたファイルが、require ‘パッケージ名’ された時に読み込まれるファイルになる

もう一度npm initすれば対話形式で再編集できる。


ディレクトリ構成

.
├── History.txt
├── Makefile
├── README.md
├── lib
│   └── rocketio-client.js
├── node_modules

│  (node_modules内は多すぎなので省略)

├── sample
│   └── sample.coffee
├── server
│   ├── Gemfile
│   ├── Gemfile.lock
│   ├── Procfile
│   ├── README.md
│   ├── config.ru
│   ├── lib
│   ├── main.rb
│   ├── npm-debug.log
│   ├── public
│   │   ├── index.js
│   │   └── jquery.min.js
│   └── views
│   └── index.erb
└── src
   └── rocketio-client.coffee


プログラムを書く

他の人のnpmを見ていると、大抵libディレクトリの下にjsを置くようだ。

coffee-scriptを使ってる場合は、lib以外のディレクトリ(src等)を作ってcoffeeはそこに置いて、
都度jsにコンパイルしてlibに書き出す。
Makefileでやった。

サンプルコードを書く

requireはモジュール名やファイル名ではなくディレクトリ名を指定した場合に、そこにpackage.jsonがあれば”main”の項目に指定されたファイルを読み込んでくれる。

なのでsample/sample.coffeeからrocketio-clientをrequireするには
RocketIO = require '../'
と相対パスで書けばいい。


README.mdを書く

markdownでREADME.mdを書くとnpmjs.orgで綺麗に表示してくれる


publish

% npm publish
npmの開発者アカウントを作っていない場合は「adduserしろ」等の指示が表示される。言われたとおりにやる。

% npm adduser
指示に従って入力していけばok

これでもう公開されているので、誰でもインストールしてすぐ使える。


開発中のnpmを公開前にローカルで試す方法

package.jsonのdependenciesにはgitリポジトリを指定できるので、それでインストールできる。

もしくは普通nodeで開発している時はプロジェクトルートにnode_modulesというディレクトリができてその中にnpmがインストールされるので、シンボリックリンクでローカルの開発中のnpmに差し替えるという手もあると思う。

0

ndenvインストールした

Node.js版のrbenvのようなやつをMacにインストールした。
複数バージョンのnode.jsをホームディレクトリにインストールでき、アプリ毎に別バージョンのnode.jsを使える。

node.jsのバージョン管理のためにndenv & node-buildを作ったのとanyenvの宣伝 – As a Futurist…
https://github.com/riywo/ndenv


ndenvインストール

git clone git@github.com:riywo/ndenv.git ~/.ndenv
mkdir .ndenv/plugins
git clone git@github.com:riywo/node-build.git ~/.ndenv/plugins/node-build
インストール機能はnode-buildというプラグインにわかれている。

.bash_profileや.zshrcに設定
export PATH=$PATH:$HOME/.ndenv/bin
eval "$(ndenv init -)"


Node.jsインストール

ndenv install --list
ndenv install v0.11.5
インストール可能なnodeの一覧を見て、v0.11.5を入れた。

インストールしたnodeの一覧
ndenv versions


global/local

ndenv global v0.11.5
v0.11.5を使う

ndenv local v0.10.15
そのディレクトリ内でのみv0.10.15を使うようになる
cdでディレクトリに入るだけでnodeバージョンが切り替わる
ディレクトリに .node-version というファイルが生成されていて、その中でバージョン指定されている
アプリ毎に別バージョンのnodeを使いたい時に有用



エイリアスと実体

which node
~/.ndenv/shims/node が得られる
これはshell scriptで書かれたラッパー
ndenv関連の設定を全て読み込んでから、下記の実体を実行する

ndenv which node
~/.ndenv/versions/v0.11.5/bin/node が得られる
こっちが実体


cdしたのにnodeやnpmのバージョンがおかしい時

ndenv rehash
シンボリックリンクを貼り直す(rehash)


cronやdaemonでアプリに合わせたバージョンのnodeを使いたい時

1. アプリのディレクトリ内で ndenv local し、.node-versionを生成する
2. cronやdaemonで、アプリのディレクトリにcdする
3. /Users/ユーザー名/.ndenv/shims/node から実行する、アプリディレクトリ内の.node-versionが読まれてnodeが実行される。

~/tmp でndenv localしてglobalと別のバージョンを使うようにしておいて、
こういうcrontabを登録してみるとちゃんと切り替わっているか確認できる。
* * * * * cd $HOME && node -e 'console.log(process.version)' > ~/homedir.version
* * * * * cd $HOME/tmp && node -e 'console.log(process.version)' > ~/tmpdir.version


どれぐらいrbenvっぽいかというとこれぐらいそれっぽい

0

mongooseのvirtual attributes

Query投げて返ってきたDocumentsが関数を持っていて欲しい時にVirtual Attributes使う。

  • スキーマ.virtual(‘名前’).get(function(){ /**/ })
  • スキーマ.virtual(‘名前’).set(function(){ /**/ })
でgetter/setterを付けれる。

例えば、MongoDBにはidしか保存しないようにして、permalinkのURL等はidから組み立てる場合、そういう関数はmodelに持たせたい。


実装例

RecipeSchema.virtual(‘url’).get に関数を登録しておくと、 doc.url で呼び出せる。

mongoose_virtual_attr.js
var _ = require('underscore');
var mongoose = require('mongoose');

mongoose.connect('mongodb://localhost/test', function(err){
if(err){
console.error(err);
process.exit(1);
}
});

var Schema = mongoose.Schema, ObjectId = Schema.ObjectId;

var RecipeSchema = new Schema({
id : {type: String, unique: true},
title : {type: String},
created_at : {type: Date, default: Date.now}
});
RecipeSchema.virtual('url').get(function(){
return 'http://shokai.org/'+this.id;
});

var Recipe = mongoose.model('Recipe', RecipeSchema);
Recipe.latests = function(num){
return this.find().sort('created_at', -1).limit(num);
};

var recipe = new Recipe({id: '29jg', title: '肉じゃが'});
recipe.save();

Recipe.latests(3).exec(function(err, docs){
if(err){
throw err;
}
else{
_.each(docs, function(doc){
console.log(doc.url);
});
}
mongoose.disconnect();
});


% npm install mongoose underscore
% node mongoose_virtual_attr.js
http://shokai.org/29jg


というのがやり方わかんないんだよねぇ・・と相談したらmyatsumoto氏が教えてくれた。

0

EventEmitter

参考



普通に使う

var events = require('events');

var foo = new events.EventEmitter();

foo.on('bar', function(data){
console.log(data.message + ' - ' + data.date);
});

foo.emit('bar', {message : 'hello', date : new Date()});
foo.emit('bar', {message : 'helloooooo', date : new Date()});

hello - Tue Jul 24 2012 18:01:48 GMT+0900 (JST)
helloooooo - Tue Jul 24 2012 18:01:48 GMT+0900 (JST)


クラスに組み込む

クラスにevents.EventEmitter.callとutil.inheritsすると、インスタンスにon/emitが追加される

var events = require('events');
var util = require('util');

var User = function(name){
events.EventEmitter.call(this);
this.name = name;
}
util.inherits(User, events.EventEmitter);

var shokai = new User('shokai');
var ahokai = new User('ahokai');

var callback = function(data){
console.log(this.name+' => '+data);
};

shokai.on('hungry', callback);
ahokai.on('hungry', callback);

shokai.emit('hungry', 'kazusuke');
ahokai.emit('hungry', 'zanmai');

shokai => kazusuke
ahokai => zanmai

0

HerokuでMongoDB+Node.js

HerokuでMongoDB+Sinatraと同じ、1行メモ的なものをNode.js+Express+mongooseで作った。


mongooseはmongoidと同じ雰囲気のQueryチェインが使えて便利。


MongoDBに接続、スキーマ定義

アドオンのMongoLabかMongoHQを入れてたらそっちに接続するようにした。
var mongoose = require('mongoose');
mongoose.connect(process.env.MONGOLAB_URI || process.env.MONGOHQ_URL || 'mongodb://localhost/memo', function(err){
if(err){
console.error(err);
process.exit(1);
}
});

var Schema = mongoose.Schema, ObjectId = Schema.ObjectId;

var MemoSchema = new Schema({
body : {type: String},
created_at : {type: Date, default: Date.now}
});

var Memo = mongoose.model('Memo', MemoSchema);

Memo.latests = function(num){
return this.find().sort('created_at', -1).limit(num);
};

Memo.find_by_id = function(id){
return this.find({'_id':id});
};


読み書き

modelのオブジェクト作ってsaveするとMongoDBに保存される。
var mongoose = require('mongoose');
var Memo = mongoose.model('Memo');

var m = new Memo({body: "はい"});
m.save();

代入しても良い。Schema定義した時にgetterが生成されている。
var m = new Memo();
m.body = "はい";
m.save();


Queryを繋げて書けて良い。
var _ = require('underscore');
var mongoose = require('mongoose');
var Memo = mongoose.model('Memo');

Memo.where(body: /はい/).sort('created_at', -1).limit(30).exec(function(err, docs)){
if(err){
console.error('error');
}
else{
_.each(docs, function(i){
console.log(i.body + ' - ' + i.created_at);
});
}
});


さっきschema定義の時にmodelに付けておいたメソッドを使う
Memo.latests(3).exec(function(err, docs){
if(err) console.error(err);
else{
_.each(docs, function(d){
console.log(d.body + ' - ' + d.created_at);
});
}
});

データの出し入れはcontrollerviewのコードが参考になるかも。
async.parallelで2つのクエリを同時に投げて結果待ちしたりしてみた。なんとなく速そう。


jadeとhamlのちがい

にはまった。

この部分

hamlだとこう書くけど
%ul
- memos.each do |memo|
%li
#{memo.body} -
%a(:href => "/#{memo.id}") #{memo.created_at}


jadeだとliと同じ行に書かないと、下のaタグの中身の変数(memo.created_atとか)が展開されなくなる。
ul
- each memo in memos
li #{memo.body} -
a(href="/#{memo.id}") #{memo.created_at}