Skip to content

Instantly share code, notes, and snippets.

@benjie
Last active January 17, 2023 15:16
Show Gist options
  • Star 44 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save benjie/d0e39fbe8a61bc30ed93 to your computer and use it in GitHub Desktop.
Save benjie/d0e39fbe8a61bc30ed93 to your computer and use it in GitHub Desktop.
Long Live CoffeeScript and Long Live ES6

Long Live CoffeeScript and Long Live ES6

Clearly ES6 is a huge improvement over ES5, and tools like 6to5 allow us to use these cool features now. I was reading Replace CoffeeScript with ES6 by Blake Williams and thought it was a great summary of how ES6 solves many of the same problems that CoffeeScript solves; however I'd like to comment on a few of Blake's points and talk about why I'll be sticking with CoffeeScript.

Classes

Classes in ES6 (like many of the syntax changes in ES6) are very similar to the CoffeeScript equivalent. To support browsers that are not fully ES5 compliant (e.g. IE8-), however, we still can't really use getters/setters, so ignoring these the comparison is:

class Person
  constructor: (@firstName, @lastName) ->

  name: ->
    "#{@firstName} #{@lastName}"

  setName: (name) ->
    [@firstName, @lastName] = name.split " "
    
  @defaultName: ->
    "Unidentified Person"

blake = new Person "Blake", "Williams"
blake.setName "Blake Anderson"
console.log blake.name()

vs

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  name() {
    return `${this.firstName} ${this.lastName}`;
  }

  setName(name) {
    [this.firstName, this.lastName] = name.split(" ");
  }
  
  static defaultName() {
    return "Unidentified Person"
  }
}

var blake = new Person("Blake", "Williams");
blake.setName("Blake Anderson");
console.log(blake.name());

I definitely like the way this is headed - no need for commas, no need to write function all over the place; but I'm in the "write less code" camp, so I prefer the former. However this is really a matter of taste so if you're happy with typing this. instead of @ and adding in all the extra curly brackets it definitely backs up Blake's statement that ES6 is a viable alternative.

That said, I much prefer CoffeeScript's implementation of super, though I can see why ES6 went the way they did allowing you to call a different method on the object's super.

Interpolation

Clearly ES6's and CoffeeScript's string interpolations are very similar; CoffeeScript interpolates for normal "strings", whereas ES6 uses `backtick escaped strings` (which are annoying to write in Markdown, by the way). CoffeeScript uses #{var} whereas ES6 uses ${var}. All much of a muchness at this point.

