「npm」と一致するもの

最近、Meteorの勉強中です。何かパッケージを作ってみたいと思ったのですが、多くのものはすでに誰かがやっています。 MeteorのパッケージはAtmosphereで探します。

今回、自分が使おうと思ったものにぴったりのものが見つからなかったので、作ってみました。 探したものはFacebookのAPIを呼び出すライブラリです。OAuthのライブラリはMeteorで提供されているのですが、Graph APIを使うものは3rt partyのものを探しことになります。

見つけたのはこれらです。

  • biasport:facebook-sdk mrt時代からあるパッケージでインストール数も多いです。ドキュメントを見るとクライアントライブラリのようです。
  • stevezhu:fbgraph fbgraphをラップしたパッケージです。サーバサイドのみで動きます。
  • mrt:facebook-sdk これがオリジナルっぽいですね。
  • jdrorrer:facebook-sdk
  • dcsan:facebook-sdk
  • dferber:graph-api
  • maxkferg:facebook-collections 少し趣向が変わった感じでおもしろそうです。クライアントから使うことがメイン。
  • borges:facebook-sdk
  • timbroddin:facebook-node-sdk ドキュメントがないのでどういうものか分からず。

いっぱいあるように見えますが、facebook-node-sdkはGitHubを見るとforkしているのが分かります。

https://github.com/jdrorrer/facebook-sdk/ ← https://github.com/dcsan/facebook-sdk ← https://github.com/biasport/facebook-sdk ← https://github.com/hugesuccess/facebook-sdk

ネットワーク図を見てもいいかもしれません。全部は表示されていないようですが。

さて、今回はサーバサイドのライブラリを探していたのでfbgraphとgraph-apiが候補なのですが、過去の検討で使っていたライブラリを使いたいという要求もありました。 そこで、そのライブラリをMeteorパッケージにラップしました。

作ったパッケージは、 https://atmospherejs.com/daishi/facebook-server-api です。

ソースコードは、GitHubにあります。 中身はなんと2行だけです。

一応、新規にFB.mapiというAPIを用意しました。Meteor流のfibersを使った同期的APIです。

fibersはあまり好まないのですが、Meteorでやっていくには仕方ないかと諦め気味です。

あまりにも特殊な話なので書くか迷ったのですが、記録のために書いておきます。

Ghostはnode.jsベースのブログシステムです。 先日、Ghostをherokuにデプロイする話を書きましたが、 使ってみて一つだけ不便に感じることがありました。

エディタでC-hが効かないのです。

C-hというのは普段Backspaceの代わりに使っているキーバインドで、 Ctrlキーを押しながらhを押します。ないと困る人には困るのです。 ブラウザでも使えるようにしています。しかし、これが困るのはLinuxユーザだけかもしれません。 OSXの場合はCtrlではなくてCommandがありますし、Windowsの場合はそもそもC-hが効かないと思います。

GhostのMarkdownエディターは気に入ったので、なんとかしてC-hを使えるようにしたいところ。 ちなみに、C-hが使えない理由はC-hがGhostのショートカットに割り振られていて、 ヘッダータグの挿入になるからです。 ちょっと調べてみましたが、本家Ghostではすぐには解決しそうにない(あまりに特殊環境か)ので、 forkすることにしました。

https://github.com/dai-shi/Ghost

です。修正したコードはほんの2行です。 C-hとC-bに設定されていたショートカットをC-S-hとC-S-bにしました。つまり、Shiftキーも同時に押した時だけショートカットが有効になるようにしました。

さて、これをghost-on-herokuでも使いたいのですが、一筋縄では行きませんでした。 というのも、gitリポジトリからcloneしてデプロイするには、gruntのタスクを走らせて ビルドする必要があるからです。手動でやるのは簡単ですが、これをnpmでやる必要がありました。

結果的には、ghost-on-herokuのpackage.jsonを次のように修正することで解決できました。

