0

watchifyでcjsxをビルド

Gruntでwatchしてbrowserifyでcjsx(coffeeで書いたReactのjsx)をビルドしてるって書いたけど
WEB+DB PressのReactの記事が良かったのでcoffeeで書きなおした

たくさんnpmをrequireする様になったら毎回5〜6秒かかって遅くなったので、gulpにしなければならないのか?とか思ったけどgulpのデバッグが難しすぎて挫折した。


watchify

watchifyはbrowserifyのラッパーで、ファイル更新を検知してbrowserifyしなおしてくれる。
ただ更新見てるだけだろって思ってスルーしてたんだけど、よく見たら更新されたファイルだけを変換しなおしてた。
速い。2回目のビルドから1秒弱になる。


オプション

verbose指定しないとビルドした事すら表示されないので付ける。
あとはbrowserifyに付けるオプションと同じものが使えて、–debugでインラインSourceMapを出力、extensionで.js以外にも探す拡張子を追加、coffee-reactifyをtransformに指定してcjsxをコンパイルしてbundle.jsに書き出す。

watchify --verbose --debug --extension=.cjsx --extension=.coffee -t coffee-reactify client/app.cjsx -o public/js/bundle.js

watch先はclinet/app.cjsxしか書いていないが、require先のファイルを全て監視してくれている。

npm run

watchifyはオプションが長くて毎回打つの無理なので、package.jsonのscriptsにコマンドを書いておく。
「npm run コマンド名」で実行できる。

