Pellucid

The Benefits of Transpiling to JavaScript

Written by Franco Ponticelli on October 20, 2014

A transpiler is a type of compiler that takes the source code of a programming language as its input and outputs the source code of another programming language.

In the last couple of years, new transpilers have been coming out more and more frequently. Their most common target is JavaScript. JavaScript is in every browser and it is used by every person using the Internet. So like it or not, JavaScript is important.

What's wrong?

So what is wrong with JavaScript? I will not go into details about the limitations or weirdness of the language - I will limit myself to refer to just one topic that bother me. JavaScript becomes quite an effort to maintain after your code base reaches a certain size. JavaScript developers need to be very disciplined to keep their code consistent and cannot rely on any solid form of 'self documentation'.

By 'self documentation' I refer to the kind of documentation that can be extracted from the code without additional work for the developer. Because let's face it, documentation is a thing that developers love to read but hate to write.

So how do transpilers help? Well most of them introduce types and types help a heap in making sense out of the code.

Which Transpiler?

Well, there are many choices, you can pick ES6 (the most complete transpiler for it is traceur), CoffeeScript, TypeScript or Dart to quote a few, but my personal preference goes to Haxe.

Haxe has all the features I want to talk about (except one: 'short lambdas', or in ES6 jargon 'arrow functions'). It is solid, it is flexible and it has a really awesome community. So the examples below are going to be in Haxe, and they should feel very familiar to any JavaScript developer, and they can most likely be ported to other languages as well.

Type System

Oooh ... types ... they really feel good when you can rely on them and when they don't get in your way too much.

var name = "Franco";

This is equivalent to:

var name : String = "Franco";

