JavaScript Transpilers: What They Are & Why We Need Them

Peleke Sengstacke


Transpilers, or source-to-source compilers, are tools that read source code written in one programming language, and produce the equivalent code in another language. Languages you write that transpile to JavaScript are often called compile-to-JS languages, and are said to target JavaScript.

Oh, and, even though people tend to use "compile/r" and "transpile/r" interchangeably, I'll prefer the latter term in this article.

You've probably heard about CoffeeScript and TypeScript. CoffeeScript provides syntactic sugar for a number of features not yet native to JavaScript, while discouraging some of JavaScript's "bad parts". TypeScript is more drastic, adding classical object-oriented semantics to a fundamentally different language.

Anything you can write in JavaScript, you can write in CoffeeScript or TypeScript.

"use strict";

// Good 'ol JS
function printSecret ( secret ) {
    console.log(`${secret}. But don't tell anyone.`);

printSecret("I don't like CoffeeScript"); 
"use strict"

# CoffeeScript
printSecret (secret) =>
    console.log '#{secret}. But don't tell anyone.'

printSecret "I don't like JavaScript." 
"use strict";

// TypeScript -- JavaScript, with types and stuff
function printSecret ( secret : string ) {
    console.log("${secret}. But don't tell anyone.");

printSecret("I don't like CoffeeScript.");

Trouble is, JavaScript environments only understand . . . Well, JavaScript. Trying those last two examples in your console will throw errors. As a matter of fact, if you try that pure JavaScript example in an older browser, you'll still get an error. Template literals still don't have reliable browser support.

That's where transpilers come in: They read CoffeeScript, TypeScript, and ES2015, and spit out JavaScript guaranteed to work anywhere.

In Defense of Transpilers

If your workflow doesn't already include a transpiler, you might wonder why you'd even bother. Why learn new syntax and pick up new tools if all we get at the end of the day is the JavaScript we could have written in the first place?

In the case of languages that target JavaScript, it's largely a matter of preference or background. Writing in a language that "thinks" the way you do makes you more productive. People with backgrounds in OOP often like TypeScript because it's familiar territory. Pythonistas like CoffeeScript. Clojurists write ClojureScript. You get the idea.

But the rest of us, who are perfectly fine with writing plain JavaScript, still use transpilers, because they're the only reliable way to use features from ES2015 and beyond.

Tomorrow's JavaScript, Today

Anyone who's had to deal with browser compatibility issues before knows its not as simple as writing JavaScript that runs everywhere. That's because every browser uses a different JavaScript engine: Chrome runs V8, Firefox runs SpiderMonkey, and Interet Explorer, Chakra. Each has different performance characteristics, each implements a different subset of ES2015 features, and each is approaching full compliance with the spec at different rates.

That means that, while our template literal example works just fine for those of you running the most recent Chrome, Firefox, or Safari, it won't work for people running older versions. Or for anyone using Internet Explorer . . . Obviously.

The ES6 compatibility table shows that, while we're clearly making progress, it's not quite time to write ES2015 directly. Instead, we write our source in ES2015, and let a transpiler translate it to vanilla ES5 that works in every browser. If you need to support browsers from the last millennium, you can even compile down to ES3.

This way, you can use any feature supported by your transpiler of choice, right now, and know that it'll work for everyone who hits your site -- from the public employee with no choice but to use IE8 to the hacker types running a FireFox nightly.

Tomorrow's JavaScript, Along the Way

Transpilers also play an important role in guiding the decisions of the TC39 committee, which is the group in charge of designing the ECMAScript standard.

For one, transpilers can contribute directly to the inclusion of a feature in the standard. For a potential feature to move from Stage 1 (Proposal) to Stage 2 (Draft):

Two experimental implementations of the feature are needed, but one of them can be in a transpiler such as Babel.

The rest of Dr Rauschmayer's overview of the inclusion process is on his 2ality blog..

Probably more important than this contribution is that feedback from users and implementers of transpilers like Babel or Traceur help TC39 iterate on syntax and proposals. This comes up often during TC39 meetings: If you read every line of the TC39 Meeting Notes -- or, you know, just rgrep -i them -- you'll find that Babel was mentioned 36 times, and spot that the conclusion of the March 25, 2015 meeting on This-binding syntax was: "Get more feedback from users of Babel"!

TC39 Meeting Notes:

To recap, transpilers:

  1. Allow us to write compile-to-JavaScript languages, like CoffeeScript, TypeScript, or ClojureScript;
  2. Let us use new and potential JavaScript features, reliably; and
  3. Contribute to the development of the ECMAScript specification.

Now that we know everything we need to know about what transpilers are, let's dig into how to use them.

Using Transpilers

There are a number of ES2015-to-ES5 transpilers out there, but I'll be focusing on Babel.

In this section, we'll cover:

  1. Comparing ES2015 source to transpiled output;
  2. Setting up the Babel command-line interface (CLI); and
  3. A look at how build toolks like Webpack, JSPM, and Babelify streamline the process.

My Source Is Your Source

To get started, head over to Babel's live transpiler. Copy the code below into the JavaScript editor (left box):

"use strict";

class Planet {

  constructor (mass, moons) {
    this.mass  = mass;
    this.moons = moons || 0;

  reportMoons () {
    console.log(`I have ${this.moons} moons.`)


// Yeah, Jupiter really does have (at least) 67 moons.
const jupiter = new Planet('Pretty Big', 67);

Take a look at the output (reproduced below, for convenience). You'll notice that there are no ES2015 features. const variables get converted to carefully scoped var declarations; class is converted to an old-school function constructor; and the template string desugars to simple string concatenation.

// Everything below is Babel's output.
'use strict';

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var Planet = function () {
  function Planet(mass, moons) {
    _classCallCheck(this, Planet);

    this.mass = mass;
    this.moons = moons || 0;

  _createClass(Planet, [{
    key: 'reportMoons',
    value: function reportMoons() {
      console.log('I have ' + this.moons + ' moons.');

  return Planet;

These are features the language has had from day one. Using them to emulate features from ES2015 is precisely how they let us use non-standardized features now, and to do so safely, to boot.

As a fun little note, a catch clause creates a scope in JavaScript. This behavior was standardized all the way back in ES3. Traceur took advantage of this for some time to emulate block-scoping, producing something like this:

"use strict";

// ES2015
    let name = "Never woulda thunk.";

// ES3/5 output
try { throw void 0; } catch(name) {
    name = "Never woulda thunk.";

How's that for a polyfill?

Setting Up the Babel CLI

The live REPL is slick, but writing your entire codebase that way would suck. Let's save ourselves some work and set up the Babel CLI.

To get started:

  1. Create a directory somewhere;
  2. Initialize it as an NPM project;
  3. Install the Babel tool, along with the presets and plugins we'll be using; and
  4. Configure Babel to use those presets and plugins.
# This moves to your home directory to create the folder. 
# If you don't want it there, cd somewhere else.
cd && mkdir babel_example && cd $_
npm init
# Hit ENTER a bunch of times . . . 
npm install --save-dev babel-cli babel-preset-es2015 babel-plugin-transform-async-to-generator  

This will install the Babel CLI (babel-cli); a collection of plugins enabling all the ES2015 features that Babel supports (babel-preset-es2015); and a plugin allowing you to use the ES7 proposal, Async functions (babel-plugin-transform-async-to-generator).

To run Babel, you simply type babel <FILENAME>. But before we do that, we need to tell it what plugins to use. Babel looks for this information in a file called .babelrc, in the top level of your NPM project.

    "presets": ["es2015"],
    "plugins": ["transform-async-to-generator"]

Now, copy the snippet from above with the Planet class into an index.js, and run babel index.js --out-file index.transpiled.js --source-maps. This will create a transpiled version of index.js, in index.transpiled.js, and a separate sourcemap file, A source map is a file that tells the browser which lines of your transpiled code correspond to which lines of your original source, so you can debug index.js directly.

Babel's output in index.transpiled.js

If you want to transpile your file every time you save changes, you can run:

babel index.js --out-file index.transpiled.js --source-maps --watch

Build Tools

There are several other options you can use with the Babel CLI. But, as you can probably imagine, figuring out how to configure them, on top of whatever other tools you're using, can become unwieldly.

For us, running babel --watch is enough. But for any nontrivial project, you'll have a number of build steps: Transpiling, minifiying, organizing your outputs for distribution, determining what needs to be updated, optimizing images . . . It's as much of a nightmare as it sounds like.

If you use the CLI directly, you'd have to run through each step manually, or script the process yourself. That second option is definitely viable, but it can be:

  1. Brittle;
  2. Difficult for teams; and
  3. Unportable across platforms.

This is where build tools come in. Grunt and Gulp are popular systems that streamline orchestration of the build process. Bundlers and module loaders like Webpack, JSPM, and Browserify -- which you can read about in another article of mine -- take care of transpilation alongside all of the other magic they work.

I've used Browserify to transpile tests written in CoffeeScript to plain JS; delete the output after the tests run; and bundle my code with Babel if they all pass; and put everything in the right place, with source maps, along the way. That's all possible using the command-line tools directly, but it's brittle.

You can trust me on that -- I used to use bash script for this. That Linux diehard coming back to bite me. And if you ever find yourself needing to load multiple module formats or wanting to experiment with hot module replacement, you don't have much choice but to turn to a build system.

Unless you like to roll your own glue code, of course.


It's hard to get away from Transpilers: You need them whether you plan to write a language that compiles to JavaScript, or simply use ES015 reliably. We saw that feedback from people in that latter group is an important source of guidance for the TC39 committee in its decisions about the future direction of JavaScript, as well.

We took a look at using the Babel CLI to transpile ES2015 with source maps, and learned about tools that streamline transpilation in more complicated projects.

Jame Kyle's Babel Handbook is a go-to for everything Babel, and if you're interested in Webpack, read the How-To before wading through its . . . Erm, "suboptimal" documentation.

Feel free to ask question in the comments, or shoot them to me on Twitter ([@PelekeS]()) -- I'll be sure to get back to everyone individually.

Peleke Sengstacke

13 posts

Peleke Sengstacke is a web and Android developer with a soft spot for functional programming.

He likes linguistics, Haskell, and powerlifting. He dislikes mosquitoes, cramped bus rides , and merge conflicts.

Catch him on Twitter (@PelekeS), or sign up for his email list on what to learn and where to learn it ( It's wicked educational.