大規模UIをJavaScriptで実現するためのテクニック、サイボウズkintone開発の現場から。デブサミ2012
7万行規模のJavaScriptプログラムで構築されたユーザーインターフェイス。そのプログラミングで使われたテクニックとはどういうものか。
2月16日、17日の2日間、都内で開催されたイベント「デベロッパーズサミット2012」(デブサミ2012)で、サイボウズ株式会社 開発部 若原祥正氏、生駒浩隆氏が講演「kintoneの表と裏~大規模JavaScript開発と非構造データベース」を行いました。
講演では前半に大規模JavaScriptのテクニック、後半でスキーマレスデータベース構築の仕組みが解説されましたが、この記事では資料が公開された前半のJavaScriptのテクニックについて、ダイジェストで紹介します。
7万行のコード、フレームワークはClosure Library
サイボウズ株式会社 開発部 若原祥正氏。
Kintoneというアプリケーションを作っています。Kintoneとは、クラウド上にノンプログラミングでアプリケーションを作れるサービスです。パーツをドラッグ&ドロップで画面上に組み合わせていくと、画面フォームを用いたアプリケーションが作れます。
作ったフォームから入力されたデータを集計してグラフ表示などもできます。
ここでは、このKintoneのクライアントサイドについて。JavaScriptでどうユーザーインターフェイスを作ってきたかという、泥臭い話をします。ふだんJavaScriptでプログラミングの仕事をしている人に役に立てばと思います。
KintoneのJavaScriptのコードは、フレームワークを除いて7万行くらい。フレームワークにはClosure Libraryを使っています。これはかなり低レベルな機能を提供するものなので、この上に自社のUIライブラリを作ってその上でアプリケーションを構築しています。
サーバサイドへはXHR(XmlHttpRequest)を叩き、JSONで結果を受け取っています。
JavaScriptで大規模UIを作るために必要なこと
まずはイベント管理について。
ドラッグ&ドロップで画面フォームを自由に作れる機能では、部品のドラッグ&ドロップだけでなく、サイズのドラッグによる変更などもできるので、扱うイベント数だけでおよそ50。さらに細かく制御しようとすると、イベントの開始、開始直後、イベント中、終了直前、終了直後など、飛躍的に扱うイベント数が増えるので、うまく管理しないと破綻してしまいます。
Kintoneでは「クラス間通信」といって、イベントを実際に処理を行うクラスに渡し、受けるところで処理しています。こうすることで、実際に誰が処理をするのかを分かりやすくしています。
シンプルなMapで実装できるので、普通のイベントを扱っているAddEventListnerと変わりません。
この仕組みのいいところは、DOM構造的には親子関係がなくても、イベントを介してやりとりができること。クラスの役割が明確になり、1つのクラスに実装が集中するのを防げます。また、DOMイベントと違ってたくさんイベントを貼っても重くなりにくいです。
メモリリーク対策
画面の編集状態と詳細状態をDOMで動的に1日中切り替えていたら、IEがフリーズする、という現象がありました。これはクラスをきれいに消せていなかったためにメモリリークが起きていたことが原因でした。
対策は、残っていた参照にnullを入れて地道にガベージコレクションさせるしかありません。swfオブジェクト内でも参照を切る必要があるので注意が必要です。
でもこれを確実にすることでものすごく軽くなりました。
遅延処理による速度向上
あるお客様が、フォームの繰り返し行を50回以上使われていて、表示が遅くなってしまったとのこと。iframeを50回繰り返すあいだ、ほかの処理がブロックされているので、それが遅くなっている原因でした。
そこで使ったのがdeferredパターン。後で使いたいリソース、Ajaxなどの非同期処理による結果を、同期的に記述できるデザインパターンです。
ループ処理をdeferred化して、しかも非同期にするにはどうすればいいか。
jsdeferredの実装も見てみたりして、結論としてdeferredのトリガはdeferredコールバックなんですが、これをsetTimeoutを囲んで、ループ処理をゆっくり実行させる。繰り返し部分のDOMがゆっくり実行されることになる。
このときの利点としては、同期的に記述できるのでコードが複雑になりにくい。
そしてふたつめのこれが結構大事なんですが、すべてのdeferredのループ処理が終わったあとでコールバックが実行されることが保証されるので、全部終わるまでフォームのボタンを押せないように、といった処理ができる。これをしないと、描画中の間違ったフォームが送られてしまう。
これでお客さんに見てもらったのですが、まず表示が行われて、表示の裏側でスクロールバーが、ガガガっとまだ処理が動いているのですが、それには気づかれずに「おお早いじゃん」と言われたので、やはり体感速度と実際の速度は違うんだなと。
大規模UIに必要な要素として、重くなったところには遅延処理を確実に使っていきましょうと。
JavaScriptをコンパイルして爆速に
アプリケーションを公開するときに何が起きたか、という話を最後にします。
今回はかなり大きなプログラムになりました。モジュールの数が多すぎて、1ページで読み込むスクリプトが300個。要するにscriptタグが300行並ぶ。すると最初に300HTTPリクエストがサーバに飛んでいきます。
これでは重すぎるので、JavaScriptをコンパイルすることにしました。
kintoneで使うことにしたのはClosure Compiler。最適化レベルは最大にしています。このレベルでは関数名も最適化(長い関数名でも短いアルファベットに変換)してくれるので、かなり小さくなります。
コンパイル後、スクリプトは1つになり、大きさも300KBから90KBになりました。コンパイル前と比べると爆速です。大きいJavaScriptを作ることを恐れる必要がなくなりました。
今日お話ししたのは、僕が痛い目にあってきたことばかりです。最初からこれが分かっていたらと思います。特にイベントの部分は最初に設計をミスって痛い目にあいましたので、これらがみなさんの参考になればと思います。
講演された若原氏が、ご自身のブログのエントリ「デブサミ2012で大規模JS開発について発表してきました」で、いくつか補足を書かれていますので、あわせてどうぞ。