diff --git a/package.json b/package.json
index 3af466a..aec84be 100644
--- a/package.json
+++ b/package.json
@@ -10,14 +10,37 @@
   "private": true,
   "version": "0.6.4",
   "dependencies": {
-    "ghost": "0.6.4",
+    "ghost": "https://github.com/dai-shi/Ghost/archive/0.6.4-for-emacs-keybinding.tar.gz",
     "ghost-s3-storage": "~0.2.1",
     "pg": "latest"
   },
+  "devDependencies": {
+    "bower": "1.4.1",
+    "csscomb": "3.0.4",
+    "grunt": "0.4.5",
+    "grunt-bg-shell": "2.3.1",
+    "grunt-cli": "0.1.13",
+    "grunt-contrib-clean": "0.6.0",
+    "grunt-contrib-compress": "0.13.0",
+    "grunt-contrib-copy": "0.8.0",
+    "grunt-contrib-jshint": "0.11.2",
+    "grunt-contrib-uglify": "0.9.1",
+    "grunt-contrib-watch": "0.6.1",
+    "grunt-docker": "0.0.10",
+    "grunt-express-server": "0.5.1",
+    "grunt-jscs": "1.8.0",
+    "grunt-mocha-cli": "1.13.0",
+    "grunt-mocha-istanbul": "2.4.0",
+    "grunt-shell": "1.1.2",
+    "grunt-update-submodules": "0.4.1",
+    "matchdep": "0.3.0",
+    "top-gh-contribs": "2.0.2"
+  },
   "engines": {
     "node": "~0.10.0"
   },
   "scripts": {
+    "postinstall": "(cd node_modules/ghost/node_modules && ln -s -b ../../grunt-* .) && (cd node_modules/ghost/node_modules/.bin && ln -s -b ../../../.bin/bower) && (cd node_modules/ghost && grunt --force init && grunt prod)",
     "start": "node server.js"
   }
 }

devDependenciesはもう少し削れるかもしれません。 この修正に加えて、heroku configの設定も行いました。

$ heroku config:set NPM_CONFIG_PRODUCTION=false NODE_MODULES_CACHE=false

これで期待通りになりました。

今さらながらbrowserifyを使ってみました。 存在は知っていたものの普段はcdnを使ってライブラリを読み込むようにしていたので、 必要性を感じていませんでした。 今回、ライブラリをローカルに保存したかったので使ってみました。 bowerと比較してnpmだけで済むのがいいです。

しかし、npm上のライブラリが対応しているかどうか分からないというのは難点ですね。 実際に使おうとしていたライブラリがうまくラップしていないものがありました。 そこで、browserify-shimを使いました。 これは、CommonJSでexportしていないライブラリをexportしたかのように使えるようにするものです。 関連して、napaを思い出しました。 うまくやればnapaとbrowserify-shimの組み合わせは最強ではないでしょうか。

browserify-shimの使い方はあまり検索しても見つからなかったので、メモしておきます。 基本的にはREADMEに書いてあるのがそのままなのですが、一瞬理解しにくかったです。

以下、package.jsonの中身の書き方です。まず、

{ 
  "browserify": {
    "transform": [ "browserify-shim" ]
  }
}

これが必須です。これにより、browserify-shimが認識されるようです。 transformは他にも用途があるのでしょうか。それも興味あります。 次に、

{
  "browserify-shim": {
    "./node_modules/foo/bar.js": { "exports": "Bar" }
  }
}

これはどのファイルのどのようにラップするのかを記述します。 詳細は調べていませんが、これはbar.jsのファイルの最後に

exports = Bar;

を追加したような感じです。もちろん、function(){}でスコープを切っているでしょうが。 さて、一番悩んだのは、どうやってこれを利用するかでした。 状況としては、./public/javascripts/hoge.jsというファイルでどのように使うかというものです。 これは、

var bar = require('../../node_modules/foo/bar.js');

としたら動きました。パスが違うのが曲者です。

改めて、これはnapaと組み合わせると便利かもしれません。 最近自分でよく使っているcdnとhttp://rawgit.com/の組み合わせに匹敵するかも。


5/13追記。

