Information
▼titanium-mobile-doc-ja(開店休業中です…ごめんなさい)
▼好評発売中:Titanium Mobileで開発するiPhone/Androidアプリ
※Titanium Studio等、刊行後の新機能について補完するPDFを公開しています
→(http://bit.ly/tsinstall)
Titanium Mobileリンク集
2011年12月30日
CommonJS Modules in Titanium
この記事はAppcelerator公式Wikiのドキュメント(Dec 20, 2011更新版)に基づき、和訳±αしたものです。
https://wiki.appcelerator.org/display/guides/CommonJS+Modules+in+Titanium
(補足:Titanium Mobile SDK 1.7.x以前とは状況が異なる部分もありますので、1.8.x前提ということでご覧ください)
概要
Titnianium Mobileは利用者がJavaScriptのコードを組み立てる方法としてCommonJSモジュール仕様を採用しようとしつつあります。
しかし、CommonJSモジュールは"標準的な"仕様である一方、複数のテクノロジ間で実装の違いがあります。
そのため、Titanium Mobile 1.8(やそれ以降の)の実装において何がサポートされ、何がサポートされていないのかを明示します。
定義
| モジュール(Module) | Titanium Mobileアプリケーションで使用されるあらゆるCommonJSに準拠した部品。JavaScriptのスクリプトファイルをアプリケーションに取り込む形、もしくはObjective-CやJavaにより拡張されるモジュールをJavaScript APIとして提供する形のいずれも指す。 |
| リソース(Resources) | ユーザソースコードが置いてあるTitaniumアプリケーションのResourcesディレクトリ |
| exports | モジュール内に存在する複数の変数に対して外部に公開されるインタフェイスを付与するすることができる |
| module.exports | モジュール内に存在する単一のオブジェクトに対して外部に公開されるインタフェイスを付与するすることができる |
CommonJSモジュールの仕様実装
TiにおけるCommonJSモジュールの仕様の実装はnode.jsのものに基づいています。
我々はnode.jsの実装の直接のクローンとなることを考慮している訳ではありませんが、Ti・node.jsのいずれの環境でもモジュールの再利用ができるようnode.jsの規則に賛同するようにしています。
単純な使い方
Titanium内でモジュールを使うために、requireファンクションを用いなくてはなりません。
これによりJavaScriptの実行コンテクストごとのグローバルスコープに組み込まれるようになります。
var myModule = require('MyModule');
Titanium Mobileのネイティブモジュール、もしくはTitanium MobileアプリケーションのResourcesフォルダ内にあるJavaScriptで記述されたモジュールのいずれかが名前解決できる必要があります。この例でいえばMyModuleと言う名前のObj-C・Javaで書かれたモジュールがあるか、MyModule.jsというスクリプトファイルが存在する必要があるわけです。
requireファンクションは(モジュールへアクセスするインタフェースとしての)プロパティや関数を伴う一般的なJavaScriptオブジェクトを返されます。
もし、sayHelloという名前の関数により、コンソール上にWelcomeメッセージと名前を表示する機能をアプリケーションに組み込むには次の通り記述すればいいでしょう。
var myModule = require('MyModule'); myModule.sayHello('Kevin'); //console output is "Hello Kevin!"
ネイティブモジュールとJavaScriptなモジュール
requireファンクションを実行したとき、Titaniumはまずネイティブモジュールをロード可否を判断した後、Resourcesフォルダ内のJavaScriptモジュールで判定を行います。
ちなみにネイティブモジュールの配置と処理方法についてはこの記事の範囲を超えていますが(この記事が書かれた時点では)ネイティブモジュールは開発者のマシン上のグローバルエリア(OSXでは/Library/Application Support/Titanium〜もしくは~/Library…)もしくは各Titanium Mobileプロジェクトディレクトリの直下にあるmodulesディレクトリ内部に展開されています。
ネイティブモジュール
Obj-CやJavaで記述されたバイナリであるネイティブモジュールはユニークな文字列で判別され、使用するTitanium mobileプロジェクトのtiapp.xml内に使用宣言の記述を行います。
<modules> <module version="1.0">ti.paypal</module> </modules>
これに対応するTitanium Mobileアプリケーションのコードは次のようになります。
var paypal = require('ti.paypal');
Titaniumは"ti.paypal"という名前のネイティブモジュールをロードしようとしますが、その際にResourcesフォルダ内を探したり、そこからロードしようとすることはありません。前述のとおり、ネイティブモジュールの在処(mobileSDKの所在の並びもしくはプロジェクト直下のmodulesフォルダ)に"ti.paypal"がない場合、Resourcesフォルダ内にあるJavaScriptモジュールを探しに行きます。
JavaScriptモジュール
ResourcesフォルダからロードされるJavaScriptのファイルもモジュールとなります。
Titanium MobileのJavaScriptモジュールは単一のJavaScriptファイルで構成されます。
モジュールがロードされたときにJavaScriptのファイルは評価され、モジュールの公開インタフェイスによってスコープに影響が与えられます。
JavaScriptモジュールのパス解決問題
JavaScriptモジュールをResourcesフォルダ内で走査するとき、JavaScriptモジュールから拡張子".js"を取り除いたパスを記述する必要があります。
指定した文字列の先頭が相対パスを表す ./ や ../ といったモノの場合、Resourcesフォルダ内を相対的に参照しようとします。例えば、プロジェクトフォルダ/Resources/app/lib/myModule.jsファイルを設置した場合、次のように記述する事でロードされます。
var moduleValueName = require('app/lib/myModule'); ||< 同じように、/で始まるパスを指定した場合、Resourcesディレクトリからの相対的な参照を行い((実質的な絶対パス指定とも言えます))、次のように記述してロードします。 >|javascript| var moduleValueName = require('/app/lib/myModule'); ||< 例えば以下のようなファイルがあるとき、 -Resources/app/ui/SomeCustomView.js -Resources/app/ui/widgets/SomeOtherCustomView.js -Resources/app/lib/myModule.js SomeCustomView.jsから相対パスを指定するとなると次のようになります。 >|javascript| //SomeCustomView.js var myModule = require('../lib/myModule'); var SomeOtherCustomView = require('./widgets/SomeOtherCustomView');
JavaScriptモジュール構成
CommonJSモジュール仕様にあるように、JavaScriptモジュールファイル内に"exports"という特別な変数があり、これにより、モジュール外部とやり取りを行えるインターフェイスを持つ事ができます。
exports.sayHello = function(name) { Ti.API.info('Hello '+name+'!'); }; exports.version = 1.4; exports.author = 'Don Thorp';
一方、モジュール制作者が設計し選んだオブジェクトのみをモジュール外部に提供したい場合には、非標準(といってもnode.jsで使用されており、共通的に使われている)の拡張方法があります。
モジュールをrequireした時に返したい値をモジュール内で"module.exports"オブジェクトに割り当てる方法です。一般的にオブジェクトのコンストラクタとして機能する機能に使用されます。典型的な例をあげると次のようになります。
// /Resources/Person.js function Person(firstName,lastName) { this.firstName = firstName; this.lastName = lastName; } Person.prototype.fullName = function() { return this.firstName+' '+this.lastName; }; module.exports = Person;
// /Resources/app.js var Person = require('Person'); var don = new Person('Don','Thorp'); var donsName = don.fullName(); // "Don Thorp"
アンチパターンとサポートされていない挙動
次のように直接割り当てると正しく挙動しません。
function Person(firstName,lastName) { this.firstName = firstName; this.lastName = lastName; } exports = Person; //THIS IS NOT OK AND PROBABLY WON'T WORK
同様に、module.exportsとexportsを混ぜて使ってはいけません。
function Person(firstName,lastName) { this.firstName = firstName; this.lastName = lastName; } module.exports = Person; //This is okay, but... exports.foo = 'bar'; //This is discouraged - use one or the other
また、次のようなmodule.exportsにプロパティを割り当てての定義も推奨されません。
exports.foo = 'bar'; module.exports.fooToo = 'something else'; // Not good style - use one or the other.
キャッシュ機構
JavaScriptモジュールがロードされた時、requireによって返されるオブジェクトはTitaniumによってキャッシュされ、複数回ファイルの中身を評価する事なく、利用者に提供されます。もしモジュールのコードが何度も評価されたいのであれば、そのように挙動するモジュールを作成する必要があります。しかし、それは妥当な方法とは言いがたいです。
セキュリティ(とSandbox機能)
CommonJSモジュール仕様にあるように、すべてのモジュールはプライベートの変数スコープを持ちます。モジュールファイル内で宣言された変数はプライベートで、外部に公開する必要があるものは、exportsオブジェクトに追加する必要があります。
Sandboxの詳細については、CommonJSのモジュールの仕様を参照してください。
ステートフルなモジュール
Titaniumのすべてのモジュールは一度作成されると、その後、requireされるたびに参照渡しされます。このため、モジュール自体がsingletonオブジェクトの状態変数である可能性があります。
ちなみに追加の実行コンテキストが作成されるとき、コンテキストごとにモジュールは作成*1されるので、新たなモジュールオブジェクトが評価され、作成されます。
app.js
var stateful = require('statefulModule'); var score = require('scoreModule'); var window = Ti.UI.createWindow({ backgroundColor:'white', fullscreen:false, title:'Click window to score' }); window.addEventListener('click', function() { try { Ti.API.info("The latest score is " + score.latestScore()); Ti.API.info("Adding " + stateful.getPointStep() + " points to score..."); score.pointsWon(); Ti.API.info("The latest score is " + score.latestScore()); Ti.API.info("Setting points per win to 10"); stateful.setPointStep(10); Ti.API.info("Adding " + stateful.getPointStep() + " points to score..."); score.pointsWon(); Ti.API.info("The latest score is " + score.latestScore()); Ti.API.info("---------- Info ----------"); Ti.API.info("stateful.getPointStep() returns: " + stateful.getPointStep()); Ti.API.info("stateful.stepVal value is: " + stateful.stepVal); // will always return default of 5 Ti.API.info("**************************"); } catch(e) { alert("An error has occurred: " + e); } }); window.open();
scoreModule.js
var appStateful = require('statefulModule'); // a reference to the "stateful" variable in app.js that contains the stateful module var _score = 0; // default exports.pointsWon = function() { _score += appStateful.getPointStep(); }; exports.pointsLost = function() { _score -= appStateful.getPointStep(); }; exports.latestScore = function() { return _score; };
statefulModule.js
var _stepVal = 5; // default exports.setPointStep = function(value) { _stepVal = value; }; exports.getPointStep = function() { return _stepVal; }; exports.stepVal = _stepVal;
グローバル変数
アプリケーション内のすべてのモジュールにわたって共有されるグローバル変数は存在してはいけません。
モジュール、またはモジュールによって公開されるオブジェクトであっても、作成時・初期化時に引き渡される必要があります。
JavaScriptモジュールの実装&使用例
ユーティリティライブラリ
// logger.js exports.info = function(str) { Titanium.API.info(new Date()+': '+str); }; exports.debug = function(str) { Titanium.API.debug(new Date()+': '+str); };
// app.js var logger = require('logger'); logger.info('some log statement I wanted with a timestamp');
関連する機能のパッケージ
// geo.js function Point(x,y) { this.x = x; this.y = y; } function Line(start,end) { this.start = start; this.end = end; } Line.prototype.slope = function() { return (this.end.y - this.start.y) / (this.end.x - this.start.x); }; Line.prototype.yIntercept = function() { return this.start.y - (this.slope()*this.start.x); }; //create public interface exports.Point = Point; exports.Line = Line;
// app.js var geo = require('lib/geo'); var startPoint = new geo.Point(1,-5); var endPoint = new geo.Point(10,2); var line = new geo.Line(startPoint,endPoint); var slopeValue = line.slope();
インスタンス化可能なオブジェクト
// Person.js function Person(firstName,lastName) { this.firstName = firstName; this.lastName = lastName; } Person.prototype.fullName = function() { return this.firstName+' '+this.lastName; }; module.exports = Person;
// app.js var Person = require('Person'); var don = new Person('Don','Thorp'); var donsName = don.fullName(); // "Don Thorp"
- 128 http://www.google.co.jp/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&ved=0CCYQFjAA&url=http://d.hatena.ne.jp/donayama/20111230/commonjs_modules_in_titanium&ei=GXZGT7esIufzmAXdueShDg&usg=AFQjCNFGp38IN_K-QmIxghX1RmT3bbAKvQ
- 82 http://www.google.co.jp/url?sa=t&rct=j&q=&esrc=s&source=web&cd=2&ved=0CC0QFjAB&url=http://d.hatena.ne.jp/donayama/20111230/commonjs_modules_in_titanium&ei=hZIrT9GxKYf0mAXF1bD8Dw&usg=AFQjCNFGp38IN_K-QmIxghX1RmT3bbAKvQ&sig2=swaoIIq7zGA7kBubgmumK
- 64 http://d.hatena.ne.jp/towerofl/20120105/1325718223
- 49 http://www.google.co.jp/url?sa=t&rct=j&q=commonjsモジュール&source=web&cd=2&ved=0CCoQFjAB&url=http://d.hatena.ne.jp/donayama/20111230/commonjs_modules_in_titanium&ei=SoQPT9bKAs-4iAfDgpw5&usg=AFQjCNFGp38IN_K-Q
- 35 http://www.google.co.jp/url?sa=t&rct=j&q=titanium module.exports&source=web&cd=1&ved=0CCIQFjAA&url=http://d.hatena.ne.jp/donayama/20111230/commonjs_modules_in_titanium&ei=zJIJT9SQIoSRiQf2op2ICQ&usg=AFQjCNFGp38IN_K-QmIxghX1RmT3bbAKvQ&sig2=z4f
- 26 http://www.google.co.jp/url?sa=t&rct=j&q=&esrc=s&source=web&cd=3&cts=1331011395714&ved=0CDgQFjAC&url=http://d.hatena.ne.jp/donayama/20111230/commonjs_modules_in_titanium&ei=ZJ5VT6S3OIWJmQWLneHjCQ&usg=AFQjCNFGp38IN_K-QmIxghX1RmT3bbAKvQ&sig2=tSU
- 24 http://www.google.co.jp/url?sa=t&rct=j&q=titanium commonjs&source=web&cd=4&sqi=2&ved=0CDUQFjAD&url=http://d.hatena.ne.jp/donayama/20111230/commonjs_modules_in_titanium&ei=KOcBT8fGO-nXmAXutICNAg&usg=AFQjCNFGp38IN_K-QmIxghX1RmT3bbAKvQ&cad=rja
- 19 http://bowz.info/3713
- 18 http://www.google.co.jp/url?sa=t&rct=j&q=commonjs titanium&source=web&cd=1&ved=0CB4QFjAA&url=http://d.hatena.ne.jp/donayama/20111230/commonjs_modules_in_titanium&ei=HxkBT4XsEKeUiAeJ8pimAQ&usg=AFQjCNFGp38IN_K-QmIxghX1RmT3bbAKvQ&sig2=3wbQmFwnh
- 16 https://www.google.co.jp/


