Titanium, Alloy, and ES6

ES6 is not only about syntactic sugars in your code, It also includes a lot of interesting features and flow control tools like destructuring and promises. It makes functional programming even more pleasant in JavaScript and thus we ought to use it with Alloy.There is already an existing solution that can be used for Titanium classic apps: ti.babel. It is using, as I will do, the power of Babel to transcompile all Titanium ES6 sources to a compatible ES5 version for the app. So, let’s dive into it and see how to make the thing works with Alloy.

Prerequisites

First of all, we’ll need some artifacts but do not worry, npm is as usual our devoted friend.

npm install -g babel browserify

For the following, I’ve been working with Alloy 1.7.x and Titanium 5.0.x; nevertheless it should work fine with any version. Also, you’ll need Node.js, at least version 0.12.x (in order to do synchronous exec)

Create destination folder

Assuming we’re now in a Titanium root project folder (the one that has the tiapp.xml file). In order to compile without disturbing the existing sources, we will ask Babel to transcompile the project app/ folder into a new one, let’s call it .app/so it is hidden and nobody cares about what is happening with this one. In that folder, add three folders controllers andlib/babel. Then, put a blank file index.js within the folder controllers (this is the minimum required by Alloy to start the compilation).

Then, we’ll need to use the lib Polyfill packaged with Babel to introduce some advanced features to ES5.

mkdir -p .app/controllers .app/lib/babel
touch .app/controllers/index.js
browserify $(npm config get prefix)/lib/node_modules/babel-core/polyfill.js -o
.app/lib/babel/polyfill.js

Great, now, let’s create a little hook for Alloy using an alloy.jmk file. We’ll put a hook before alloy compilation to transcompile all our original sources to something ES5-compatible. So, create an alloy.jmk and place it into the .appfolder with the following content:

var babel = require('babel'),
    fs = require('fs'),
    path = require('path'),
    exec = require('child_process').execSync;

task("pre:compile", function (e, log) {
    var babelSrc = path.join(e.dir.home, '*').replace('/' + process.env.ALLOY_APP_DIR, '/app'),
        alloy = path.join(e.dir.home, 'alloy.js');

    // Babel everything
    if (process.env.ALLOY_APP_DIR && process.env.ALLOY_APP_DIR !== 'app') {
        log.info("Transcompiling to ES5");
        // First of all, erase previous compilation but index.js and cie.
        exec('find ' + e.dir.home +
            ' ! -name "alloy.jmk"' +
            ' ! -path "' + path.join(e.dir.home, 'controllers', 'index.js') + '"' +
            ' ! -path "' + path.join(e.dir.home, 'lib', 'babel', 'polyfill.js') + '"' +
            ' -type f -delete');
        // Remove empty folders
        exec('find ' + e.dir.home + ' -empty -type d -delete');
        // And copy the current app into the future app folder for Alloy
        exec('cp -r ' + babelSrc + ' ' + e.dir.home);

        // Add a little line to alloy.js in order to include Polyfill
        var content = fs.readFileSync(alloy);
        content = 'require("babel/polyfill");\n' + content;
        fs.writeFileSync(alloy, content);

        // And finally, transcompile with Babel
        exec('babel ' + e.dir.home + ' --out-dir ' + e.dir.home);
    }
});

So, the Titanium project now basically has this kind of structure:

.
|--- app
|     |-- alloy.js
|     |-- controllers
|          | -- index.js
|     |-- models
|     |-- views 
|--- .app
|     |-- alloy.jmk
|     |-- controllers
|          | -- index.js
|     |-- lib/babel/polyfill.js
|--- build
|--- i18n
|--- modules
|--- platform
|--- plugins
|--- Resources
|--- tiapp.xml

Do the trick

Now, this is the tricky part. We need to make Alloy thinks that the correct application folder is .app/ and not app/. To do so, we’ll need to edit Alloy’s source files (we’ll see about a Pull Request maybe?).

Incidentally, everything could be easier and simplified if we were working in a separate folder, using the Alloy.jmk to copy and transcompile our sources into the original app/ folder. However in order to use all other Alloy features like scaffolding and also to keep a common folder architecture, those small hacks into Alloy plugins are – to me – worth it.

So, let’s find the Alloy installation, usually /usr/local/lib/node_modules/alloy/ and open the file Alloy/commands/compile/index.js.

At the very beginning of the first exported function, you’ll need to add those lines (those with a + sign at the beginning):

//////////////////////////////////////
////////// command function //////////
//////////////////////////////////////
module.exports = function(args, program) {
    BENCHMARK();
    var alloyConfig = {},
        compilerMakeFile,
        paths = U.getAndValidateProjectPaths(
            program.outputPath || args[0] || process.cwd()
        ),
        restrictionPath;

+    if (process.env.ALLOY_APP_DIR) {
+        paths.app = path.join(paths.project, process.env.ALLOY_APP_DIR);
+    }

We’re almost done! Because Alloy is a bit messy sometimes ^.^, there is another little line to fix, around line 250, find the one that refers to CONST.ALLOY_DIR and change it for our paths.app like this:

// Create collection of all widget and app paths
    var widgetDirs = U.getWidgetDirectories(paths.app);
    var viewCollection = widgetDirs;
-   viewCollection.push({ dir: path.join(paths.project, CONST.ALLOY_DIR) });
+   viewCollection.push({ dir: paths.app });

Save it, and be ready to enjoy :)

Last step

Okay, last step, as you probably noticed, we’re refering to an environment variable ALLOY_APP_DIR, so, we’ll have to define that variable !

export ALLOY_APP_DIR=.app

aaaaaaaaand… you’re done. You may want to add this export line to your .bashrc or .bashprofile or whatever you’re using. Also, if you’re using a version control system as git, make sure to correctly set up your .gitignore to avoid the.app folder (however be careful, you still need the index.js, alloy.jmk and polyfill.js in there !).

Enjoy !

Original post from KtorZ’s blog

Lead developer at The Smiths. JavaScript and functional programming lover.


Comments

  • Josh Jensen

    Thanks for reposting the KtorZ blog post about this and re-sharing it with the community. It’s a fun look at how to push Alloy and Titanium. That said I think it should be made clear in the interest of new comers and those looking for best practices that this is not supported by Appc and should not be used in a production app.

    Posts like this are fun to play with, but could get a new Titanium user or inexperienced longtime Titanium user into some hot water when it comes to debugging and longterm application maintainability.

  • Matthias Benkort Post author

    Indeed, this is more like a temporary Alloy hack than anything else.
    However, people shouldn’t be afraid of transcompiling with Babel into a fully ES5-compatible code. I mean, what is really compiled and ship with the app is just a compatible JavaScript app.

    Nevertheless, I get your point and I’ll add a little warning at the beginning.

  • Josh Jensen

    Thanks Matthias,

    For someone, like yourself, that knows Titanium and its intricacies saying something like “thus we ought to use it with Alloy” can make sense for you.

    However I take issue with that statement, knowing that many people use TiDev as a resource for learning Ti/Alloy. The more we abstract, the more room there is for issues in development and down the road.

    For example would a user know how the code they are writing is transpiled from ES6 to ES5? And how that will effect memory management? When they get a Ti error is it in Titanium, Alloy, or Babel?

    For someone to effectively manage a project using this workflow one would need to know how Babel translates to ES5, how Alloy interprets that generated code, how Alloy then compiles the generated ES5 and eventually how Titanium will interpret it.

    You’re right, what is being run on the device is just vanilla ES5 js running against Titanium. However getting there and maintaining that is the issue.

  • uzbbert

    if only Alloy itself was completely configurable, that would help a lot with this issue and also quite some issues i’ve had so far myself. Why not read all alloy compiler settings from an `alloy.json` file, allowing complete control over directory structure etc.

    I would try this out but I’m not going to go through the pain of editing source files, since there are more developers working on the app right now

  • matthiasbenkort

    I am currently writing a little plugin that might solve the issue. It’s coming soon and will be as easy as doing appc build –es6, nothing more.