よりよい設定方法が分かりました。というか単にbrowserify自体の設定方法をよく理解していなかっただけでした。 package.jsonを、

{ 
  "browserify": {
    "transform": [ "browserify-shim" ]
  },
  "browser": {
    "bar": "./node_modules/foo/bar.js"
  },
  "browserify-shim": {
    "bar": { "exports": "Bar" }
  }
}

のように書けば、

var bar = require('bar');

として利用することができました。 分かってしまえば簡単なのですが、browserify-shimのドキュメントだけでは理解できなかったです。

Angularのdependency injectionはminifyに弱いです。 angular.jsのチュートリアルにも書かれています。

https://code.angularjs.org/1.3.8/docs/tutorial/step_05

A Note on Minificationのセクションに、 annotationをつけると書いてあります。

しばらく悩まずこの書式で書いていましたが、これはとても気をつかいます。スペルミスや並び替えをするときなど。なによりも、同じ文字を2回タイプするのは効率が悪いです。

ngminというのは聞いたことがあったのですが、100%変換できるわけではないという噂を聞いて使っていませんでした。今ではng-annotateというツールがあります。

https://github.com/olov/ng-annotate

ngminとの違いも説明されています。うまく行かない場合のためにコメントで明示的に指示できるというのが安心感があります。

これで快適になりました。やはり、自動化できることを人がやるのは無駄ですね。

ng-annotateはminifyをしてくれないので、minifyツールと合わせて使うことになります。今回は、

https://github.com/mishoo/UglifyJS2

を使うことにしました。下記にpackage.jsonを載せます。

{
  "scripts": {
    "minify:main": "ng-annotate -a public/main.js | uglifyjs -c -m > public/main.min.js"
  },
  "devDependencies": {
    "ng-annotate": "*",
    "uglify-js": "*"
  }
}

これで、

npm run minify:main

で動きます。 grunt等を使いたい人は、対応しているプラグインを使えばできるでしょう。

ちょっとチャレンジングなタイトルにしてみました。

nodeを使った開発では、タスクランナーというものがあるらしいです。 ユーザ数が一番多いのはgruntなんですかね。 というのも、実はこれまで使ったことがないのです。 使わなければいけないシーンがなかったです。 タスクランナーの印象は、何というか、簡単なことをするために、大変なことをしている感じです。

似たようなことを考えている人はいるんだろう、と思って、 検索してみました。

http://blog.keithcirkel.co.uk/how-to-use-npm-as-a-build-tool/

最近の記事です。npmで全部できる、と。 windowsでの対処法も書かれていて参考になります。 パッケージによっては、windowsでも**.jsと書けるそうです。

http://substack.net/task_automation_with_npm_run

npmのscriptsフィールドを使おう、と。 ./node_modules/.binにパスが通っているので 依存パッケージとしてインストールすれば簡単に使えます。

https://mattandre.ws/2014/08/task-runner-npm-scripts/

これもだいたい同じです。windowsのことは考えられていませんが。

http://anders.janmyr.com/2014/03/running-scripts-with-npm.html

これにはコンプリーションの話が載っていました。

自分だけのプロジェクトの場合は、windowsのことは気にせずnpmだけですんでしまいますね。一番上の記事をちゃんと読むと、windows対応も意識すればそこそこできそうです。

gruntのエコシステムもすばらしいと思うのですが、個人的には依存パッケージが増えるのを好みません。9割程度のタスクはnpmでも十分にできそうです。というわけで、これからはpackage.jsonのサンプルなども記事にしてみようかと思いました。

LiveReloadをご存知でしょうか。Web開発をするときに、ブラウザのリロードを省く機能を提供するものです。 類似のものとしては、Live.jsというのもあります。 また、Meteorでも実装されています。

一言で言うと、websocketでコネクションを維持して、サーバ側のファイル変更を通知して自動でリロードするものです。 CSSの場合はリロードせずに変更を適用することもできます。 livereloadは商用のようですが、そのコアのソースコードはMITライセンスで公開されています。

