How To Use Babel And TypeScript, Together!

Use Case

The Selenium bindings for NodeJS are very nice that everything is a promise. When you are sending a command to the browser, this command might pass (say, return the element if found), or fail (say, trying to click an element that doesn’t exist), so, it’s a good fit for promises.

However, when I write tests, I think of them as a sequence of steps. With promises, the next step is always in a then() function.

But no one wants to keep nesting code like
x.then((y)=>y.then( (z)=>z.then(...) )).
Selenium extends promises so that you don’t have to do this all the time, but I soon hit limits (like loops).

Then I remembered reading about TypeScript support for async/await. When I tried it, the coding experience was nice, but I had to do a bit of setup.

Configuring Async / Await In TypeScript

I had to turn some experimental flags on, and target ES6. The easiest way to do this is to create a tsconfig.json file in the directory where I run tsc (TypeScript compiler) from. Here’s what mine mainly looked like:

<code>{
    "compilerOptions": {
        "target": "ES6",
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true,
        "experimentalAsyncFunctions": true
    },
    "files": [
        "app/**/*.ts"
    ],
    "exclude": [
        "node_modules"
    ]
}
</code>

Node, ES6, Babel, And ES7

I tried to run the output of TypeScript in Node, but it didn’t work. It complained about import statements. It seems that Node doesn’t understand ES6 module system, not very surprising given it has its own module system too, commonJS.

TypeScript can target commonJS, but async/await is only supported when targeting ES6.

So, I decided to take the output through Babel before I run it with NodeJS.

Side Note: Babel already had support for async/await as it’s also coming to ES7. But by the time I got to that I was already happy with my autocomplete experience in TypeScript that I wanted to continue with this setup.

Using Atom Editor

It was so easy to get babel to work with TypeScript in Atom. I had atom-typescript package installed, and it suppoted an extra property in tsconfig.json:

<code>"externalTranspiler": "babel"
</code>

This is a NON-standard TypeScript property. But it allowed me to work very happily with Atom, and my .ts files processed via TypeScript and Babel whenever I save them.

This approach had a main no-go issue though. Anyone who pulls my code should be able to get it to work without me committing the “auto-generated” JS files, and without the others having my Atom setup, and building the project from Atom. This won’t work in CI build server.

Using Gulp Tasks

At this point, I decided to have a gulp task that does the transformation. It turned out to be very easy.

The following code sets gulp to process all .ts files in “app” folder via TypeScript and Babel, and save them as .js files:

<code>var gulp = require("gulp");
var ts = require("gulp-typescript");
var babel = require("gulp-babel");
var rename = require("gulp-rename");

gulp.task("ts-babel", function () {
    // Using my existing tsconfig.json file
    var tsProject = ts.createProject(__dirname + "/tsconfig.json");

    // The `base` part is needed so
    //  that `dest()` doesnt map folders correctly after rename
    return gulp.src("app/**/*.ts", { base: "./" })
        .pipe(ts(tsProject))
        .pipe(babel({
            optional: ["runtime"]
        }))
        .pipe(rename(function (path) {
            path.extname = ".js";
        }))
        .pipe(gulp.dest("."));
});
</code>

You might wonder why did I do both transformations in the same task. That’s mainly to save disk writes, so that I don’t have to write an ES6 file that’s never going to be used except for Babel processing.

If you want to split them, should be easy, like:

<code>var gulp = require('gulp');
var ts = require('gulp-typescript');
var babel = require('gulp-babel');
var rename = require('gulp-rename');

gulp.task('tsc', function() {
    var tsProject = ts.createProject(__dirname + '/tsconfig.json');
    return gulp.src('app/**/*.ts', { base: './' })
        .pipe(ts(tsProject))
        .pipe(rename(function (path) {
            path.extname = '.babel';
        }))
        .pipe(gulp.dest('.'));
});

gulp.task('babel', function() {
    return gulp.src('app/**/*.babel', { base: './' })
        .pipe(babel())
        .pipe(rename(function (path) {
            path.extname = '.js';
        }))
        .pipe(gulp.dest('.'));
});

gulp.task('ts-babel', ['tsc', 'babel']);
</code>

NPM Prerequisites

To run these, you need to install the needed packages:

<code>npm install gulp gulp-typescript gulp-babel gulp-rename babel-runtime --save-dev
</code>

Example & Conclusion

So, that’s all it takes to run the compilation/transpilation. You might want to hook this into nodemon or some gulp-watch task etc., but this is not specific to TypeScript or babel in any way, so, I thought it’s not worth mentioning here.

If you are curious about the code that I needed this setup for, check out my sample code post.

Share With Friends:

How did I learn that?

As a bonus for coming here, I'm giving away a free newsletter for web developers that you can sign up for from here.

It's not an anything-and-everything link list. It's thoughtfully collected picks of articles and tools, that focus on Angular 2+, ASP.NET (4.x/MVC5 and Core), and other fullstack developer goodies.

Take it for a test ride, and you may unsubscribe any time.

You might also want to support me by checking these out [Thanks]: