Back to Blog Home
← all posts

Using Babel in NativeScript Apps

December 23, 2015 — by Todor Totev

The NativeScript 1.5 release introduced first-class support for languages which transpile to JavaScript. In plain English, if your language of choice generates JavaScript, it can now be used to create NativeScript apps. The NativeScript CLI has integrated support for TypeScript, Babel, and CoffeeScript (here’s the full list of languages we support), but if you want to use another language, just create a plugin following the template of the existing Babel one.
 
The upcoming ES7 language edition has the async/await feature which makes the UI and networking code a breeze to write and support. I was very eager to experiment with async code in a NativeScript app, and decided to try it by implementing a simple app. I decided to mirror the cuteness sample. But my project was going for something more awesome, and that's why it follows /r/hardcoreaww sub-reddit instead.
 
I've started with a fresh project:

tns create hardcoreaww && cd hardcoreaww

Then I added support for Babel and Android platform:

tns install babel && tns platform add android

To set up my development environment, I started an Android emulator and my code editor. I am using the awesome
Visual Studio Code to write this blog post.
 
Then, I needed to set up Babel. First of all, Babel requires a runtime to work.
 
I used

npm install babel-polyfill --save

to add it to my project and then "require" it in my app.js:

"use strict";
require("babel-polyfill");

As I wanted to use the async keyword, I needed to load the appropriate Babel plugins. To do so, I created a .babelrc file at the root of my project with this content:

{
  "presets": ["stage-3", "es2015"]
}

The presets are discussed here. Please consult Babel's docs for other possible options.
 
As these components are only needed while developing the app, I installed them as "dev dependencies" using:

npm install babel-preset-stage-3 -D
npm install babel-preset-es2015 -D

Then I started the LiveSync process: 

tns livesync --watch

and checked that the initial app from the template works in the emulator. I was ready to start hacking!
 
As I developed my app, I discovered some gotchas. It turned out that the "application" module from version 1.5 cannot be imported using the ES6 import syntax.
 
Therefore, this syntax

import * as application from "application";

is not yet applicable. We will try to lift this limitation for the upcoming 1.6 release. In the meantime, I am using

let application = require("application");

It gets the job done for the time being. As I was writing my code, I was able to use different ES6 constructs, like named and default exports, for example:

export default class AppViewModel extends Observable { ....
 
which I used later as

import AppViewModel from "./reddit-app-view-model";

This code also uses ES6 classes with constructors and inheritance. I also used `let` and `const` everywhere, so that the compiler can check my code for dangerous behavior.
 
But this was not enough. I wanted to taste the simplicity and effectiveness of the ES7 async construct. The perfect opportunity presented itself when I was fetching the data out of reddit.
 
The code turned out to be as succinct and linear as the ads suggested:

try {
    const url = redditUrl + args.count + (after ? "&after=" + after : "");
    const response = await fetch(url);
    const result = await response.json();
    const itemsToLoad = result.data.children.map(i => new RedditViewModel(i.data));
    this._redditItems.load(args.index, itemsToLoad);
} catch (e) {
    // not production code
    console.log("Error fetching data: " + e.toString());
}

The same code with promises does not look too bad, but I find it harder to read, and thus maintain:
 
fetch(url)
    .then(response => response.json())
    .then(result => {
        let itemsToLoad = result.data.children.map(i => new RedditViewModel(i.data));
        this._redditItems.load(args.index, itemsToLoad);
    }).catch(e => {
        // not production code
        console.log("Error fetching data: " + e.toString());
    });

As you can see, the async code is easier to read and the error handling happens with the good old boring (and dependable) try/catch.
 
The entire sample app can be found at GitHub. Please note that to run it on iOS, you need to manually change the info.plist. Read this blog post for instructions.

The results are nice:

aww-1   Android image


To conclude my experiment, I found I enjoyed writing ES6/ES7 style code. Modern JavaScript is a very different beast than it used to be just a couple of years ago. It is suitable for writing complex apps which need to be continuously maintained and improved. I am very happy how simple integration of NativeScript and Babel turned out to be.
 
tns install babel && tns platform add android
tns install babel && tns platform add android
tns install babel && tns platform add android
tns install babel && tns platform add android