yo_waka's blog

418 I'm a teapot

デブサミ2012で大規模JS開発について発表してきました

「Developers Summit 2012 - 10年後も世界で通じるエンジニアであるために」で発表してきました。


イマドキのJSの話とかではなくて、UIをJSで作る際の設計ノウハウみたいな話にしたので、つまらなかったら申し訳ないなと思ってたのだけど、終わったあとも何人か質問しにきてくれた方がいたのでホッとしました。
10年後も・・というテーマとして、激しく進化するJSの最新動向に左右されず使えるネタを選んだつもりではあります。
普段からJSでもパフォーマンス意識して設計してる方には当たり前のことばかりだったかも。


jQueryは甘えってのは書いてみたかっただけですすいません。。
けど、適材適所というかSinatraで100画面近くあるようなWebアプリは作らないでしょそういうときはRails採用するでしょというかそういう感じのことが言いたかったんだ!
実は初期の頃はjQueryも検討してたんですけど、要素が多くなってくるとコントロールが増えてイベント数が増えると画面が重くなることが分かって、上手くやれば回避もできるんですが属人性の高いコードになりそうだし将来チューニングに追われるのが目に見えていたのであきらめた経緯があったりして。jQueryは家でサクッと書きたいとき便利に使ってます。
全然関係ないけど、気軽にDOM操作が出来るところがjQueryのストロングポイントだと思っているのですが、最近のバージョンアップはどういうユーザーをターゲットにしてるのかよく分からないなー


書き忘れたことがあって、クラス同士で通信するやり方で紹介したものは書いてある通り単純なMapで実装されているので、dispatchEventを受け取ったハンドラは同期処理されます。
その辺が通常のDOMイベントと違うので注意が必要ですね。でもボタン押した後の処理は分割されても同期で処理してほしいケースがほとんだし問題ないか。


Deferredに関してはClosure LibraryはMochikitのDeferredクラスをポーティングしてきているので、普段はそれを使ってます。
でもスライドで解決法として上げた、非同期ループ(JSDeferredでいうところのloop)は機能として実装されてないので、goog.async.Deferredクラスを継承したクラスに自前でcybozu.async.forEachみたいに書いて使っています。


Closure CompilerのAdvanced Optimizations(最大レベル)による圧縮は、サクッと書いてしまいましたが既存のJSプロジェクトをAdvanced Optimizationsで圧縮するのはかなり大変です(JSDocコメントの整備、extern書いたり、コンパイルできない記法を書き換えたり)。
ウチでもみんなでひたすら置換するシェルスクリプト書いて後は人海戦術みたいな力技で対応した思い出が。
規模が小さければ対応は簡単ですが、大きくなってくると時間がかかります。なるべくプロジェクトの初期に使う前提でいくかどうかを検討して調査しておくのがいいと思います(体験談)。


後半の@hikomaによるRDBMSで非構造型DBをどう作るかという話もぜひ公開したかったのですが、ちょっと事情があって泣く泣く削除。。。
ウチのトップエンジニアの発表だし、こちらも時代に左右されない技術だったので公開したかったなー><
でも、見たい!という人がTwitterなりでつぶやいたりしてくれれば公開できるかもしれません。

※ @hikomaがブログで後半の話をフォローしてくれたみたいです
デブサミ2012でkintoneの裏側を紹介したけど・・


それにしても疲れた><
帰宅後、週末にスライド数十枚作るのって大変なんだなーということが分かっただけでも勉強になりました。

WebRTCを使ってWebカメラから顔認識+画像効果

ChromeのDevチャンネル版でWebRTCが使えるようになったそうなので早速試してみた。
DevChannelからChromeを落として、「chrome://flags」から有効にできます。

WebRTC(Web Real-Time Communications)は、ブラウザからカメラやマイクを利用できるようにしたり、P2Pで通信できるようにしたりといったこれまでと違ったユーザー体験をJavaScriptから簡単に使えるようにするための仕様です。
SWFやFlash Media Serverとかで出来ることがJSで出来るようになるイメージ?
詳しい仕様は、https://sites.google.com/site/webrtc/homeとかhttp://dev.w3.org/2011/webrtc/editor/webrtc.htmlを見るとよいです。


試しに、Webカメラから顔認識して切り取れればプロフィール画像とか作りやすくなっていいかなーと思ったのでやってみた。素の画像だとちょっと恥ずかしいので効果もつけれるようにしてみた。

デモの置き場所に初めてGitHub Pagesを使ってみました。静的なコンテンツ置くのに超便利!
Webカメラデモ(Chrome Dev版じゃないと動きません><)

流れはこんな感じ。
1. WebカメラをCanvasに書き出し
2. Canvasのビットマップデータで顔認識
3. 顔周辺を切り取り
4. 切り取った画像に効果つける
5. 保存!

JavaScriptからはStreamAPIで定義されている、navigator.getUserMedia(media, successCallback, errorCallback)でWebカメラを取得できるようになるみたいだけど、今はブラウザごとにベンダプレフィクス付ける必要があるらしい。Webkit系だったらnavigator.webkitGetUserMedia。
参考

navigator.getUserMediaはOpera Labs版でも使えるみたいだけど、ccv.jsでエラーになったので断念><

顔認識部分にはccv.js、画像のエフェクトにはCamanJSを使ってみた。ccv.jsの早さにびっくり。
エフェクト処理も画像サイズを200x200と小さめにしてるので結構早い。
最初500x500にしてたのだけど、それだとかなり時間かかりました。
Canvasから直接DataURL使ってダウンロードするときファイル名を付けられるようになるといいんだけど。。。
あと、StreamからgetImageDataとかできるとよりCanvasで扱いやすくなっていいなと思った。

っていうかこういうのがサクッとHTMLとJavaScriptで作れるなんて進化早すぎ。
ブラウザの性能がもう少し上がってCanvasがもっとサクサク動いたら本当に実プロダクトで使える日も近いかも。

オレオレCoffeeScriptを作ろう

若干遅いですが、あけましておめでとうございます。
今年もこっそり!


昨年からcoffee-scriptが流行り出しましたね!
coffee-scriptってもう現場でも使われ始めてるのかな?
Railsな人たちは使ってるんだろうけど、JavaScriptでアプリ書いちゃうようなゴリゴリ書いてるところでも採用してたりするのかしら。

個人的には素のJSの方が小回りが利くので好きなのですが、誰が書いても出力されるJSの文法を揃えられるところはいいですね。
いや待てよ、よくよく考えてみれば、JavaScriptトランスレータな形でフレームワークの機能を提供するのはありかも。
人によってコールバックだったりイベントドリブンだったり、書き方が異なるとコードレビューも大変ですしそういうのを言語側で吸収するのはよさそう。ってか同じこと考えてる人結構いるんじゃね?
これはもう、2012年は間違いなくオレオレJavaScriptトランスレータが流行る!(嘘


ということでJavaScriptからJavaScriptを出力するトランスレータを作ってみました。
前のブログで、Closure Libraryのgoog.inheritsとgoog.baseはいいよねみたいなこと書いたので、言語としてその機能を提供しましょう。
素のJavaScriptに加えて、"class Foo extends Bar"と"super()"、"super.someMethod()"を使えるようにして、JSファイルとして出力する仕様にします。


JavaScriptの文法+上の文法を構文解析して作ったASTをJavaScriptに変換すればいいわけですが、JavaScriptのLexer/Parserを1から書くのは大変そう。。。

ググったところ、id:ConstellationさんがEcmaScriptのASTを作ってくれるLexer/Parserを作られていたので使わせてもらって、パーサーに"ClassDeclaration"と"ExtendsStatement"を追加しました。
素晴らしいもの、ありがとうございます!

これでASTからJavaScriptへの変換器を書くだけで上手くいきそうです。

Rewriterを書くにあたっては、id:teramakoさんがまとめられている、ECMAScript 6th Syntax Grammerがとても参考になりました。

ソースコードはGitHubに上げてあります。
名前はJSに少し機能追加したものなので、なんとなくJC Scriptでw
waka / jc-script


git cloneしてnpmインストールすると(しなくてもいいです)、こんな感じでCoffeeScriptライクに使える。
jcファイルをコンパイルすると、デフォルトでjcファイルと同じディレクトリに変換したjsファイルを書き出します。

$ jc -c -p test/extends.jc // 出力されるJavaScriptコードを標準出力に表示する
$ jc -c test/extends.jc // test/extends.jsを書き出す

extends.jcの中身はこう。

/**
 * @constructor
 */
class Parent() {}

/**
 * @type {boolean}
 * @private
 */
Parent.prototype.inDocument_ = false;

/**
 */
Parent.prototype.enterDocument = function() {
    this.inDocument_ = true;
};

/**
 * @constructor
 * @extends {Parent}
 */
class Child() extends Parent {
  super();
}

/**
 * @type {string}
 * @private
 */
Child.prototype.status_ = 'deactive';

Child.prototype.enterDocument = function() {
    super.enterDocument();
    this.status_ = 'active';
};

書き出されるextends.jsはこうなる。

function Parent() {
}

Parent.prototype.inDocument_ = false;

Parent.prototype.enterDocument = function () {
    this.inDocument_ = true;
};

function Child() {
    this.constructor.superClass_.constructor.apply(this, []);
}
(function(childCtor, parentCtor) {
    function tempCtor() {};
    tempCtor.prototype = parentCtor.prototype;
    childCtor.superClass_ = parentCtor.prototype;
    childCtor.prototype = new tempCtor();
    childCtor.prototype.constructor = childCtor;
})(Child, Parent);

Child.prototype.status_ = "deactive";

Child.prototype.enterDocument = function () {
    this.constructor.superClass_.constructor.prototype.enterDocument().call(this);
    this.status_ = "active";
};


これ自体は年末年始に「言語実装パターン」を読んだアウトプットだったりします。
といいつつ変換器しか書いてないけど。。
構文解析とかこれまでまともに勉強したことなかったので、よい機会になった!いい本でした。
ANTLRが何やってるのかとか、ようやくこの本読んで理解出来た気がする。

上で作ったのはおもちゃだけど、入力はECMAScript 6thで書けて、出力は5thとの差分を現状の代替コードとして吐き出すトランスレータとかは結構使えるかもなーと思ったりしました。
変換器は作ったので、時間できたらやってみようかな。

Google Closure Libraryの良いところ

Google Closure Libraryを使ってると、基本的にプロトタイプベースのOOPで書かざるを得なくなる訳ですが、その中でClosure Libraryを使ってクラスを書く上で最もベースとなっている"goog.inherits"と"goog.base"は改めてよくできてると思った。

goog.provide('foo.bar.Sample');

goog.require('goog.ui.Component');

/**
 * @constructor
 * @extends {goog.ui.Component}
 */
foo.bar.Sample = function() {
    goog.base(this);
};
goog.inherits(foo.bar.Sample, goog.ui.Component);

/**
 * @override
 */
foo.bar.Sample.prototype.createDom = function() {
  var el = goog.base(this, 'createDom');
  el.className = 'ui-sample';
  return el;
};

/**
 * On added to the DOM tree.
 * @override
 */
foo.bar.Sample.prototype.enterDocument = function() {
  goog.base(this, 'enterDocument');
  var el = this.getElement();
  this.getHandler().listen(el, 'mousedown', this.mouseDownHandler_, false, this);
};

/**
 * @param {goog.events.Event} evt
 * @private
 */
foo.bar.Sample.prototype.mouseDownHandler_ = function(evt) {
    console.log("Clicked sample");
};

こんな感じですごい自然にクラス継承したJSが書けるのが素晴らしいなーと。


goog.inheritsではいわゆるプロトタイプ継承やってるわけですが、多重継承するケースにも対応されています(といってもconstructorプロパティに自分自身を突っ込むだけだけど、こうしないと子孫クラスがみんな同じconstructorプロパティを見てしまう)。


goog.baseではgoog.inherits()でsuperClass_に指定されたクラスのプロトタイプから指定のメソッドを引いてきて、指定のコンテキストオブジェクトでもって実行してくれる。
メソッド指定なしだと、arguments.callee.caller.superClass_.constructor、つまり親クラスのコンストラクタを実行する。
EcmaScript5のStrictModeでは、arguments.calleeとarguments.caller使うと例外吐いてしまうので多分近いうちに中の実装は変わるんだろうけど。

function Child(arg1, arg2) {
    goog.base(this, arg1, arg2); // これは
    Parent.apply(this, Array.prototype.slice.call(arguments, 0)); // これと同じ
}
goog.inherits(Child, Parent);

/**
 * @override
 */
Child.prototype.someMethod = function(arg) {
    goog.base(this, 'someMethod', arg); // 親クラスのメソッドでargを処理してから
    // 独自の処理をごにょごにょ
};

大規模なJS開発

最近大規模すごい!とか言われるようになってそれは嬉しいんだけど、複数人で作ってるすごい!なのか、10万行すごい!なのかよく分からない。
この2つはちょっとアプローチの仕方が違う。


前者の方は、厳密にPrivate変数がないJSでプライベート/パブリックをどう表現するとか、FixJSStyleやGJSLintやClosureCompilerをビルドプロセスに組み込んで通らなかったらメール飛ばすとか、コードスタイルの統一化だったりCI的な自動化をどう取り入れるかを考えないといけない。

後者の方は、UI作る場合はClosureLibraryだったら必ずgoog.ui.Componentを継承して作ることでDOM生成のエントリポイントを複数に分けたりDOMイベント減らしてカスタムイベントを増やすとか、どうしても描画数が多くてパフォーマンスが保てないので非同期化してDeferredでつなぐとか、イベントやDOMが多くても性能を出すためのコードデザインの工夫を考えないといけない。


前者は仕組みなので、割と導入しちゃえばそれまでよ!なところがあるけど、後者はノウハウがものをいうしブラウザの進化と共によいやり方が日々流動してる感がある。
なので、複数人≒でかいコードなんだけど、そこで考えることはちょっと違うのでどっちかを見ただけでなるほどなとなってほしくはないなーと。