livereloadはブラウザ拡張を使うことがメインのようですが、javascript版のクライアントもあります。 javascript版だと、mobile safariでも動きます。 livereloadのnode.js用のサーバは、https://github.com/livereload/livereload-serverで公開されていますが、 これはそのまますぐには使うことができません。 平たく言うとプロトコルが実装されているだけで、実際に更新通知する機能は含まれていません。

そこで、簡単に使えるライブラリとしてまとめてみました。特徴をまとめると、

  • livereloadのサーバコードの最新版を使用(現在npmに登録されているlivereload系のモジュールはほとんど旧バージョン)
  • livereloadのクライアントコードの最新版を使用(npm化されていないのでnapaというモジュールで導入)
  • fs.watch()を使ってファイル変更を迅速に監視(node-watchというモジュールを使用)
  • node-devを使うことでサーバ側のファイル変更を監視して、クライアントを自動リロードする機能を追加
  • これらの機能をたった一行を加えるだけで使えるようにパッケージ化

となります。

プロジェクトページは下記です。

https://github.com/dai-shi/easy-livereload

最低限の使い方は、

app.use(require('easy-livereload')())

ですが、細かい設定はオプションを引数に与えることでできます。

本質的には他人が作ったライブラリを結合して簡単に使えるようにしただけですが、 相当便利だと思いますので、ぜひお試しください。

過去にKarma(Testacular)を使ったことはありますが、今回やっと時代に追いついてProtractorを使ってみました。特にPhantomJSを使ってheadlessでE2Eテストするときに気づいたことをまとめます。

Protractorは、 Seleniumの WebDriverJSのラッパーで、AngularJSのE2Eテストのためのフレームワークです。

https://docs.angularjs.org/guide/e2e-testing

がエントリーポイントになるでしょう。 しかし、これだけ読んでも最終的な実行方法は分かりづらいです。 今回の目的は、コマンド一つ、例えば、

$ npm run e2e-test

でE2Eテストを実行してするための環境セットを作ることです。また、headlessで動かしたかったので、典型的に使われるChromeではなく、PhantomJSを前提にしました。サーバはExpress/Node.jsの想定ですが、それ以外の場合にも応用できるかと思います。

初めによくある環境設定です。

$ npm install protractor
$ npm install phantomjs
$ ./node_modules/.bin/webdriver-manager update

この例ではローカルディレクトリにモジュールをインストールしていますが。グローバルにインストールしたい人はそのようにどうぞ。

まず初めにはまったことはwebdriverが起動しないことでした。

$ ./node_modules/.bin/webdriver-manager start
INFO - Launching a standalone server
WARN - null
java.net.SocketException: No such device
    at java.net.NetworkInterface.isLoopback0(Native Method)

このようなエラーがでました。ワーニングかと思って無視していたらいけませんでした。どうやら、IPv6のアドレスのみを使っていてうまく行かないようだったので、Javaのオプションを追加して次のようにするとうまく行くことが分かりました。

$ env _JAVA_OPTIONS=-Djava.net.preferIPv4Stack=true ./node_modules/.bin/webdriver-manager start

さて、Protractorのチュートリアルには、webdriverをコマンドラインで手動で実行するように書いてありますが、個人的にはこれは面倒に感じます。時間はかかりますが、毎回自動で起動して欲しいです。これは簡単で、protractor.conf.jsにseleniumAddressを書かなければよいだけでした。下記に、protractor.confを載せます。

exports.config = {
  specs: [
    '*-spec.js'
  ],
  capabilities: {
    'browserName': 'phantomjs',
    'phantomjs.binary.path': './node_modules/.bin/phantomjs'
  },
  baseUrl: 'http://localhost:' + (process.env.PORT || 3000) + '/'
};

これだけで大丈夫でした。baseUrlは、specにURLをフルパスで書かなくてもよいようにするためで、expressが下記のように起動されている想定です。

app.listen(process.env.PORT || 3000);

specの細かい内容は今回は書きませんが、もっとも単純なものは例えば次のようになります。

