読者です 読者をやめる 読者になる 読者になる

リア充爆発日記

You don't even know what ria-ju really is.

TitaniumでiOS,AndroidアプリをCoffeeScript+TDDで爆速開発する環境構築手順メモ

さて、Titaniumによる開発についてこれまで個別にいろいろ調べてきたけど、一気に実際にアプリを開発するための環境についてまとめてみる。

目指す環境

これまでTitanium Studioと標準的エミュレータでサンプルアプリの開発+実行を行ってきたけど、やっぱりEclipseベースのIDEはストレスが高い。また特にAndroidに顕著だけど、標準のエミュレータは重すぎる。これではコーディング外のところで多く時間をとってしまう。
別のところでは、Rails開発をやってきているのでJavascriptCoffeeScriptで書きたいし、なによりTDDできないと死んでしまう。

このあたりを解決するため、ここ数日調査をしたところ

  • IDEの問題
    • Titanium Command-Line Interfaceを利用したIDEに頼らない環境
  • エミュレータ重すぎの問題
    • TiShadowによる爆速デプロイ+実行
  • CoffeeScript+TDD
    • jmk+TiShadowを組み合わせたTDD環境

という構成がベストと思えたのでこの環境を作っていく。

前提

  • Android,iPhoneアプリ開発
  • OSX
  • Android SDKはインストール済み
  • Xcodeもインストール済み
  • node.js,npmインストール済み
  • Titanium3.x + Alloy1.2.xインストール済み
  • JasmineによるTDD環境
  • エディタ(IDE)は自由(Titanium-cli使用)

プロジェクトの作成

まずはTitaniumプロジェクトを作成する。
http://docs.appcelerator.com/titanium/3.0/#!/guide/Titanium_Command-Line_Interface_Reference
http://www.absolute-keitarou.net/blog/?p=414

ログイン

$ titanium login
Titanium Command-Line Interface, CLI version 3.1.2, Titanium SDK version 3.1.2.GA
Copyright (c) 2012-2013, Appcelerator, Inc. All Rights Reserved.

Please report bugs to http://jira.appcelerator.org/

Username: ria10@example.com
Password:

Logged in successfully

プロジェクト作成

$ titanium create
Titanium Command-Line Interface, CLI version 3.1.2, Titanium SDK version 3.1.2.GA
Copyright (c) 2012-2013, Appcelerator, Inc. All Rights Reserved.

Please report bugs to http://jira.appcelerator.org/

Target platforms: (android,blackberry,ios,ipad,iphone,mobileweb,tizen) android,iphone
App ID: com.example.ria10.titanium
Project name: sample
Directory to place project: .

[INFO] Creating Titanium Mobile application project
~snip~
[INFO] Project 'sample' created successfully in 80ms

対話式なので、順に応えていく。AppIDなんかは、たぶんあとで変えられるんじゃないかな。
でProject nameで指定したディレクトリにプロジェクトのひな形一式が生成される。

$ ll
total 0
drwxr-xr-x 92 ria10 staff 3128 9 2 13:32 ..
drwxr-xr-x 8 ria10 staff 272 9 2 13:37 sample
drwxr-xr-x 3 ria10 staff 102 9 2 13:37 .

$ cd sample
$ ll
total 56
-rw-r--r-- 1 ria10 staff 1766 9 2 13:37 tiapp.xml
-rw-r--r-- 1 ria10 staff 221 9 2 13:37 manifest
drwxr-xr-x 7 ria10 staff 238 9 2 13:37 Resources
-rw-r--r-- 1 ria10 staff 551 9 2 13:37 README
-rw-r--r-- 1 ria10 staff 11491 9 2 13:37 LICENSE
-rw-r--r-- 1 ria10 staff 33 9 2 13:37 .gitignore
drwxr-xr-x 3 ria10 staff 102 9 2 13:37 ..
drwxr-xr-x 8 ria10 staff 272 9 2 13:37 .

Alloy適用

