Instantly share code, notes, and snippets.

What would you like to do?
Moving to ES6 from CoffeeScript

Moving to ES6 from CoffeeScript

I fell in love with CoffeeScript a couple of years ago. Javascript has always seemed something of an interesting curiosity to me and I was happy to see the meteoric rise of Node.js, but coming from a background of Python I really preferred a cleaner syntax.

In any fast moving community it is inevitable that things will change, and so today we see a big shift toward ES6, the new version of Javascript. It incorporates a handful of the nicer features from CoffeeScript and is usable today through tools like Babel. Here are some of my thoughts and issues on moving away from CoffeeScript in favor of ES6.

While reading I suggest keeping open a tab to Babel's learning ES6 page. The examples there are great.


Holy punctuation, Batman! Say goodbye to your whitespace and hello to parenthesis, curly braces, and semicolons again. Even with the advanced ES6 syntax you'll find yourself writing a lot more punctuation. Accept it now. You'll see it in the examples below. The good news is that with a good linter the code will still be pretty.


I used to use CoffeeLint. Now I use ESLint through babel-eslint. Along with the Airbnb ES6 style guide it's quite strict. Your best bet is to set your editor up to lint as you type or on save. Atom's eslint plugin seems to work well and looks for a locally-installed/configured eslint per directory, which should find the .eslintrc which you can copy from the Airbnb link above. Sublime has a similar plugin.