Haxe uses type inference to assign the right type to name, but once it is set (because I specified it or because it has been inferred) it cannot be changed or reused for another type. If afterward, I try to reuse the variable name with another type, the compiler (I should probably say transpiler but I really don't want to) will point its finger at me and it will make me feel bad about myself.

var name = "Franco";
name = 1; // compile time error!!!

Of course this simple example is probably a little too simple and you should never recycle variables for different data types (not even in JavaScript, where it is allowed), but where most of the JS engines usually fail is at optimizing such weirdness.

So keeping it closer to the usual JS scenario, let's suppose that you define a function that takes an options object that looks like this in JS:

company.addPerson({
    name : "Franco", // this should be a string and not an optional value
    company : "Pellucid", // same requirements as before
    age : null, // this is a nice to have, but it is optional and should be an integer
});

Now in JavaScript you are pretty much on you own, you can either live on the edge and assume that the developer will never make mistakes and will always pass the right parameters, or you can add runtime validation which will make your code more robust, but will also take a lot of your precious time and will add some runtime weight to your output.

A typed language simply solves the issue by correctly defining the function addPerson in the first place:

public function addPerson(options : {
    name : String,
    company : String,
    ?age : Int // note the question mark in front of the field name?
               // It means it is optional
}) { /* do something here */}

Of course validation might still be required (negative age anyone?) but it still helps a lot. Also, can you see how this helps a lot in 'self documentation'? There is basically no need to add manual documentation to the function; although, you are still free to do so, and in some cases encouraged.

So what happens if I try to add a field to the options object that is not specified in the type? The compiler will slap your hand and report an error.

The compiler understands your code

It might sound obvious but if the compiler understands your code, it can probably do more than just type checking or transpiling. Indeed it can. The compiler can be leveraged by editors to provide features like autocompletion (that actually makes sense) and refactoring tools (in Haxe these are pretty new and still not very widely adopted).

Syntax, sugar and other stimulants

Obviously, languages that transpile to JavaScript are not JavaScript, or the benefits would be quite unclear. The only notable exception is probably TypeScript - which is a super-set of JavaScript - fully compatible with the language, but with some extras for coolness.

This gives to the developers of the language a lot of power to add stuff that, in JavaScript, would be impossible, if not very clumsy. Let's discuss some of these features.

String Interpolation

In JavaScript:

return 'My name is ' + name.toUpperCase() + ' and I am ' + age + ' old';

What about?

return 'My name is ${name.toUpperCase()} and I am $age old';

Strings can also span multi-line without the need for concatenation or crazy unofficial \ hacks.

Array Comprehension

It provides ways to initialize array in more ways than just manually declaring the desired values. Consider the following in Haxe:

var arr = [for(i in 0...5) i*2];

The JavaScript code would be:

var arr = [];
for(var i = 0; i < 5; i++) {
  arr.push(i*2);
}

This, in my opinion, makes clear why there is such a proliferation of libraries for JS that try to make javascript code more concise. The problem with such libraries is that they tend to reimplement the same features in many different ways, resulting in a language that is even more confusing than the original, and often with additional penalties in term of performances.

To reiterate on that concept, write pretty and elegant code and use a transpiler to make it efficient (and probably ugly).

Inlining

Speaking about efficiency, have you ever had the desire to write thin abstractions to make your code better? Do these abstractions often have a side-effect adding up in the execution time? Provided that JIT compiler usually does an excellent job at doing that kind of optimization without the developer even noticing it, sometimes it is nice to be in control.

The inline keyword does exactly that ... consider this:

// in a class definition you have a method like this
inline public function getNameLength() {
    return name.length;
}

// usage
trace(instance.getNameLength());

For this usage, the compiler will remove the wrapping function and only produce:

console.log(instance.name.length);

Another thing to note is that the compiler can make sense of the scope of the function getNameLength and knows that if there is no local variable named name then it must belong to the instance and you don't have to use this. Obviously if name doesn't belong to the instance or to the class (as a static member) then you will get a nice error.

Trimming your output

Remember how the compiler knows your code? Well, that also applies to things you might not expect like DCE (dead code elimination). Such feature recognizes the parts (types, function definitions ...) that are defined in your code, or in the library you use, but are actually not used in your app. By default, these parts will be discarded from the output keeping your result as lean as it can be. This is awesome because adopting a library doesn't automatically mean adding a bunch of random code to your output.

Sometimes DCE can get in your way, particularly when you use dynamic features like Reflection. For that reason, DCE can be disabled.

Beside DCE there are other smart mechanisms you can adopt in your code to remove non-essential code. Pretty common example: let's just remove all those nasty console.log() (trace in Haxe) from the production deployment. Easily done, just add --no-traces to your compiler instructions and they are gone.

With some more effort you can define your own logic of adding/removing pieces of code from specific outputs; IE8 code only, anyone? You can use conditional compilation or macros to that effect. To be fair, you can use Macros for much more than just that. Macros can be used for DSL, embed resources, assets management, template parsing, dynamic type generation ... Just a word of advice - it is really easy to go crazy with macros - just don't.

Code Organization

What about modular code? For clarity, you certainly want to split your code into atomic containers (called modules in Haxe). In each module you can put type definitions and refer to other modules (and the included types) using the import statement. Code is also organized in packages (or namespaces) that reflect the structure of your Haxe files in the filesystem (the namespace matches the containing directory). That makes for a very easy way to interact between types. Also the generated code looks a lot like mainstream libraries that use objects to avoid name conflicts.

If you are paranoid and want to keep your structure as flat as possible to enhance performances you are in luck and you can use the compiler flag -D js-flatten.

Fancy Types

In Haxe there are a few very common types like classes, interfaces and typedefs (type aliases for anything from anonymous objects to function signatures). All of these map pretty naturally to pure JavaScript, even if they are not defined in the language. But with a transpiler you can do much more than that. Haxe brings 'enums' and 'abstracts' to the game.

Enums

There are no enums in JavaScript but they are pretty common in other languages. Enums are a way to give meaningful names to entities that belongs to a certain kind. For example, you can have a type:

enum Color {
    Black;
    White;
}

Whenever an instance of type Color is expected you can only use the values Red, Green or Blue. But Haxe goes beyond that and allows you to define enum values that can bring additional information. We can rewrite the above enum to be more generic: haxe enum Color { Black; White; RGB(r : Int, g : Int, b : Int); }

Haxe supports switch expressions natively on enums and switch support advanced pattern matching features to produce nice things like this: haxe var color = RGB(100, 0, 0), description = switch color { case Black, RGB(0,0,0): 'pitch black'; case RGB(r, _, _) if(r > 120): 'there is a lot of red in it'; case _: 'anything else'; }; trace(description);

Abstracts

Abstract types are type wrappers. They are types that provide additional features or mask characteristics of other types. For example you could store the RGB channels of a color using an integer. While efficient, it does not semantically fit very well. So here is the abstract solution.

abstract RGB(Int) {
    inline public function new(rgb : Int)
        this = rgb;
}

Now that alone doesn't add much, but abstracts have special powers. Let's add a few methods to the abstract.

using StringTools;

abstract RGB(Int) {
    inline public function new(rgb : Int)
        this = rgb;

    public var red(get, never)   : Int;
    public var green(get, never) : Int;
    public var blue(get, never)  : Int;

    inline function get_red()
        return (this >> 16) & 0xFF;
    inline function get_green()
        return (this >> 8) & 0xFF;
    inline function get_blue()
        return this & 0xFF;

    @:from
    static inline public function fromString(s : String)
        // some additional pre-processing
        // might be needed here
        return new RGB(Std.parseInt(s));
    @:to
    inline public function toString()
        return '#${red.hex()}${green.hex()}${blue.hex()}';
}

And a simple usage might look like:

var rgb : RGB = "0x001133";  // implicit casting of string to RGB(Int)
trace(rgb.green);            // additional property
trace('Nice color ${rgb}!'); // implicit casting to string

And the JS generates it just:

var rgb;
var rgb1 = Std.parseInt("0x001133");
rgb = rgb1;
console.log(rgb >> 8 & 255);
console.log("Nice color " + ("#" + StringTools.hex(rgb >> 16 & 255) + StringTools.hex(rgb >> 8 & 255) + StringTools.hex(rgb & 255)) + "!");

I am not sure why the temp variable is there but I believe there is an optimization in the new upcoming release that takes care of it.

External Types

In the real world there are plenty of very high quality libraries written in pure JavaScript that I don't want to rewrite just because I use a transpiler. Well, that is a very common problem and for that reason the solution is right at hand. Haxe let's you define extern definitions for existing libraries. In essence you just wrap the libraries into types that Haxe can understand. This doesn't add any load at all at runtime since extern are purely compile-time constructs. An additional benefit is that when you write your externs you are forced to assign the correct types and you will earn the same advantages described above for typed code.

Evolution at a different pace

A final world about language evolution. If you have been developing for a long time, you are well-aware how slowly innovation happens in the browser world. Lately, things have gotten much faster than they used to be, but still pretty slow. There are many good (and less-good) reasons for this, but the point is that JavaScript has begun to smell a little.

Transpilers, on the other hand, do not need to wait; with transpiled languages, language creators can invent, experiment and try whatever they want!