Edited at

Titanium + Alloy + TypeScript で、iOS & Android アプリを作る

More than 5 years have passed since last update.


願望

Titanium(Alloy)を使って、iOSとAndroid両方のアプリを同時に作成したい。ついでに、TypeScriptを使いたい!

というわけで、いろいろやってみました。


準備

TypeScriptはインストールしておいてください。

$ npm install -g typescript

Macの場合は、npmの前にsudoが必要かもしれません。


alloy.jmk

参考:

http://k0suke.be/post/36585786781/alloy-with-coffeescript

TitaniumでAlloyプロジェクトを使っているときには、alloy.jmkというファイルを書いてやると、コンパイル前とコンパイル後にしたい処理を記述できるようです。


alloy.jmk

task("pre:compile", function(event,logger) {

// コンパイル前処理
});

task("post:compile",function(event,logger){
// コンパイル後処理
});


というふうに書けば、あとはTitaniumが自動で処理してくれます。


alloy.jmkでTypeScriptをコンパイル出来るように設定する

TypeScriptのソースを読むをいう記事の「まとめ」のところで書いたcompile関数をちょっと変更して、alloy.jmkファイルから呼び出すようにうまく書いてやればtypescriptで記述されたファイルを自動でjavascriptファイルに変換してビルド出来るでしょう。

Titaniumuで、"File" -> "New" -> "File"で、プロジェクトのルートに、"alloy.jmk"というファイルを作成します。

その中の記述は以下を参考に。


gistにもアップしました。

https://gist.github.com/JunSuzukiJapan/6062233



alloy.jmk

function compile(TypeScript, logger, sourcefilename){

var _this = this;

var ByteOrderMark = {};
ByteOrderMark[ByteOrderMark["None"] = 0] = "None";
ByteOrderMark[ByteOrderMark["Utf8"] = 1] = "Utf8";
ByteOrderMark[ByteOrderMark["Utf16BigEndian"] = 2] = "Utf16BigEndian";
ByteOrderMark[ByteOrderMark["Utf16LittleEndian"] = 3] = "Utf16LittleEndian";

var FileInformation = (function () {
function FileInformation(contents, byteOrderMark) {
this._contents = contents;
this._byteOrderMark = byteOrderMark;
}
FileInformation.prototype.contents = function () {
return this._contents;
};

FileInformation.prototype.byteOrderMark = function () {
return this._byteOrderMark;
};
return FileInformation;
})();

var Environment = (function () {
function getNodeEnvironment() {
var _fs = require('fs');
var _path = require('path');
var _module = require('module');

return {
currentDirectory: function () {
return (process).cwd();
},
readFile: function (file) {
var buffer = _fs.readFileSync(file);
switch (buffer[0]) {
case 0xFE:
if (buffer[1] === 0xFF) {
var i = 0;
while ((i + 1) < buffer.length) {
var temp = buffer[i];
buffer[i] = buffer[i + 1];
buffer[i + 1] = temp;
i += 2;
}
return new FileInformation(buffer.toString("ucs2", 2), ByteOrderMark.Utf16BigEndian);
}
break;
case 0xFF:
if (buffer[1] === 0xFE) {
return new FileInformation(buffer.toString("ucs2", 2), ByteOrderMark.Utf16LittleEndian);
}
break;
case 0xEF:
if (buffer[1] === 0xBB) {
return new FileInformation(buffer.toString("utf8", 3), ByteOrderMark.Utf8);
}
}

return new FileInformation(buffer.toString("utf8", 0), ByteOrderMark.None);
},
writeFile: function (path, contents, writeByteOrderMark) {
function mkdirRecursiveSync(path) {
var stats = _fs.statSync(path);
if (stats.isFile()) {
throw "\"" + path + "\" exists but isn't a directory.";
} else if (stats.isDirectory()) {
return;
} else {
mkdirRecursiveSync(_path.dirname(path));
_fs.mkdirSync(path, 0775);
}
}
mkdirRecursiveSync(_path.dirname(path));

if (writeByteOrderMark) {
contents = '\uFEFF' + contents;
}
_fs.writeFileSync(path, contents, "utf8");
},
fileExists: function (path) {
return _fs.existsSync(path);
},
deleteFile: function (path) {
try {
_fs.unlinkSync(path);
} catch (e) {
}
},
directoryExists: function (path) {
return _fs.existsSync(path) && _fs.statSync(path).isDirectory();
},
listFiles: function dir(path, spec, options) {
options = options || {};

function filesInFolder(folder) {
var paths = [];

var files = _fs.readdirSync(folder);
for (var i = 0; i < files.length; i++) {
var stat = _fs.statSync(folder + "\\" + files[i]);
if (options.recursive && stat.isDirectory()) {
paths = paths.concat(filesInFolder(folder + "\\" + files[i]));
} else if (stat.isFile() && (!spec || files[i].match(spec))) {
paths.push(folder + "\\" + files[i]);
}
}

return paths;
}

return filesInFolder(path);
},
arguments: process.argv.slice(2),
standardOut: {
Write: function (str) {
process.stdout.write(str);
},
WriteLine: function (str) {
process.stdout.write(str + '\n');
},
Close: function () {
}
}
};
}
;

return getNodeEnvironment();
})();

var IOUtils;
(function (IOUtils) {
function createDirectoryStructure(ioHost, dirName) {
if (ioHost.directoryExists(dirName)) {
return;
}

var parentDirectory = ioHost.dirName(dirName);
if (parentDirectory != "") {
createDirectoryStructure(ioHost, parentDirectory);
}
ioHost.createDirectory(dirName);
}

function writeFileAndFolderStructure(ioHost, fileName, contents, writeByteOrderMark) {
var path = ioHost.resolvePath(fileName);
var dirName = ioHost.dirName(path);
createDirectoryStructure(ioHost, dirName);
return ioHost.writeFile(path, contents, writeByteOrderMark);
}
IOUtils.writeFileAndFolderStructure = writeFileAndFolderStructure;

function throwIOError(message, error) {
var errorMessage = message;
if (error && error.message) {
errorMessage += (" " + error.message);
}
throw new Error(errorMessage);
}
IOUtils.throwIOError = throwIOError;

var BufferedTextWriter = (function () {
function BufferedTextWriter(writer, capacity) {
if (typeof capacity === "undefined") { capacity = 1024; }
this.writer = writer;
this.capacity = capacity;
this.buffer = "";
}
BufferedTextWriter.prototype.Write = function (str) {
this.buffer += str;
if (this.buffer.length >= this.capacity) {
this.writer.Write(this.buffer);
this.buffer = "";
}
};
BufferedTextWriter.prototype.WriteLine = function (str) {
this.Write(str + '\r\n');
};
BufferedTextWriter.prototype.Close = function () {
this.writer.Write(this.buffer);
this.writer.Close();
this.buffer = null;
};
return BufferedTextWriter;
})();
IOUtils.BufferedTextWriter = BufferedTextWriter;
})(IOUtils || (IOUtils = {}));

var IO = (function () {
function getNodeIO() {
var _fs = require('fs');
var _path = require('path');
var _module = require('module');

return {
readFile: function (file) {
return Environment.readFile(file);
},
writeFile: function (path, contents, writeByteOrderMark) {
Environment.writeFile(path, contents, writeByteOrderMark);
},
deleteFile: function (path) {
try {
_fs.unlinkSync(path);
} catch (e) {
IOUtils.throwIOError("Couldn't delete file '" + path + "'.", e);
}
},
fileExists: function (path) {
return _fs.existsSync(path);
},
dir: function dir(path, spec, options) {
options = options || {};

function filesInFolder(folder) {
var paths = [];

try {
var files = _fs.readdirSync(folder);
for (var i = 0; i < files.length; i++) {
var stat = _fs.statSync(folder + "/" + files[i]);
if (options.recursive && stat.isDirectory()) {
paths = paths.concat(filesInFolder(folder + "/" + files[i]));
} else if (stat.isFile() && (!spec || files[i].match(spec))) {
paths.push(folder + "/" + files[i]);
}
}
} catch (err) {
}

return paths;
}

return filesInFolder(path);
},
createDirectory: function (path) {
try {
if (!this.directoryExists(path)) {
_fs.mkdirSync(path);
}
} catch (e) {
IOUtils.throwIOError("Couldn't create directory '" + path + "'.", e);
}
},
directoryExists: function (path) {
return _fs.existsSync(path) && _fs.statSync(path).isDirectory();
},
resolvePath: function (path) {
return _path.resolve(path);
},
dirName: function (path) {
return _path.dirname(path);
},
findFile: function (rootPath, partialFilePath) {
var path = rootPath + "/" + partialFilePath;

while (true) {
if (_fs.existsSync(path)) {
return { fileInformation: this.readFile(path), path: path };
} else {
var parentPath = _path.resolve(rootPath, "..");

if (rootPath === parentPath) {
return null;
} else {
rootPath = parentPath;
path = _path.resolve(rootPath, partialFilePath);
}
}
}
},
print: function (str) {
process.stdout.write(str);
},
printLine: function (str) {
process.stdout.write(str + '\n');
},
arguments: process.argv.slice(2),
stderr: {
Write: function (str) {
process.stderr.write(str);
},
WriteLine: function (str) {
process.stderr.write(str + '\n');
},
Close: function () {
}
},
stdout: {
Write: function (str) {
process.stdout.write(str);
},
WriteLine: function (str) {
process.stdout.write(str + '\n');
},
Close: function () {
}
},
watchFile: function (fileName, callback) {
var firstRun = true;
var processingChange = false;

var fileChanged = function (curr, prev) {
if (!firstRun) {
if (curr.mtime < prev.mtime) {
return;
}

_fs.unwatchFile(fileName, fileChanged);
if (!processingChange) {
processingChange = true;
callback(fileName);
setTimeout(function () {
processingChange = false;
}, 100);
}
}
firstRun = false;
_fs.watchFile(fileName, { persistent: true, interval: 500 }, fileChanged);
};

fileChanged();
return {
fileName: fileName,
close: function () {
_fs.unwatchFile(fileName, fileChanged);
}
};
},
run: function (source, fileName) {
require.main.fileName = fileName;
require.main.paths = _module._nodeModulePaths(_path.dirname(_fs.realpathSync(fileName)));
require.main._compile(source, fileName);
},
getExecutingFilePath: function () {
return process.mainModule.filename;
},
quit: process.exit
};
}
;

return getNodeIO();
})();

var ErrorReporter = (function() {
function ErrorReporter(ioHost, compilationEnvironment) {
this.ioHost = ioHost;
this.hasErrors = false;
this.setCompilationEnvironment(compilationEnvironment);
}

ErrorReporter.prototype.addDiagnostic = function(diagnostic) {
this.hasErrors = true;

if (diagnostic.fileName()) {
var soruceUnit = this.compilationEnvironment.getSourceUnit(diagnostic.fileName());
if (!soruceUnit) {
soruceUnit = new TypeScript.SourceUnit(diagnostic.fileName(), this.ioHost.readFile(diagnostic.fileName()));
}
var lineMap = new TypeScript.LineMap(soruceUnit.getLineStartPositions(), soruceUnit.getLength());
var lineCol = {
line : -1,
character : -1
};
lineMap.fillLineAndCharacterFromPosition(diagnostic.start(), lineCol);

this.ioHost.stderr.Write(diagnostic.fileName() + "(" + (lineCol.line + 1) + "," + (lineCol.character + 1) + "): ");
}

this.ioHost.stderr.WriteLine(diagnostic.message());
};

ErrorReporter.prototype.setCompilationEnvironment = function(compilationEnvironment) {
this.compilationEnvironment = compilationEnvironment;
};

ErrorReporter.prototype.reset = function() {
this.hasErrors = false;
};
return ErrorReporter;
})();

this.ioHost = IO;
this.hasResolveErrors = false;
this.compilerVersion = "0.9.0.1";
this.printedVersion = false;
this.errorReporter = null;
this.compilationSettings = new TypeScript.CompilationSettings();
this.compilationEnvironment = new TypeScript.CompilationEnvironment(compilationSettings, ioHost);
this.resolvedEnvironment = this.compilationEnvironment;
this.errorReporter = new ErrorReporter(this.ioHost, this.compilationEnvironment);

var compiler = new TypeScript.TypeScriptCompiler(logger, this.compilationSettings, null);

//
// ソースファイルの設定
//
var fileInformation = ioHost.readFile(sourcefilename);
var code = new TypeScript.SourceUnit(sourcefilename, fileInformation);

compiler.addSourceUnit(
code.path,
TypeScript.ScriptSnapshot.fromString(code.fileInformation.contents()),
code.fileInformation.byteOrderMark(),
0,
false,
code.referencedFiles);

//
// シンタックスエラーのチェック?
//
var syntacticDiagnostics = compiler.getSyntacticDiagnostics(code.path);
compiler.reportDiagnostics(syntacticDiagnostics, this.errorReporter);

var anySyntacticErrors = false;
var anySemanticErrors = false;

if (syntacticDiagnostics.length > 0) {
anySyntacticErrors = true; // シンタックスエラー有り
}

if (anySyntacticErrors) {
return true; // エラーで終了
}

//
// 型チェック
//
compiler.pullTypeCheck();

//
// コンパイル結果の書出し
//

// ファイル書出し用オブジェクト
var emitterIOHost = {
writeFile: function (fileName, contents, writeByteOrderMark) {
return IOUtils.writeFileAndFolderStructure(_this.ioHost, fileName, contents, writeByteOrderMark);
},
directoryExists: this.ioHost.directoryExists,
fileExists: this.ioHost.fileExists,
resolvePath: this.ioHost.resolvePath
};

// よくわからない何か(ソースファイル名と出力ファイル名がどうしたこうした?)
var mapInputToOutput = function (inputFile, outputFile) {
_this.resolvedEnvironment.inputFileNameToOutputFileName.addOrUpdate(inputFile, outputFile);
};

// 発行(ファイルに書出し?)
var emitDiagnostics = compiler.emitAll(emitterIOHost, mapInputToOutput);
compiler.reportDiagnostics(emitDiagnostics, this.errorReporter);
if (emitDiagnostics.length > 0) {
return true; // エラーで終了
}

if (anySemanticErrors) {
return true; // エラーで終了
}

// 発行(ファイルに書出し?) その2
var emitDeclarationsDiagnostics = compiler.emitAllDeclarations();
compiler.reportDiagnostics(emitDeclarationsDiagnostics, this.errorReporter);
if (emitDeclarationsDiagnostics.length > 0) {
return true; // エラーで終了
}

return false; // 無事コンパイル完了
}

// コンパイル前処理
task("pre:compile", function(event,logger) {
var wrench = require("wrench"),
fs = require("fs"),
path = require("path");

var code = [
fs.readFileSync("/usr/local/lib/node_modules/typescript/bin/typescript.js"), // <-各自の環境に合わせて設定する。
"module.exports = TypeScript;"
].join("");
fs.writeFileSync(process.env.TMPDIR + "typescript.js", code);
var TypeScript = require(process.env.TMPDIR + "typescript.js");
fs.unlinkSync(process.env.TMPDIR + "typescript.js");

logger.information = logger.info;
logger.warning = logger.warn;
logger.fatal = logger.info;
logger.log = logger.info;

event.alloyConfig.tsc = [];

wrench.readdirSyncRecursive(event.dir.home).forEach(function(target){
if (target.match(/\.ts$/) && ! target.match(/\.d\.ts$/)) { // .d.tsファイルはコンパイルしない。
var filename = path.join(event.dir.home + "/" + target);
compile(TypeScript, logger, filename);
event.alloyConfig.tsc.push(target.replace(/.ts$/, ".js"));
}
});

});

// コンパイル後処理
task("post:compile",function(event,logger){
var fs = require("fs");

event.alloyConfig.tsc.forEach(function(target){
fs.unlinkSync(event.dir.home + "/" + target);
});
});


あとは、TypeScriptでcontrollerなりmodelなりを書いて、Titaniumの"Run"コマンド、あるいは"Debug"コマンドを実行すればオッケーです。

TypeScriptのコンパイル時にエラーが表示されることがありますが、エラーがあってもJavaScriptに変換されていれば、そのまま処理が進みます。


wrenchまわりでひっかかる場合は、ターミナルで

npm install wrench

してやる必要があるかも…。



追記

2013/07/29

 ・*.d.tsファイルはコンパイルしないようにした(node.d.tsなどをコンパイルしたくないから)。

 ・意味解析部分を削除(エラーになることがあるので…)。