$ alloy new
.__ .__
_____ | | | | ____ ___.__.
\__ \ | | | | / _ < | |
/ __ \| |_| |_( <_> )___ |
(____ /____/____/\____// ____|
\/ \/
Alloy by Appcelerator. The MVC app framework for Titanium.

[INFO] Deployed ti.alloy plugin to plugins/ti.alloy/plugin.py
[INFO] Deployed ti.alloy hook to plugins/ti.alloy/hooks/alloy.js
[INFO] Installed "ti.alloy" plugin to tiapp.xml
[INFO] Generated new project at: app

$ ll
total 56
-rw-r--r-- 1 ria10 staff 221 9 2 13:37 manifest
drwxr-xr-x 7 ria10 staff 238 9 2 13:37 Resources
-rw-r--r-- 1 ria10 staff 551 9 2 13:37 README
-rw-r--r-- 1 ria10 staff 11491 9 2 13:37 LICENSE
drwxr-xr-x 3 ria10 staff 102 9 2 13:37 ..
-rw-r--r-- 1 ria10 staff 1820 9 2 13:44 tiapp.xml
drwxr-xr-x 3 ria10 staff 102 9 2 13:44 plugins
drwxr-xr-x 10 ria10 staff 340 9 2 13:44 app
-rw-r--r-- 1 ria10 staff 48 9 2 13:44 .gitignore
drwxr-xr-x 10 ria10 staff 340 9 2 13:44 .
$ ll app/
total 24
drwxr-xr-x 3 ria10 staff 102 9 2 13:44 views
drwxr-xr-x 3 ria10 staff 102 9 2 13:44 styles
drwxr-xr-x 2 ria10 staff 68 9 2 13:44 models
drwxr-xr-x 3 ria10 staff 102 9 2 13:44 controllers
-rw-r--r-- 1 ria10 staff 183 9 2 13:44 config.json
drwxr-xr-x 7 ria10 staff 238 9 2 13:44 assets
-rw-r--r-- 1 ria10 staff 529 9 2 13:44 alloy.js
-rw-r--r-- 1 ria10 staff 1540 9 2 13:44 README
drwxr-xr-x 10 ria10 staff 340 9 2 13:44 ..
drwxr-xr-x 10 ria10 staff 340 9 2 13:44 .

appディレクトリができて、その下に一式Railsチックなファイル郡が生成される。

エミュレータで確認

この時点でエミュレータHello Worldアプリが起動できるはずなので確認する。

$ titanium build -p ios

git登録

$ git init
その他リモートリポジトリにpushまで。

この時点でResourceディレクトリ以下はgitで管理しなくていいので.gitignoreに追加しておく。

CoffeeScript導入

これでHello Worldが動作するAlloyアプリができあがったので、これにCoffeeScriptを導入する。

このあたり、ちょっとややこしくなるんだけど、Titanium+AlloyプロジェクトでMVC系のプログラムをCoffeeScriptで開発する場合、最終的なビルド対象のソースまでは
coffeeファイル -- [coffeeのコンパイラ] --> appディレクトリ配下のjsファイル -- [alloyコンパイル] --> Resourcesディレクト配下のjsファイル
という流れになる。

実機転送でもTiShadowでのテストでも、Resourcesディレクトリのファイルが対象になるわけだからappディレクトリ配下のファイルは本質的には要らないはず。
問題は、apployのコンパイル処理がコピー以上のことをやっているかどうか、ということになる。
ということで、実際にAlloyコンパイル処理をやってみてログを見てみたんだけど、やっぱコピー以外にもいろいろあるので、今回はcoffee->Resourcesディレクトリまで一気、はあきらめて、二段階の手順を自動的に踏ませる方向でやることにした。

参考:http://www.absolute-keitarou.net/blog/?p=426

CoffeeScriptインストール

$ npm install -g coffee-script

jmkファイル作成と編集

ビルドコンフィグファイルのjmkを作成

$ alloy generate jmk
.__ .__
_____ | | | | ____ ___.__.
\__ \ | | | | / _ < | |
/ __ \| |_| |_( <_> )___ |
(____ /____/____/\____// ____|
\/ \/
Alloy by Appcelerator. The MVC app framework for Titanium.

[INFO] Generated "alloy.jmk" compiler hooks file.

編集

task("pre:compile", function(event,logger) {
    var wrench = require("wrench"),
        fs = require("fs"),
        path = require("path"),
        coffee = require("coffee-script");

    event.alloyConfig.coffee = [];

    wrench.readdirSyncRecursive(event.dir.home).forEach(function(target){
        if (target.match(/\.coffee$/)) {
            event.alloyConfig.coffee.push(target.replace(/\.coffee$/, ".js"));
            fs.writeFileSync(
                path.join(event.dir.home,target.replace(/\.coffee$/, ".js")),
                coffee.compile(fs.readFileSync(path.join(event.dir.home + "/" + target)).toString(), { bare: true }));
        }
    });
});

task("post:compile",function(event,logger){
    var fs = require("fs");

    event.alloyConfig.coffee.forEach(function(target){
        fs.unlinkSync(event.dir.home + "/" + target);
    });
});
jsをcoffeeに変更

app/controllers/index.jsを.coffeeに変更し中身を以下のように修正

doClick = ->
  alert($.label.text);

$.index.open()
実行して試してみる。

$ alloy compile --config platform=ios,android
~snip~
[TRACE] Benchmarking
[TRACE] ------------
[TRACE] [0.5202s] TOTAL
[INFO]
[INFO] Alloy compiled in 0.5202s
$ titanium build -p ios
~snip~

オーケーオーケー。

Coffee導入編はとりあえずここまでということで。もし、欲しくなったら.coffeeを修正するごとに自動的にalloy compileを走らせる、というのにトライするかも。

TDD環境の導入

エミュレータを真正面から使うと、修正のたびにけっこうな時間を待たされるので、それを解決する。
ここでのポイントはTiShadowなんだけど、これほんと意味分かんないけどすごい。
参考:https://speakerdeck.com/astronaughts/rapid-prototyping-by-tishadow

TiShadowとは

自分でたてるTestFlightみたいなもので、

  • TiShadowサーバを立てる
  • TiShadowアプリを実行対象のデバイスにインストール
  • TiShadowアプリからTiShadowサーバに接続をする

という手順をとると、コマンド一発でサーバを介して各デバイスにインストールされたTiShadowアプリ内で、自分のアプリを実行&テストできたりするというものです。

何を言っているのかわかりづらいと思うけど、やればわかる。

TiShadowのインストール

tishadowモジュールをインストールしたのち、tishadowapp(名前は何でもいい)という、エミュレータとか実機などのデバイスにインストールするアプリプロジェクトを自動生成し、それをデバッグや実行したいデバイスにインストールする。

$ npm install tishadow

$ cd ..
$ mkdir ./tishadowapp && tishadow app -d ./tishadowapp
$ cd tishadowapp/
$ ti build -p ios

tishadowappはどこに作ってもいいけど、本来のアプリと同列のところに作った。参考サイトのスライドにも画像が載っているけど、インストールするとこんな感じになる。

TiShadowサーバ起動

何のことだかわからなくていいのだ。とにかくこのプロセスが必要なのだ。

$ tishadow server

どこで実行してもよくて、このターミナルは実行の間、そっとしておこう。&つけてバックグラウンドで走らせることもできるけど、管理がめんどくさいのでターミナルひとつ潰したほうがいいと思われ。
で、この状態でhttp://localhost:3000にアクセスできるか確認する。
railsも同時開発してるから困るぜ!という人は-pでポート指定できるのでその辺はテキトウに。
https://github.com/dbankier/TiShadow

TiShadowアプリからサーバに接続

さっき各デバイスにインストールしたアプリからIPアドレス指定でサーバに接続する。
iOSエミュレータとサーバが同じマシンで起動しているとき、アプリに指定するIPは127.0.0.1でもいけるけど、Androidエミュレータは別デバイス扱いになるので注意。

アプリの実行

$ tishadow run

ヤバイでしょう。意味分かんない。
ちなみにrunはResourceディレクトリのリソースをもとに各デバイスにデプロイする。ので、Alloyアプリの場合、修正したものを反映するときは以下のコマンドにする必要がある。

$ alloy compile --config platform=ios,android && tishadow run

これでヤバイ実行環境はできた。
ちなみに他の日本語情報あたりたかったら「TiShadow 爆速」でググるといいとおもいます。

TDD環境導入

CoffeeScriptの導入とほとんど同じ。TiShadowはJasminサポートしているので、あとはspecを書く環境を作るのみ。もちろんspecもCoffeScript前提じゃ。

spec用ディレクトリ作成

プロジェクトルート直下に以下のディレクトリを作る

  • spec
  • spec_coffee
    • ここにテストコードを書く。
jmkファイルに追記
task("pre:compile", function(event,logger) {
~snip~
    // compile CoffeeScript in spec_coffee directory
    var specCoffeeDir = event.dir.project + "/spec_coffee";
    var specDir = event.dir.project + "/spec";
    wrench.mkdirSyncRecursive(specDir, 0755);
    var rmdirRecursive = function(dirPath) { // cleanup directory
        fs.readdirSync(dirPath).forEach(function(target){
            var targetPath = path.join(dirPath, target);
            if (fs.statSync(targetPath).isDirectory()) {
                rmdirRecursive(targetPath);
                fs.rmdirSync(targetPath);
            } else {
                fs.unlinkSync(targetPath);
            }
        });
    };
    rmdirRecursive(specDir);
    wrench.readdirSyncRecursive(specCoffeeDir).forEach(function(target){
        var targetPath = path.join(specCoffeeDir,target);
        if (fs.statSync(targetPath).isFile()) {
            var dir = path.dirname(path.join(specDir,target));
            var out_file, out_data;
            if (!fs.existsSync(dir)) {
                wrench.mkdirSyncRecursive(dir, 0755);
            }
            if (target.match(/\.coffee$/)) {
                out_file = path.join(specDir,target.replace(/\.coffee$/, ".js"));
                out_data = coffee.compile(fs.readFileSync(path.join(specCoffeeDir + "/" + target)).toString(), { bare: true });
            } else {
                out_file = path.join(specDir,target);
                out_data = fs.readFileSync(path.join(specCoffeeDir + "/" + target));
            }
            fs.writeFileSync(out_file, out_data);
        }
    });
テストコードを書く

spec_coffee/index_spec.coffee

Alloy = require('alloy')
$ = Alloy.createController('index')

describe 'index controller', ->
  beforeEach () ->

  describe 'display', ->
    it "label", () ->
      expect($.__views.label.text).toEqual 'Hello, World'

beforeEachいらんね。。

テスト実行

tishadowの実行環境が整っている状態で

$ alloy compile --config platform=ios,android && tishadow spec
~snip~
[TEST] [iphone, 6.1, 192.168.1.3] Runner Started
[TEST] [iphone, 6.1, 192.168.1.3]
[TEST] [iphone, 6.1, 192.168.1.3] display
[TEST] [iphone, 6.1, 192.168.1.3] √ label
[PASS] [iphone, 6.1, 192.168.1.3] √ 1 test(s) completed.
[TEST] [iphone, 6.1, 192.168.1.3]
[PASS] [iphone, 6.1, 192.168.1.3] √ 1 spec(s) completed.


うひょー!
まだ、超シンプルなテストしか書いてないからアレだけど、でもウレシイ。

Titanium Mobile iPhone/Androidアプリ開発入門―JavaScriptだけで作る

Titanium Mobile iPhone/Androidアプリ開発入門―JavaScriptだけで作る

JavaScriptとTitaniumではじめる iPhone/Androidアプリプログラミング 【Titanium Mobile SDK 2.1 & Titanium Studio 2.1 対応】

JavaScriptとTitaniumではじめる iPhone/Androidアプリプログラミング 【Titanium Mobile SDK 2.1 & Titanium Studio 2.1 対応】