ExtJSは豊富なコンポーネントを提供していますが、変化の激しいHTML5+JavaScript開発において、サードパーティのJavaScriptライブラリを使うケースもあります。
ExtJS4.2 + Sencha Cmdの開発環境で、サードパーティのJavaScriptライブラリを使うにはどのような構成にすればよいか試してみました。
JavaScript/ExtJS/ExtJS4 + Sencha Cmd 勉強メモ3, アイコン画像のカスタマイズ からの環境で練習します。
今回の実験環境:
Ubuntu 12.04 LTS (64bit) ext-4.2.1.883 (GPL版) Sencha Cmd v4.0.4.84 Ruby (rbenvにて ruby 2.0.0p481 をインストール済み) /work/www 以下をDocumentRootとしてApacheHTTPDで公開 /work/www/ext-4.2.1.883 にExtJSを展開済み /work/devtools 以下にSencha Cmdをインストール Webブラウザによる動作確認:Firefox 29 on Win7 Pro 日本語版
引き続き、"/work/www/extjs-tests/t1" を "${app.dir}" として参照します。
サンプルコード : https://github.com/msakamoto-sf/extjs42-senchacmdv4-exercise
最初に試してみたのが、Sencha Cmdのビルドシステムに組み込む構成でした。
実はSencha CmdのビルドシステムでExtJS以外のJSライブラリを使う話題、ぐぐってみるとSencha Touchのケースが多く見つかります。Sencha Touch のソースツリーの場合、app.jsonに "js" というキーがあり、そこでSencha Touchを含む、使用JSファイル一式を指定しているようです。(Sencha Touchは未使用なので正確なところは分かりませんが・・・)
そこで、見よう見まねで app.json にこんな風にjQueryをCDN経由で参照するように "js" 設定を追加してみました。
"${app.dir}/app.json":
{ "name": "MyApp", "requires": [ ], // 追加 "js": [ { "path": "//code.jquery.com/jquery-1.11.0.min.js" } ], "id": "97333d8b-3cca-4f30-aa90-d40a2910b45e" }
ビルドに関連するファイルが変更されたので、"sencha app refresh" しようとしてみたのですが・・・
msakamoto@dev1-u1204lts-x64:/work/www/extjs-tests/t1$ sencha app refresh Sencha Cmd v4.0.4.84 [INF] [INF] init-plugin: [INF] [INF] cmd-root-plugin.init-properties: [INF] [INF] init-properties: [INF] [INF] init-sencha-command: [INF] [INF] init: [INF] [INF] app-refresh: [INF] [echo] Refreshing app at /work/www/extjs-tests/t1 [INF] [INF] app-refresh-impl: [INF] [INF] -before-init-local: [INF] [INF] -init-local: [INF] [INF] -after-init-local: [INF] [INF] init-local: [INF] [INF] find-cmd-in-path: [INF] [INF] find-cmd-in-environment: [INF] [INF] find-cmd-in-shell: [INF] [INF] init-cmd: [INF] [echo] Using Sencha Cmd from /work/devtools/Sencha/Cmd/4.0.4.84 for /work/www/extjs-tests/t1/build.xml [INF] [INF] -before-init: [INF] [INF] -init: [INF] Initializing Sencha Cmd ant environment [INF] Adding antlib taskdef for com/sencha/command/compass/ant/antlib.xml [INF] [INF] -after-init: [INF] [INF] -before-init-defaults: [INF] [INF] -init-defaults: [INF] [INF] -after-init-defaults: [INF] [INF] -init-compiler: [INF] [INF] init: [INF] [INF] refresh: [INF] [INF] -before-refresh: [INF] [INF] -init: [INF] [INF] -init-compiler: [INF] [INF] -detect-app-build-properties: [INF] Loading app json manifest... [ERR] [ERR] BUILD FAILED [ERR] com.sencha.exceptions.ExBuild: Mixed-Mode x-compile and microload markup is currently unsupported [ERR] [ERR] Total time: 2 seconds [ERR] The following error occurred while executing this line: /work/devtools/Sencha/Cmd/4.0.4.84/plugins/ext/4.2/plugin.xml:386: The following error occurred while executing this line: /work/www/extjs-tests/t1/.sencha/app/build-impl.xml:367: The following error occurred while executing this line: /work/www/extjs-tests/t1/.sencha/app/js-impl.xml:11: com.sencha.exceptions.ExBuild: Mixed-Mode x-compile and microload markup is currently unsupported
→ 上記のように "com.sencha.exceptions.ExBuild: Mixed-Mode x-compile and microload markup is currently unsupported" というエラーが発生してしまいました。
参考:
どうやら、現状は "${app.dir}/index.html" での <x-bootstrap> と、app.json の"js"設定は共存できないようです。もしapp.jsonの"js"設定だけでやろうとしたら、逆に今まで <x-bootstrap> で囲んでいた部分を app.json 側に取り込み、 <x-bootstrap> の方を削除する必要があるようです。
著者のスキル不足のため、現時点で "${app.dir}/index.html" の以下のブロックを、その意味するところと処理の流れを維持したまま、app.json に移行することはできませんでした。
"${app.dir}/index.html":
... <!-- <x-compile> --> <!-- <x-bootstrap> --> <link rel="stylesheet" href="bootstrap.css"> <script src="ext/ext-all-dev.js"></script> <script src="bootstrap.js"></script> <!-- </x-bootstrap> --> <script src="app.js"></script> <!-- </x-compile> --> ...
もしかしたら他のアプローチが取れるかもしれませんし、将来のSencha Cmdのバージョンアップで対応されるかもしれません。
しかし本記事を書いている時点では、著者のスキル不足もあり、このアプローチについてはここで諦めさせて下さい。
余談1: 以下については Sencha Touch についての話なので、ExtJS 4.2側のapp.jsonとは内容が異なり、参考にならない。
余談2: 以下のように、jsを空っぽにしたら "sencha app refresh" は成功したが、bootstrap.jsなど変化なし。もとより、外部JSをロードしているわけでもないのでナンセンス。
"${app.dir}/app.json":
{ "name": "MyApp", "requires": [ ], "js": [ ], "id": "97333d8b-3cca-4f30-aa90-d40a2910b45e" }
上記のようにサードパーティのJavaScriptファイルをSencha Cmdのビルドシステムの枠組みにしたがって取り込むのは困難があったため、別のアプローチとして、Sencha Cmdのビルドシステムに頼らず、手動で組み込む方法を試してみました。
まず "${app.dir}/index.html" を以下のように修正し、CDN経由でjQueryを取り込みます。 Sencha Cmdのビルドシステムが触らない領域に、直接scriptタグを埋め込んでいます。
<!DOCTYPE HTML> <html> <head> <meta charset="UTF-8"> <title>MyApp</title> <!-- この一行を追加 --> <script src="//code.jquery.com/jquery-1.11.0.min.js"></script> <!-- <x-compile> --> <!-- <x-bootstrap> --> <link rel="stylesheet" href="bootstrap.css"> <script src="ext/ext-all-dev.js"></script> <script src="bootstrap.js"></script> <!-- </x-bootstrap> --> <script src="app.js"></script> <!-- </x-compile> --> </head> <body></body> </html>
手動で組み込むscriptタグの位置についてですが、上の例では念のため "<x-compile>" の前に配置しています。
ExtJSのMVCアプリケーションコード内からjQueryを参照する場合、そのコードはビルドによりapp.jsに一本化されます。また、デフォルトではapp.jsの中にExt.applicaton()を呼ぶ出すことによるアプリ起動コードも含まれます。このため、"<x-compile>"の後にサードパーティJSファイルをロードしてしまうと、処理内容によっては、ビルドされたapp.js中からまだロードされていないサードパーティ製ライブラリのコードを呼び出してエラーになる可能性も考えられます。(ボタンクリックなど遅延して発生するイベント中なら大丈夫かもしれませんが・・・)
このため、今回は念のため、先にjQueryをロードするコードを含めてしまっています。
なお、例としてproduction環境のビルドを実行すると、 "${app.dir}/build/production/MyApp/index.html" は以下のようになりました。
<!DOCTYPE HTML> <html> <head> <meta charset="UTF-8"> <title>MyApp</title> <script src="//code.jquery.com/jquery-1.11.0.min.js"></script> <link rel="stylesheet" href="resources/MyApp-all.css"/> <script type="text/javascript" src="app.js"></script> </head> <body></body> </html>
これでExtJSのアプリコードからjQueryを参照できるようになります。
続いて、以下のように "${app.dir}/app.js" を修正し、念のためjQueryの名前空間がExtJS(および将来追加されるかもしれない他のサードパーティ製JavaScriptライブラリ)と衝突しないよう、"$jq" に調整しておきます。
// 追加 jQuery.noConflict(); var $jq = jQuery; Ext.application({ name: 'MyApp', extend: 'MyApp.Application', autoCreateViewport: true });
では、実際にjQueryを "$jq" 経由で使用するコードを書いてみます。
"${app.dir}/app/view/sub/FormDemo.js": "iconCls demo"ボタンのクリックハンドラで、jQueryのeach()関数を使ってコンソールに配列内容をロギングしてみます。
Ext.define("MyApp.view.sub.FormDemo", { extend: 'Ext.form.Panel', xtype: 'sub-formdemo', frame: true, title: 'Form Demo', bodyPadding: 10, autoScroll:true, defaultType: 'textfield', defaults : { anchor: '100%' }, initComponent: function() { /* ... */ this.buttons = [ { text: 'iconCls demo', iconCls: 'myapp-icon-accept', // 追加 handler: function() { var greetings = [ "hello", "good", "morning" ]; $jq.each(greetings, function(k, v) { console.log('greetings[' + k + ']=' + v); }); } }, /* ... */
この状態で "${app.dir}/index.html" にアクセスして"iconCls demo"ボタンをクリックすれば、開発者用ツールのコンソールにjQueryのループにより以下のログが出力されます。
greetings[0]=hello greetings[1]=good greetings[2]=morning
app.jsがminifyされた状態でも、app.js中およびFormDemo.js中からのjQueryのシンボル参照が正しく動作するか、production環境(="sencha app build の引数なしのデフォルト)でビルドしてみます。
msakamoto@dev1-u1204lts-x64:/work/www/extjs-tests/t1$ sencha app build
productionのビルド出力をブラウザからアクセスしてみると、正常に上記のconsole.log()が出力されました。
より複雑なコードをExtJSアプリ側のJavaScriptに埋め込んだ時にどうなるかは分かりませんが、今回のような単純な例であれば、"${app.dir}/index.html" に手動でscriptのロードを組み込むことで、利用できることを確認しました。
上の例ではCDN経由で取り込んでいますが、アプリ自身に持たせようとした場合どこに配置すればよいでしょうか。
今回は例として、"${app.dir}/resources/" の下に配置してみます。この下のディレクトリやファイルは、画像等の静的リソースとしてそのままビルド先の "resources/" に展開されるため、JavaScriptファイルについても特にminifyされずにそのまま配置されます。
まず、サードパーティ製のJavaScriptコードとして、以下の様な簡単なJavaScriptファイルを "${app.dir}/resources/" の下に"js"ディレクトリを作成し、配置します。
"${app.dir}/resources/js/myklass.js":
var MyKlass = function(yourName, myName) { this.you = yourName; this.myself = myName; } MyKlass.prototype.greeting = function(greet) { return greet + " " + this.you + ", I'm " + this.myself + "."; };
"${app.dir}/index.html" で myklass.js をscriptタグでロードします。相対パスで "resources/..." で始めることで、productionおよびtestingのそれぞれでも正常にロードできるようになります。
<!DOCTYPE HTML> <html> <head> <meta charset="UTF-8"> <title>MyApp</title> <script src="//code.jquery.com/jquery-1.11.0.min.js"></script> <!-- 以下の一行を追加 --> <script src="resources/js/myklass.js"></script> <!-- <x-compile> --> ...
FormDemoの方で、"iconCls demo"ボタンがクリックされたら、myklass.jsで定義したクラスを使うコードを追加します。
"${app.dir}/app/view/sub/FormDemo.js":
Ext.define("MyApp.view.sub.FormDemo", { /* ... */ initComponent: function() { /* ... */ this.buttons = [ { text: 'iconCls demo', iconCls: 'myapp-icon-accept', handler: function() { var greetings = [ "hello", "good", "morning" ]; $jq.each(greetings, function(k, v) { console.log('greetings[' + k + ']=' + v); }); // 以下を追加 var myKlass = new MyKlass('Bob', 'Alice'); console.log(myKlass.greeting('Hello')); } }, /* ... */
ビルドします。
msakamoto@dev1-u1204lts-x64:/work/www/extjs-tests/t1$ sencha app build msakamoto@dev1-u1204lts-x64:/work/www/extjs-tests/t1$ sencha app build testing
実際にproduction環境のビルド結果にアクセスして "iconCls demo" ボタンをクリックすると、開発者ツールのコンソールに以下のようなログが出力され、myklass.jsでロードしたコードをExtJSアプリのJavaScript中から正常に使用できていることが分かります。
greetings[0]=hello greetings[1]=good greetings[2]=morning Hello Bob, I'm Alice.
今回は、Sencha Cmdの枠組みの外でサードパーティ製JavaScriptライブラリを取り込んでみました。しかしこのアプローチはSenca Cmdが想定するお作法を一部破っていますので、将来のExtJSおよびSencha Cmdのバージョンアップにより、使えなくなる可能性もあります。
著者のスキル不足でやむを得ずこのようなアプローチを取ってしまいましたが、現実の開発現場ではこのような調査にかけられる時間はさらに厳しいプレッシャーに曝されることとなり、満足な検証をできずに体当たりで組み込むケースもあるかもしれません。
調査と検証にかけられるリソースとコストと、各種環境の制約のバランスを見て、将来の後方互換性とを秤にかけて、出来る範囲でのベターを目指すしか無いのかもしれません。
コメント