Where the difference really stands out is in the handling of whitespace - ES6 (or at least the 6to5 tool) includes all the whitespace between the `s (including newlines and indentation), whereas CoffeeScript either joins with a single space in the case of simple " strings or preserves all whitespace accounting for indentation level in the case of """ block strings. To my mind both of these behaviours are desirable, whereas ES6's is not, take for example:

(function() {
  function foo(bar, val) {
    if (bar) {
      var str = `This is quite a long first line
        so I wrap it to a second line and then
        append the value ${val}`;
    }
  }
})();

The output from passing this through 6to5's REPL is:

var str = "This is quite a long first line\n        so I wrap it to a second line and then\n        append the value " + val;

CoffeeScript equivalents:

do ->
  foo = (bar, val) ->
    if bar
      str = "This is quite a long first line
        so I wrap it to a second line and then
        append the value #{val}";
      str2 =
        """
        This is quite a long first line
        so I wrap it to a second line and then
        append the value #{val}
        """

produce

var str = "This is quite a long first line so I wrap it to a second line and then append the value " + val;
var str2 = "This is quite a long first line\nso I wrap it to a second line and then\nappend the value " + val;

I can't think of a situation where I'd prefer 6to5's implementation.

Fat Arrows, Default Arguments

Brilliant additions to the JS syntax, these behave the same as CoffeeScript's but with ever-so-slightly different syntax rules.

Splats

Another brilliant addition, but I find splats can be quite powerful in the middle of an argument list, particularly in Node.js-style callback situations so that the callback is automatically popped off the end. For example:

# Splat dereferencing
names = ["Alice", "Belinda", "Catherine", "Davidson"]
[firstName, middleNames..., lastName] = names

# Splat function args preserving callback
foo = (fn, args..., callback) ->
  results = (fn arg for arg in args)
  process.nextTick ->
    callback null, results

double = (a) -> a * 2

foo double, 80, 60, 40, (err, results) ->
  console.log results

Sadly ES6 only allows splats at the end, requiring you to then manually pop() the callback (or lastName), making your code longer and making you choose more variables names (and we all hate picking variable names right?):

var names = ["Alice", "Belinda", "Catherine", "Davidson"];
var firstName, middleNames, lastName, rest;
[firstName, ...rest] = names;
lastName = rest.pop();
middleNames = rest;

Structuring/Destructuring

I must say I do like that ES6 lets you leave true blanks when doing var [first, , last] = [1, 2, 3] but using an underscore or similar is a one character workaround.

ES6 does object de/structuring pretty much the same as CoffeeScript (var {a, b} = {a: 1, c:3}, var {foo: a, bar: b} = {foo: 1, baz: 3} and var c = {a, b}) however there's a slight circumstance where CoffeeScript does it better: when referencing properties off of the current object, e.g. c = {@a, @b} (var c = {a: this.a, b: this.b}).

Things ES6 has that I wish CoffeeScript had

It wouldn't be fair to try and paint CoffeeScript as a perfect language - it certainly is not without its faults. Here's a list of features from ES6 (and even ES3) that I miss in CoffeeScript:

  • The ternary operator a ? b : c (if a then b else c is too verbose for my taste; that said there's no way I'd give up the ? operator!)
  • Computed (dynamic) property names, {[getKey()]: getValue()} (see the links in this StackOverflow answer for some interesting history)

Conclusion

All in all ES6 is a great leap forward for JavaScript and my huge thanks to all the developers who have made this possible. Not mentioned above are many of the features that ES6 has added that don't involve syntax changes and hence CoffeeScript can use without changes, such as Proxies, WeakMaps and much more. (We even have yield now too.)

I still prefer CoffeeScript's syntax and find it very readable yet concise which significantly boosts my productivity. I would also find it hard to give up all CoffeeScript's various syntax sugars, such as: object?.property which doesn't throw if object is null/undefined; a ?= b, a ||= b, etc.; implicit returns; unless; improved switch; ranges [a..b]; array/string slicing with ranges arr[2..6]; for own k, v of obj; chained comparisons a < b < c; block RegExps; and many more!

Long live CoffeeScript and long live ES6!

@jazeee
Copy link

jazeee commented Sep 10, 2015

This is a great document. Thanks for the comparison rundown!

@sukima
Copy link

sukima commented Sep 19, 2015

FYI CoffeeScript currently (version 1.10.0) allows you to use computed (dynamic) property names using string interpolation:

foo = "#{foobar()}": "bar"
var foo, obj;

foo = (
  obj = {},
  obj["" + (fooBar())] = "baz",
  obj
);

try it.

@vietnogi
Copy link

Is it wrong to compare CoffeeScript with ES6? CoffeeScript is a preprocessor tool that is used to generate ES6. To me, the two things are on different layers and wouldn't replace each other. Its like comparing SCSS with CSS3 or possibly CSS4 (when it comes out).

@louy2
Copy link

louy2 commented Nov 2, 2015

@vietnogi
In the end it is the comparison between two languages that help people decide which one to use. C compiles to assembly, but people use C not because of that compilation step, but that it is easier to use.

@jazeee
Copy link

jazeee commented Dec 13, 2015

Regarding ternary operator (a ? b : c), CoffeeScript does have a nice, and perhaps most common use case option:

result = a ? b  #Note the spaces.
# result will be a if a? else it will be b

Often, when I want a ternary operator, it is because I want something if it exists, otherwise I want something else. So, while not a full ternary operator replacement, it is probably sufficient.

Note also, this is extensible:

result = a ? b ? c ? d ? e

is the same as

result = a if a? 
result ?= b if b?
result ?= c if c?
result ?= d if d?
result ?= e if e?

@dustinrjo
Copy link

👍
Well said. We need more voices resisting the surge of haters defaulting to "standard" is better.

@mwmessin
Copy link

mwmessin commented Jun 7, 2016

wow @sukima I didn't know about that. That was literally the only thing that made me envious of es6 over coffee.

So the verdict seems to be 'Anything es6 can do coffeescript can do better'. Is there any technical argument left in the debate? Is it just "cause standards!" at this point?

@benjie
Copy link
Author

benjie commented Jun 30, 2016

For what it's worth; I now write most new code in ES6.

From a language point of view I still stand by the above. When it's well written I find CoffeeScript a lot easier to read and write than ES6 - lacking the distraction of curly braces everywhere, and a much terser syntax. (I know many people that have written a lot of CoffeeScript and don't agree with this - I guess it comes down to personal taste in the end.) There's a lot I miss from CoffeeScript - not least the safe navigation ?. operator (which even the latest Ruby 2.3 now has via &. - everyone's jumping on that band wagon!); however ES6's tooling is miles ahead of CoffeeScript's and CoffeeScript seems to have stagnated at ES3 with no plans to support later versions even with a special command line parameter.

It was nice to get yield support in CoffeeScript but without support for let / const, async / await, ES6 modules syntax, the rest operator for objects (const {a, b, ...rest} = blah and const o = {...obj1, a, b, ...rest}), and a lot of the other ES6/7 goodness it's becoming more and more painful to stick with - especially for React and Redux workflows. Many of the people who were moving the language forward have done a sterling job, and I thank them deeply for it (and for the effect that's had on JS), but many have now moved on or no longer have time to spend on keeping CoffeeScript relevant.

ESLint is incredible - it's a far more flexible and thorough linter than coffeelint, e.g. checking that you've define the propTypes for the props you use in a React component - that's pretty incredible! It's also easily extensible so you can add your own lint rules, and because it integrates with Babel you can use bleeding edge syntax with your lint rules. Babel is amazing too, I think that goes without saying. Sure you can (and I do!) use these tools after the CoffeeScript has compiled down to JS, but it's non-optimal. Plus then you're going to have all the CoffeeScript helpers, and then all the babel helpers on top of that - that's going to increase your bundle sizes!

What I would really like to see is a CoffeeScript-like preprocessor built on top of babel/acorn that can allow me to write a CoffeeScript-like syntax whilst still reaping the benefits of the ES6/7 environment. Alas, I don't have time to write it myself just yet...

@max-degterev
Copy link

@benjie great addition, I find myself coming to the same conclusions, esp after CJSX has been discontinued.

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