{
"name": "node-flux-boilerplate",
"private": true,
"version": "0.0.1",
"description": "react+fluxxor+socket.io chat",
"main": "server/app.coffee",
"scripts": {
"start": "coffee server/app.coffee",
"build": "grunt build",
"watchify": "watchify --verbose --debug --extension=.cjsx --extension=.coffee -t coffee-reactify client/app.cjsx -o public/js/bundle.js",
"test": "grunt"
},
〜〜(略)〜〜

この中は./node_modules/.bin/にPATHが通った状態になっているのでwatchifyやgruntがグローバルインストールする必要も無くなって便利。



flux-boilerplate

grunt-contrib-watchで監視するのやめて、watchifyを使うようにした。

https://github.com/shokai/node-flux-boilerplate

React+Fluxxor+socket.ioでfluxなチャットを作った


他のビルドタスク

cssとかも、cssメタ言語で作って各DOMにクラスを振るのじゃなくて、React内のInline Stylesで書いてmixinする方がいい気がしたのでbrowserify以外に継続的なビルドタスクが必要無い気がする。

参考:reactjs – React.js + CSS – Qiita

1

React+Fluxxor+socket.ioでfluxなチャットを作った

fluxxorとsocket.ioを使ってチャットを作った。大変ミニマムな感じの実装を心がけてて、今後何かWebアプリを作る時はこれを少し組み替えて作るぞというのを意識してる。ようするに低機能最低限でしょぼい。サーバー側はexpressとmongodb。

https://github.com/shokai/node-flux-boilerplate

herokuでも動く
https://node-flux-boilerplate.herokuapp.com/

クライアントはこんな実装


Fluxアーキテクチャ

Reactはなんとなくわかってfluxをやるか、と思ったらflux npmの中がdispatcherしかなかった。
flux.DispatcherのwaitForの実装


まずこのへんを読んでフムフムなるほど(わかったようなわからないような)となる。
Flux | Application Architecture for Building User Interfaces


で、このスライド見たらわかりやすすぎて気絶した。とりあえずなんらかのfluxフレームワーク使ってチャットでも作る事にした。


あとこっちも、フレームワークなしで最低限のfluxを自作するとしたらこんな感じになるのね、とわかって気が楽になる。
これ読んでおけば、Fluxxor等のフレームワークがどういう工夫をしているか差分を理解しやすくなりそう。
10分で実装するFlux



Fluxxorというフレームワークを使う



http://fluxxor.com/


FluxxorはFluxなフレームワークで、StoreとActionとDispatcherを担当する。ReactをViewとして使うこと前提に作られている。

この記事がとてもわかりやすい
Fluxなフレームワーク、Fluxxorの紹介 – マルシテイアは月の上


で、Fluxxor+ReactにSocket.ioを追加してチャットをつくってみて、こんな感じの実装になった。


fluxはaction→dispatcher→store→view→actionの4つの部品で構成されるが、fluxxorは三叉のロゴのとおりDispatcherが無くてaction/store/viewだけになる。

Dispatcherはfluxxorを使うことでactionとstoreに内蔵されるようになっている。そしてactionからはdispatch関数のみが、storeからはbindActionでの受信のみがアクセスできる様になっているので、action→storeの1方向にしかメッセージが流れない。試してないけどwaitForで順序制御もできるようだ。

fluxorのstoreにはeventemitterが継承されていて、emit(“change”)するとViewにイベントが飛ぶ。ViewはcreateClassした時のmixinsにFluxxor.storeWatchMixin(Store名)を指定すると、changeイベントを受けたらrenderを呼ぶようになっている。ここでも1方向のメッセージングが強制される。

Viewからactionを呼び出すのはthis.getFlux().actionsの下に関数が生えているので、それを呼び出す。

ふつうにfluxを自分で作ったら、dispatcherがどこからでも見える場所にあったりとかで、1方向にしかメッセージを流さないことを心がける事はできても、メッセージの逆流を防ぐ実装をするのはちょっと面倒臭い。fluxxorに乗ってやると円環の理が強制的に生まれる。


socket.ioどこに置くか問題


socket.ioからの入出力はactionを通してfluxとやりとりする。

受信
socket.ioからチャットメッセージ受信→action→storeのlogsに追加→reactがView更新

送信
チャット送信ボタン押す→action→socket.ioでサーバーに送信

socket.io関係のコードをどこに置くかでけっこう迷った。
最初はChatStoreに持たせようとして、でもただの「接続しました!」みたいなStore関係ないメッセージ表示するようなコードはどこに置くのか。

結局fluxの外に独立した部品として設置してactionで通信することにしたけど、送信時のactionからのdispatchを受信させる方法が良くない。dispatchはstoreのbindAction以外にも、flux.on “dispatch”, (type, payload) ->でも受信できるけど、

An action of type send-chat was dispatched, but no store handled it dispatcher.js:105


というwarningがどうしても出される。これを避けるにはどうしてもStoreのフリをするしかないし、そもそもdispatchがstoreじゃない所に飛ぶ可能性を作るのはせっかくfluxとかいう円環の理を作ってバグの場所を特定しやすくした意味がぶち壊しになるのでやめた。
おとなしくactionから関数を呼び出すことにした。socktes/という別ディレクトリを掘ってその中にsocket.io関連のコードはまとめて、fluxとのやりとりは必ずaction経由でやる。

0

flux.DispatcherのwaitForの実装

Promiseもコールバックも使ってないのにDispatcherのwaitForで順番の制御ができる。どういう事なのか気になったので調べた。


flux.Dispatcher

Reactはなんとなく使えるようになった気がしたので、fluxでやろう、StoreとかActionとかどう書くんだと調べてて、とりあえずflux npmというFacebookの中の人によるfluxアーキテクチャの説明とDispatcherが一つだけ入っているnpmを見ていた。

Dispatcherは使ってみた感じ、イベント名が無いEventEmitterみたいな感じで、登録順に実行される。

flux = require 'flux'
Dispatcher = new flux.Dispatcher

Dispatcher.register (action) ->
console.log "1 - #{JSON.stringify action}"

Dispatcher.register (action) ->
console.log "2 - #{JSON.stringify action}"

Dispatcher.register (action) ->
console.log "3 - #{JSON.stringify action}"

Dispatcher.dispatch
actionType: 'add-food'
food: 'beef'

Dispatcher.dispatch
foo: 'bar'
1 - {"actionType":"add-food","food":"beef"}
2 - {"actionType":"add-food","food":"beef"}
3 - {"actionType":"add-food","food":"beef"}
1 - {"foo":"bar"}
2 - {"foo":"bar"}
3 - {"foo":"bar"}


waitFor([id…])


なんかwaitForにregisterメソッドから返ってくるIDを渡すと、そのコールバック呼び出しの順序を制御できるらしい。便利そう

flux = require 'flux'
Dispatcher = new flux.Dispatcher

wait_ids = []

Dispatcher.register (action) ->
Dispatcher.waitFor wait_ids
console.log "1 - #{JSON.stringify action}"

wait_ids.push Dispatcher.register (action) ->
console.log "2 - #{JSON.stringify action}"

Dispatcher.register (action) ->
console.log "3 - #{JSON.stringify action}"

Dispatcher.dispatch
actionType: 'add-food'
food: 'beef'

2 - {"actionType":"add-food","food":"beef"}
1 - {"actionType":"add-food","food":"beef"}
3 - {"actionType":"add-food","food":"beef"}

何度もwaitFor

1つのコールバック内でwaitFor→なんか処理→またwaitFor→なんか処理 ができる。気持ち悪い。
flux = require 'flux'
Dispatcher = new flux.Dispatcher

wait_ids = []
wait_ids2 = []

Dispatcher.register (action) ->
Dispatcher.waitFor wait_ids
console.log "1 - #{JSON.stringify action}"
Dispatcher.waitFor wait_ids2
console.log "done!"

wait_ids.push Dispatcher.register (action) ->
console.log "2 - #{JSON.stringify action}"

wait_ids2.push Dispatcher.register (action) ->
console.log "3 - #{JSON.stringify action}"

console.log "wait_ids #{wait_ids}"
console.log "wait_ids2 #{wait_ids2}"

Dispatcher.dispatch
actionType: 'add-food'
food: 'beef'

wait_ids  ID_2
wait_ids2 ID_3
2 - {"actionType":"add-food","food":"beef"}
1 - {"actionType":"add-food","food":"beef"}
3 - {"actionType":"add-food","food":"beef"}
done!


waitForの実装

https://github.com/facebook/flux/blob/2.0.2/src/Dispatcher.js#L154-L177

waitForというより、指定idのコールバックを先に実行しているだけ。待っているように見えてるだけだった。

そもそもDispatcher.registerにはEventEmitterのようにイベント名は登録できないので、dispatchしたらregister済みの全てのコールバック関数は登録順に即実行される。(actionTypeなどのプロパティで自分宛てなのか判定してねという作法になってる)

dispatchすると、コールバックは実行直前に自分のidにisDispatchingフラグを立てる。
コールバック内でwaitFor([id…])を呼ぶと、id指定したコールバックをその場で実行しようとする。
対象がisDispatchingの場合は次のidを見る。
waitForで指定されたidのコールバックの中で、さらにwaitForが呼ばれたら再帰的にまた同じことをやる。

自分のidにはpendingフラグが立っていて、pendingなコールバック間でwaitForがループするとCircular dependency detected while…というエラーがthrowされる。

waitFor指定したコールバックの呼び出しが完了したら、そのまま処理が続くので順番が制御できる。

0

WEB+DB PressのReactの記事が良かったのでcoffeeで書きなおした

昨日か今日ぐらいの発売のWEB+DB Press vol.86のp123~のnaoya氏のReactの記事が良いらしいからみんなで勉強しましょう、って増井先生が言ってたので、昨日夕方数人で読んでて家帰ってから実際にコード書いてみた。

JavaScript苦手なのでcoffee-scriptで写経した。

https://github.com/shokai/react-coffee-study

observer modelかつデータの流れを1方向にするのをFluxアーキテクチャと呼ぶのとか、DOM切り貼り職人のつらみとVirtualDOMの差分更新とかそういう概念の話は知ってたけど、なぜかなかなかReactそのものを使う機会が無かった。

Reactの感想

触ってみた感じではBackbone+Marionetteよりとっつきやすい。
WEB+DB Pressの記事の前半のtodoリストはみたままcoffeeで書いて、それですぐ馴染んできたので後半のmarkdown previewはなんとなく適当に書いたらちゃんと動いた。



coffeeでreact(coffee内にJSX)

最終的にcjsxというcoffee内にJSXを書ける拡張を使って、
Gruntからbrowserify+coffee-reactifyでsourceMap付きでJSX変換済みのjsを吐き出すようにした。
NODE_ENV=production付けるとさらにuglifyjsで圧縮する。
リポジトリ内のGruntfile.coffee見るとわかる。


ReactにはJSXというHTMLのようなテンプレートエンジンが埋め込まれたJavaScriptが使われていて、ふつうのJSの中にこういうのを書くと

こういうJSに変換される。

つまりHTMLっぽいJSXというマークアップを書く→JSに変換→VirtualDOMが構築され→適当なタイミングで本物DOMが差分更新される、らしい。

このJS内のJSXの変換は、WEB+DB Pressの記事では最初JSXTransformer.jsでクライアントサイドでやらせてて、後半はbrowserifyでサーバー側であらかじめ変換する方法を取っていた。


coffeeでjsx使うにはcoffee-react npmの中にあるcjsxコマンドを使うんだけど、この後にさらにbrowserify通すとsourcemapが崩れてデバッグ不能になる。
多段階の変換するとsourcemapズレたりどっか飛んでいってしまってよくないので、cjsxの変換もbrowserifyにやらせる。


browserify

ReactはjQueryと共存できない→jQueryないと俺はajaxできない→ajax機能だけのjQueryをカスタムビルドするのもまあアリかもしれないけど、ここはbrowserifyでnodeのライブラリをブラウザに持ってくれば解決、あとeventemitterとかも持ってこれるし便利!

というわけでbrowserifyというJSを静的解析してrequire先のファイルを全部1つのjsファイルに固めてくれるツールを使う。

debugオプション付ければsourceMapもちゃんと付くので、cjsxでのエラーもその先のnpmのファイル名や行番号も見える。

全部リポジトリ内のGruntfile.coffeeに書いた。