Farewell CoffeeScript

CoffeeScript and I had a great run. If I recall correctly, I started to get into it seriously and working it into side projects sometime in late 2010. This was just before 1.0 came along and it was becoming a pretty smooth experience.

In August 2011 Rails 3.1 was released with CoffeeScript support by default. With Rails as my primary stack and already starting to invest in CoffeeScript, this was like a dream come true!

Fast-forward to late 2012 and I started working on my second SaaS product, CourseCraft with my wife. Of course it was heavily backed by CoffeeScript.

Those were happy, carefree times.

A New Hope

Then, well, ES6 started to look like a thing and real progress was being made. One day, 6to5 (now Babel, of course) appeared and sealed CoffeeScript’s fate.

Articles started to come out about switching from CoffeeScript to ES6 via 6to5/Babel. It even supports potential upcoming ES7 features like decorators and function bind. It’s easy to see that quite a few of the ES6/7 improvements were inspired by CoffeeScript (like the ‘fat arrow’).

All of this means you can take some (contrived) CoffeeScript like this:

module.exports =
  someFunc: ( array ) ->
    text = "interpolated: #{array.join(',')}"
    obj = x: x
    someOtherFunc obj, array...

…and instead write:

export default {
  someFunc( array ) {
    const text = `interpolated: ${array.join(',')}`
    const obj = { x: x }
    return someOtherFunc( obj, ...array )
  }
}

I think that’s pretty good. Even superior in some ways. It’s starting to feel more and more like tomorrow’s JavaScript transpiled to today’s JavaScript is the way forward.

The Empire Strikes Back

ES6 and ES7 go a long way to making CoffeeScript less desirable - we don’t really need the sugar anymore to hide the ugly parts of JavaScript - but there’s still some parts that I will fondly remember.

Implicit Return – I really, really like implicit return. It’s never burned me even once, and it feels so natural especially as a Ruby programmer.

Optional Parentheses/Braces – This one I’m a bit hot-and-cold on. Honestly, I use it sparingly in certain situations and I think it’s fine. However, I’ve also seen a lot of code out there that really abuses this feature. Do I see my own code with rose-coloured glasses? Perhaps. This is probably a net win with some time to get used to it again.

Automatic === – Yea I just can’t remember to write === instead of ==. I do tend to be careful about types though and don’t compare strings to numbers/etc… Maybe time to start re-training that muscle memory anyway :)

No Semicolons – In my snippet above, I skipped semicolons. It makes me (and the Bobs) feel bad though. Most of the time you can safely omit them. Unfortunately there are some edge-cases that can bite you and you’ll have to write silly things like ;[x,y,z].forEach(...). It’s not a deal breaker, but CoffeeScript got this right - none of this “it’s sort of required but you can skip them if you know what you’re doing” stuff.

Postfix Conditionalsreturn if something, I will miss you friend.

Everything is a Statement – The docs show some great examples. That’s just really nice.

…The Phantom Menace

All in all my biggest problem with moving on from CoffeeScript is the dreaded legacy code issue. If that’s a con, then it’s also a pro - why add more technical debt? I have all of this CoffeeScript, and realistically it’ll be CoffeeScript forever - even when we’ve got ES10. CoffeeScript has made decisions along the way that are sure to interfere with adding new JavaScript features and make it hard to get at them.

Actually, this has already happened.

For instance take generators. It took CoffeeScript over a year to decide what to do about this and finally the result was “if the function contains the keyword yield, it is a generator”. Cool, except that you don’t always have a yield in your generator (for example let’s say you wanted to use Koa).

Or how about the proposed ES7 function bind that I mentioned earlier. CoffeeScript already uses :: in a Ruby-esque way to turn String::dasherize = -> into String.prototype.dasherize = function() {.

More will come up I’m sure, so all my existing CoffeeScript will most likely need to go without these features. That’s not the end of the world, but greenfield projects will (should?) have much less of this problem since I’ll be sticking to the ‘standard’.

Now I’ll just have to somehow resist using stage 0 proposals all over my codebase that might end up having to be removed! :)