describe('tutorial', function() {
  it('should have a title', function() {
    browser.get('index.html');
    expect(browser.getTitle()).toEqual('The Title');
  });
});

ところで、Protractorはテストのフレームワークであるもののアプリの起動には関与せず、アプリは自分で起動しなければいけません。また、テスト終了後はアプリも終了したいです。今回はお手軽にシェルスクリプトを書きました。

#!/bin/sh

export _JAVA_OPTIONS=-Djava.net.preferIPv4Stack=true
export PORT=12345
node ./app.js &
PID=$!
./node_modules/.bin/protractor ./e2e-test/protractor.conf.js
kill $PID

このスクリプトをpackage.jsonのscriptsに書いておけば、当初の目的であったnpmで一発でE2Eテストを走らせることができました。

Happy e2e testing!

mongodbのaggregateがパワフルで便利

  • 投稿日:
  • by

mongodbはドキュメントベースのデータベースですが、APIがシンプルでよいです。これまで、find()とcount()で足りていたのですが、ちょっと複雑なことをやろうとするとクライアント側で処理するはめになってしまいました。

mongodbの集計機能にはいくつかの種類があります。

http://docs.mongodb.org/manual/core/aggregation/

によると、Aggregation Pipeline, Map-Reduce, Single Purpose Aggregation Operationsとあります。 最後のSingle Purpose Aggregation Operationsはまさにcount()のようなものなのですが、他にもdistinct()やgroup()もあります。SQLから類推すれば機能は分かりやすいでしょう。 Map-Reduceは並列処理にはいいでしょう。ただ、JavaScriptの関数を記述することになります。なんでもできるといえばなんでもできるのですが。 残るは、Aggregation Pipelineです。これは処理を逐次的に書くので分かりやすいのが利点ですが、注目したのはJSONで記述できることです。JavaScriptの関数と違ってシリアライズができるのがうれしい。Pipeline Operatorsというのがあって、そこそこ柔軟に書けます。

今回は詳しく述べませんが、参考までにパイプラインのサンプルを載せておきます。

var pipeline = [{
  $project: {
    record: '$records'
  }
}, {
  $unwind: '$record'
}, {
  $match: {
    'record.name': target_name
  }
}, {
  $group: {
    _id: 'all',
    count: {
      $sum: '$record.score'
    }
  }
}];

この$unwindがいいですね。ドキュメントを展開してくれるのです。

欲を言えば、パイプラインではなく、処理を分岐できる有効グラフを記述できたらさらに強力だったろうに。

さて、なぜJSONで記述したかったかというと、social-cms-backendで使いたかったからです。aggregateをサポートしてバージョンアップしました。

https://www.npmjs.org/package/social-cms-backend

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の開発が進められているようです。クリーンに書き直したり、依存ライブラリを減らす計画のようですので、期待できそうです。

いつも思いますが、nodeではモジュール探しが大変です。

今回は、icalフォーマットを出力するためのモジュールを探します。 icalというのは通称で、通称としては他にもicalendarとかicsとかあって紛らわしいです。iCalというソフトの名称もあるのでなおさら。RFC5545と言えば一意に特定できるでしょう。

npmで探しました。上記のようにタグがicalだったりicalendarだったり統一されていません。それでも、5個見つけました。

  • peterbraden/ical.js これはパーザしかなく、今回の目的には使えず。
  • tritech/node-icalendar Staræ•°44でトップ。しかしIssue#5が無視されているのが気になるところ。
  • sunrise/vobject-js vobjectという名前なので一瞬分からない。新しそう。
  • shanebo なんとイベント一つ分しか出力できない。
  • sebbo2002/ical-generator APIはシンプルで分かりやすそう。しかし、save, saveSync, serveは余計な機能ではないか。

セオリー通りにいくとStar数トップのnode-icalendarでいくのですが、Issue#5が気になるのとドキュメントが貧弱なのがマイナスポイントです。ドキュメントの貧弱さは自分のプロジェクトをみると、人のこと言えないですが。

今回は、vobject-jsで行くことにしました。現時点でStar数2!今後の発展に期待します。