Since support for ES6 is still sketchy at best, you'll probably be transpiling just like you did with CoffeeScript. Unlike with CoffeeScript, there are some caveats, however.

  1. Some ES6 features are just not available, like Proxies. Maybe some day we'll get the equivalent of Python's __getattr__ and Ruby's method_missing, but that day is not today. Them's the breaks.

  2. Some of the more interesting ES6 features require a polyfill/runtime. Symbols, generators, and new types like WeakMap for example. Example package.json snippet:

      "scripts": {
        "lint": "eslint --ext .js,.es6 src",
        "precompile": "npm run lint",
        "compile": "babel --optional runtime src --out-dir lib",
      "dependencies": {
        "babel-runtime": "^5.3.3"
      "devDependencies": {
        "babel": "^5.3.3",

    Don't make the mistake of depending on babel because that will download many more packages than you need. Depend on babel-runtime, devDepend on babel for the build.

    By the way, these polyfills may have strange edge case behavior in some environments.

  3. Some features are disabled by default. Specifically, those in TC39 Stage 0 like list comprehensions. This isn't Babel's fault - the specifications are not yet complete. Yes, we had these in CoffeeScript and now have to wait to use them again.

Let and Const

Haven't you heard the news? var is out while her buddies let and const are in. With Javascript you should declare your variables. Default to const and if a value needs to change, then use let. The transpiler will enforce constants while the linter will complain if you use var or nothing.

Keep in mind that const applies only to the value itself. This takes a little getting used to:

const name = 'Daniel';

// This is a compile error
name = 'Kari';

// ---------
const options = {};
const items = [];

// This is *not* a compile error = 'bar';
options.baz = 5;

// This *is* a compile error
options = {};
items = null;

String Interpolation

Luckly, string interpolation is one area where you just need to retrain your fingers:

const name = 'World';

console.log(`Hello, ${name}!`);

Notice the backticks instead of single quotes.

It takes some time, so make sure your editor syntax highlights these properly or I guarantee you'll get stuff like #{name} printed out by accident.


CoffeeScript has a concept of a range object that acts like an array of items from some starting point to some ending point. It's similar to Python's range() built-in and is super useful for loops. ES6 doesn't have this, so you'll need to either use the new Array.from and array.keys() or make a custom range function with your own loop logic.

let range;

# Coffee (inclusive): [0..10]
range = Array.from(new Array(11).keys());

# Coffee (exclusive): [0...10]
range = Array.from(new Array(10).keys());

# Coffee (non-zero): [2...10]
range = Array.from(new Array(10).keys()).slice(2);

# Coffee (decrementing): [10...2]
range = Array.from(new Array(10).keys()).slice(2).reverse();

# Then use it in a loop:
for (let i of range) {

# Coffee: evens = (x for x in [0..10] by 2)
let evens = Array.from(new Array(6).keys()).map(i => i * 2);


Javascript has some new function types! With ES6 you can use both fat arrows and generators. Fat arrows behave like they do in CoffeeScript and keep this passed through to the function body. They support both an implicit return shorthand and a long form that has no implied return value.

Splats are also supported, but the ellipsis is on the other side. Default arguments are also supported and work as you'd expect. Same with assignment destructuring.


square = (value) -> value * value

someTask (err, result) =>
  # Handle err and result

myFunc = ({source, flag}, args...) ->
  otherFunc source, args...


const square = value => value * value;

someTask((err, result) => {
  // Handle err and result

function myFunc({source, flag}, ...args) {
  return otherFunc(source, ...args);


Generators provide a way to iterate through a large or possibly infinite list of items by only processing one at a time. Read up on them if you like. Here's a quick example:

// Instead of creating a 10000 item array, we yield each item as
// needed.
function *squares() {
  for (let n = 0; n < 10000; n++) {
    yield n * n;

for (let square of squares()) {

The function * syntax tells Babel that this is a generator, unlike in CoffeeScript where the presence of a yield statement made it a generator. Note that generators can both yield and return values.


I'm happy to report that classes are actually very similar, and my main gripe with them is something that's being worked on in the spec and also has a usable workaround (only being able to create functions in a class). The following example will show just how similar the syntaxes are, including things like inheritance.


class Account extends Foo
  @types = ['checking', 'savings']

  constructor: (@balance) ->

  history: (done) ->
    someLongTask (err, data) ->
      # Do something with data
      done null, data

  deposit: (amount) ->
    @balance += amount


class Account extends Foo {
  constructor(balance) {
    this.balance = balance;

  history(done) {
    someLongTask((err, data) => {
      // Do something with data
      done(null, data);

  deposit(amount) {
    this.balance += amount;
    return this.balance;

// Currently, you can only declare functions in a class
Account.types = ['checking', 'savings'];

One cool feature is the ability to define getters and setters. Unfortunately they cannot be generator functions.

class Account {
  constructor() {
    this.balance = 0;

  get underBudget() {
    return this.balance >= 0;

  get overBudget() {
    return this.balance < 0;

const myAccount = Account();
myAccount.balance = 100;
console.log(myAccount.underBudget); // => true


Just like in CoffeeScript, ES6 classes can call on super to access the parent's method of the same name. Unlike CS, though, you need to use the method name outside of the constructor. Notice the use of super.deposit below:

class CachedAccount extends Account {
  constructor() {
    this.cacheDirty = false;

  deposit(amount) {
    this.cacheDirty = true;
    return super.deposit(amount);

Iterable Classes

Another neat feature is the ability to make iterable classes, and the ability to use generators for the iterator.

class MyIterable {
  constructor(items) {
    this.items = items;

  *[Symbol.iterator]() {
    for (let item of this.items) {
      yield `Hello, ${item}`;

const test = new MyIterable([1, 2, 3, 4, 5]);

for (let item of test) {
  console.log(item); // => Hello, 1...


ES6 brings with it a new module syntax. It'll take some getting used to, because it includes both a default export and named exports.

import _ from 'lodash';
import {item1, item2} from './mylib';
import * as library from 'library';

export const name = 'Daniel';

export function abc() {
  return 1;

export class Toaster {
  // ...

export default function def() {
  return new Toaster();

Some interesting caveats:

  1. If no default is given then you don't just get an object of all the named exports. Instead use import * as moduleName from 'moduleName';. Feels weird, right? Particularly for Python users, where import * is a four letter word.

    // mymodule.js
    // -----------
    export function yes() { return true; }
    // script-broken.js
    // ----------------
    import mymodule from './mymodule';
    // This gives an error about `undefined`!
    // script-working.js
    // -----------------
    import * as mymodule from './mymodule';
  2. If there is only one export and it is a default export, then module.exports will be set to it. If however there are other exports, you get something that's very strange to consume from normal Node.js require statements.

    // mymodule.js
    // -----------
    export function yes() { return true; }
    function no() { return false; }
    export default {yes, no};
    // script-working.js
    // -----------------
    import mymodule, {yes} from './mymodule';
    // script-broken.js
    // ----------------
    const mymodule = require('./mymodule');
    // Wat? This is an error.
    // This works instead. Turns out the module is an object with a 'default'
    // key that contains the default export.

    I currently don't have a great solution for this nonsense, but if you want to write libraries that are consumed by Node.js users who will likely be using require, this is something to keep in mind. As a possible workaround you can export default one thing and then assign all the things to it as properties. For example:

    // myclass.js
    // ----------
    function export1() { return true; }
    function export2() { return false; }
    export default class MyClass {
      hello() { console.log('Hello!'); }
    MyClass.export1 = export1;
    MyClass.export2 = export2;
    // script-working.js
    // -----------------
    const MyClass = require('./myclass');
    console.log(MyClass.export1()); // => 'true'
    const instance = new MyClass();
    instance.hello(); // => 'Hello!'


Hopefully this article helps someone. It's been really fun learning ES6 and getting to play with all of the new toys like Babel and ESLint.


This comment has been minimized.

josep2 commented May 20, 2015

Nice work.


This comment has been minimized.

netmilk commented May 21, 2015

Solid gold.


This comment has been minimized.

ghost commented May 21, 2015

I think that with all the ES6 enhancements there will be no necessity for Google's Dart :)


This comment has been minimized.

osmelmora commented May 21, 2015

Nice, but I still hate punctuation


This comment has been minimized.

hemanth commented May 22, 2015


This comment has been minimized.

simov commented May 22, 2015

semi colon at the end of the line is optional in javascript


This comment has been minimized.

jalcine commented May 23, 2015

Optional, sure. But legibility > optional.


This comment has been minimized.

gyaresu commented May 23, 2015

I found out to drop a master .eslintrc into your home directory and override repos with a local .eslintrc when necessary


This comment has been minimized.

Vhornets commented May 23, 2015

Thanks, this is helpful. ES6 is great indeed


This comment has been minimized.

devinrhode2 commented May 23, 2015

The javascript community was split between coffeescript and javascript, and ES6 makes clear attempts to decrease the divide (arrow functions are great!). The least we could do is stop using optional semicolons. ES6 with no semicolons makes the argument for coffeescript very very weak


This comment has been minimized.

bigzhu commented May 24, 2015

ES6 is better than JavaScript, but I still prefer coffeescript, cleaner syntax is most important


This comment has been minimized.

Howcanology commented May 24, 2015

In moments like these, I find myself looking for a "Like" button. Well done.


This comment has been minimized.

JulienBreux commented May 24, 2015



This comment has been minimized.

b1rdex commented May 26, 2015

@Howcanology you can use Star button instead. It's on top of page.

//edit: whoa, gists have no mentions support? o_o


This comment has been minimized.

limichange commented May 26, 2015



This comment has been minimized.

xgrommx commented Jun 6, 2015

It's not true:

// Currently, you can only declare functions in a class
Account.types = ['checking', 'savings'];

You can look on class properties section


This comment has been minimized.

componentDidMount commented Jul 7, 2015

@b1rdex, lol .. oh wait...


This comment has been minimized.

nmaquet commented Oct 15, 2015

Really cool writeup, thanks.


This comment has been minimized.

jloiola commented Nov 1, 2015

This is great. Thanks.


This comment has been minimized.

cdll commented Nov 4, 2015

so nice~


This comment has been minimized.

fiatjaf commented Feb 13, 2016

I thought you would teach how to transpile Coffeescript into ES6.


This comment has been minimized.

backspaces commented May 31, 2016

Use Standard rather than AirBnB. A few of us tried both and ended up with Standard. Unless you're in a group of 100 devs, you can lighten up a bit.. Way, WAY too (arbitrarily) strict.

And with

"rules": {
  "curly": [ 0 ]

... you get even cleaner, more coffee-like syntax.


This comment has been minimized.

backspaces commented May 31, 2016

BTW: In my journey from Coffee to es6, I found that doing it by hand offered considerable advantages. I now use promises a lot more, and with generators, you can basically get async functions. Also TypedArrays were way useful, making my code webgl-ready. Also using arrow functions in several places offers the chance of inlining by the compiler .. no "this" to worry about.

Besides, the vastly improved semantics really are worth learning and using.

The biggest difference however is that now my team-mates can fix my code .. they wouldn't touch Coffee.


This comment has been minimized.

linuxenko commented Feb 2, 2017

standard - is a styleguide without semicolons by default


This comment has been minimized.

kpinedok commented Feb 21, 2017

We've been migrating from CoffeeScript to ES6 at Bugsnag. We just open-sourced the tool we've been using to make it faster.


This comment has been minimized.

chabib commented Sep 28, 2017

Guys, coffescript 2 is here!
Announcing CoffeeScript 2
It is transpiled to latest javascript.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment