Sencha Architect 3.0 プレビューでのユーザー拡張機能の実装
こんにちは、ゼノフィnakamuraです。
このブログ投稿はArchitect 3のユーザー拡張機能を開発していたときに直面し乗り越えた、いくつかの特定の技術的な問題を紹介します。
Sencha Architectとは
まず、Architectについてのバックグラウンドを説明します: Sencha Architect はExt JSとSencha Touchフレームワークを利用してHTML5アプリケーションを作成するためのビジュアルツールです。Architectに対して立てた三つの主な目標は:Senchaアプリケーションの作成に関して最良の方法となること:フレームワークを利用するのにベストプラクティスを表示する綺麗なコードを生成すること:開発者とデザイナーが一緒に作業できるようにすることです。
Architect 3はこの目標をすべて進めていきます。はじめにSenchaCon 2013にて発表され、8月上旬に公開されたArchitect 3のプレビューをご覧ください。プレビューで導入された新しい機能は:最初の二つの目標と強く関係するアプリケーションテンプレートとユーザー拡張機能:三つ目の目標と関係するテーマサポート。
テーマサポートは、デザイナーは開発者と一緒に作業して、会社と自分たちのスタイルの要領に従って、ビジュアル的にとても魅力的なアプリケーションを作成することが目的です。デザイナーと開発者の間でよくあるフィードバックの繰り返しは長過ぎるので、この過程を改善できると思いました。
それぞれがそれぞれの専門分野と協力して、助け合う状態になるための単一のツールを作りたかったのです。 アプリケーションテンプレートとユーザー拡張機能はArchitectの範囲と良いコードの生成を確保することで最初の二つの目標に近づいています。デザインビューから生成されたコードが綺麗で上級の開発者が書いたように見えることを保証したかったのです。読みにくいコードを生成するツールをたくさん経験したので、そうならないようにしよう心に決めました。ユーザー拡張機能の基本概念は容易かつ便利な方法により別の場所で生成されたコードをArchitectに挿入しながら、デザインタイム対応を確保することです。アプリケーションテンプレートの基本概念はユーザーが新しいプロジェクトを作成する時に一般的なアプリケーションパターンから選択できることです。
注:プレビュービルドはまだユーザー定義のアプリケーションテンプレートの生成はできませんが、最終的なバージョン3.0では出来るようになります。全ての機能はユーザーから提案されたので、それを出荷できるのを嬉しく思います。
ユーザー拡張機能の実装の経験
Sencha Architectのアーキテクチャに、ユーザー拡張機能の対応を追加するのは少々難しいことでしたので、私達の体験から学んで下さい。
一歩下がると:Architectのユーザーインターフェースは全てExt JSで作成されたHTML5アプリケーションです。Sencha Desktop Packagerでラップされているバイナリ実行可能・インストーラとしてArchitectを出荷しています。これはネイティブ機能にアクセスするためにAPIを追加した私達のChromiumの特別バージョンとなります。いくつか利用しているネイティブシステム機能は:ファイルシステムからファイルの読み込み、書き込み、削除、そしてネイティブウィンドウメニューの操作です。私達のコントロールをレンダリングするためにブラウザを使用することにより、多くのデザインツールに欠けている豊かな忠実度を提供することができます。そして、アプリケーションが指定されているブラウザで実行していることはわかっているので、利用可能となる全ての最新HTML5の機能を生かせます。
“「Sencha Desktop Packagerに包まれているバイナリ実行可能・インストーラとしてArchitectを出荷しています。ネイティブ機能性をアクセスするためにAPIを追加した私達のChromiumの特別バージョンとなります。」
Architectでユーザー拡張機能を可能にする私達の経験に戻ると、アプリケーション内でユーザー提供のコードを実行する必要がありました。このユーザーコードは文字通り何でも可能です。このコードは私達の特権のAPIからサンドボックス化される必要がありました。あなたのホームディレクトリ内のファイルをいきなり削除するユーザー拡張機能をインストールすることを想像して下さい!
デザインビュー(キャンバスと呼んでいます)で実行しているコードを取り分けるために、iframeとHTML5 sandbox属性を利用しました。
sandbox属性はユーザーがiframeをどうのように動作するかを設定できます。私達の場合では、スクリプトを許可するように設定しましたので、JavaScriptがiframe内で実行できます。sandbox属性が提供している詳細コントロールの説明が書かれているHTML5 Rocksのセクションをご覧下さい。デフォルトでは、sandbox属性を設定するときに、iframeの親またはトップレベルのフレームにとどきません。 このため、キャンバスのiframeにコードをロードして、私達の特権APIにアクセスを禁止にさせます。アプリケーションでは、フレームに格納されている任意のプロパティにアクセスできないので、フレームになんらかの方法で通信する必要があります。window.postMessage経由で通信するためにHTML5スタンダードのウェブメッセージ(またはクロスドキュメントメッセージ)を利用しています。フレーム間の通信を全て抽出するCrossWindowMessagingクラスを作成しました。
CrossWindowMessagingクラスを利用して、Ext.util.Observableで利用可能となる機能をほとんど対応しながら、フレーム間でのイベントを簡単に行えるようになりました。以下のコードでそのインタフェースを一覧してます:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // まず最初は各ドキュメント内でメッセージインタフェースを設定 // これは別々のドキュメントでサンドボックス化されていることを意識していて下さい // JSプロパティやDOMコンテンツにアクセス出来ません // その中に // 次のコードを包む外側のドキュメントで実行して下さい var outerMessaging = Ext.create('sencha.CrossWindowMessaging', { targetWindow: iframe.contentWindow }); // 次のコードを中のドキュメント(iframe)で実行して下さい var innerMessaging = Ext.create('sencha.CrossWindowMessaging', { targetWindow: window.parent }); // iframeドキュメント内で発生しているイベントサブスクライブ outerMessaging.on('myevent', myEventHandler, this, {buffer: 100}); // ゼロまたは多くの加入者に対してイベントが発生したことを通知する innerMessaging.notify('myevent', 'arg1', 'arg2'); innerMessaging.notify('myevent', 'arg1', 'arg2'); |
メッセージクラスを利用した上、二つのフレーム間の関数を非同期で呼び出すことが出来ました。
1 2 3 4 5 6 7 8 9 10 11 | // フレーム全体に呼び出せるメソッドを実装 outerMessaging.implement('myMethod', function(arg1, arg2) { return 'myReturnValue'; }); // フレーム全体にメソッドを呼び出して下さい var promise = innerMessaging.call('myMethod', 'arg1', 'arg2'); promise.then(function(value) { // 値は実装でご覧になる'myReturnValue' と同じになります。 }); |
このアプローチで以前では同期だったAPIが全て非同期になったため、深く入れ子されていた関数コールバックの設定に行き着きました。このお陰で、私達の開発パラダイムのシフトが必要となり、私達の全てのクロスフレームメソッドの呼び出しからpromiseを返しました。Sencha Touchチームとほぼ同時期に、この問題が発生しました。興味深いことに、各チームはpromiseを利用することにそれぞれが決めたので、この実装は近い将来で収束するだろうと思います。promiseを利用すると、いくつか非同期の呼び出しから戻り値を取得して、一連の中で利用する必要があるときに、全ての必要な手順を手続き的に簡単に表現するします。 Node.jsで少しでも開発経験のある人は、非同期開発を行っている時にこの問題はすぐに直面ことをご存知だと思います。
下記は、Architectからpromiseの利用を表示するコードスニペットです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | return me.promiseAll(xds.DefaultStores.map(function(storeConfig) { return me.messaging.call( 'registerStore', 'defaultstore:' + storeConfig.storeId, me.encodeCanvasConfig(storeConfig) ); })) .then(function() { // 全ての user model を登録 return xds.app.manager.Model.updateAllCanvasInstances(); }) .then(function() { // 全ての user store を登録 return xds.app.manager.Store.updateAllCanvasInstances(); }) .then(function() { me.ready = true; // If an activeInstance was set before the framework loaded, force it to get rendered // フレームワークがロードする前にactiveInstanceがセットされていたら強制的に描画 if (me.activeInstance) { me.setInstance(me.activeInstance, true); } }, function(ex) { // 例外の再スロー throw ex; }); |
各関数の呼び出しで、入れ子のコールバックを利用した場合、これがどのようになっているか想像してみてください。とても手に負えない状態になります!promiseを利用して、コードベースを綺麗にし、iframeに常に行ったり来たりする非同期の呼び出しを整理する問題も解決してきました。
Architectでユーザー拡張機能を開発したときに遭遇した問題の紹介が、あなたのプロジェクトに役に立てば私達は嬉しいです。
最後に、Ext JSとTouchのユーザー拡張機能を開発しているユーザーのコミュニティが豊富にあることを知っています。ユーザー拡張機能の作成 と ユーザー拡張機能の統合 のガイドに従って、.aux(Architect User Extension)ファイルとしてユーザー拡張機能をコミュニティに提供できたら嬉しいです。Architect 3.0フォーラムでフィードバックを下さい。