

During July's Hackweek, the three of us rewrote Dropbox's full browser-side codebase to use CoffeeScript instead of JavaScript, and we've been really happy with how it's been going so far. This is a controversial subject, so we thought we'd start by explaining why.
CoffeeScript:
#"https://dropboxtechblog.files.wordpress.com/2015/02/javascript_iteration.wav">
By the way, the JavaScript has a scoping bug, did you catch it??
We've heard many arguments against CoffeeScript. Before diving in, we were most concerned about these two:
.coffee files and compiled.js equivalents. Anything needing an update gets compiled. Compilation is imperceptibly fast thanks tojashkenas and team. This means we didn't need to change our workflow whatsoever, didn't need to learn a new tool, or run any new background process (nocoffee--watch). We just write CoffeeScript, reload the page, loop.Especially considering the strange, difficult and rushed circumstances of its origin, JavaScript did many things well: first class functions and objects, prototypes, dynamic typing, object literal syntax, closures, and more. But is it any surprise that it got a bunch of things wrong too? Just considering syntax, things like: obscuring prototypical OOP through confusingly classical syntax, thevar keyword (forgotvar? congrats, you've got a global!), automatic type coercion and== vs===, automatic semicolon insertionwoes, thearguments object (which acts like an array except when it doesn't), and so on. Before any of these problems could be changed, JavaScript was already built into competing browsers and solidified by an international standards committee. The really bad news is, because browsers evolve slowly, browser-interpreted languages evolve slowly. Introducing new iteration constructs, adding default arguments, slices, splats, multiline strings, and so on is really difficult. Such effortstake years, and require cooperation among large corporations and standards bodies.
Our point is to forget CoffeeScript's influences for a minute, because it fixes so many of these syntactic problems and at least partially breaks free of JavaScript's slow evolution; even if you don't care for significant whitespace, we recommend CoffeeScript for so many other reasons.Disclaimer: we love Python, and it's Dropbox's primary language, so we're probably biased.
An interesting argument against CoffeeScriptfrom Ryan Florence, that seemed plausible to us on first impression but didn't hold up after we thought more about it, is the idea that (a) human beings process images and symbols faster than words, so (b) verbally readable code isn't necessarily quicker to comprehend. Florence uses this to argue that (c) while CoffeeScript may be faster to read, JavaScript is probably faster to comprehend. We'd expect cognitive science provides plenty of evidence in support of (a), including the excellent circle example cited by Florence. (b) is easily provenby counterexample. Making the leap to (c) is where we ended up disagreeing:
On to some code samples.
JavaScript
if (files) { BrowseDrag._update_status_position(e, files);} else if (e.dataTransfer && e.dataTransfer.types && e.dataTransfer.types.contains('Files')) { CrossDomainUploader.show_drop_indicators(e);}CoffeeScript
if files @_update_status_position e, fileselse if e.dataTransfer?.types?.contains 'Files' CrossDomainUploader.show_drop_indicators eJavaScript
this.originalStyle = {};['top', 'left', 'width', 'height'].each(function (k) { this.originalStyle[k] = this.element.style[k];}.bind(this));CoffeeScript
@originalStyle = {}for k in ['top', 'left', 'width', 'height'] @originalStyle[k] = @element.style[k]JavaScript
Sharing = { init: function (sf_info) { [sf_info.current, sf_info.past].each(function (list) { list.each(function (info) { Sharing._decode_sort_key(info); }); }); }}CoffeeScript
Sharing = init: (sf_info) -> for list in [sf_info.current, sf_info.past] for info in list @_decode_sort_key infoWe'll let this comparison speak for itself. We consider it our strongest argument in favor of CoffeeScript.
| JavaScript | CoffeeScript | |
|---|---|---|
| Lines of code | 23437 | 18417 |
| Tokens | 75334 | 66058 |
| Characters | 865613 | 65993 |
In the process of converting, we shaved off more than 5000 lines of code, a21% reduction. Granted, many of those lines looked like this:
}); }); }}Regardless, fewer lines is beneficial for simple reasons — being able to fit more code into a single editor screen, for example.
Measuring reduction in code complexity is of course much harder, but we think the stats above, especially token count, are a good first-order approximation. Much more to say on that subject.
In production, we compile and concatenate all of our CoffeeScript source into a single JavaScript file, minify it, and serve it to browsers with gzip compression. The size of the compressed bundle didn’t change significantly pre- and post-coffee transformation, so our users shouldn’t notice anything different. The site performs and behaves as before.
Rewriting over 23,000 lines of code in one (hack)week was a big undertaking. To significantly hasten the process and avoid bugs, we usedjs2coffee, a JavaScript to CoffeeScript compiler, to do all of the repetitive conversion tasks for us (things like converting JS blocks to CS blocks, or JS functions to CS functions). We'd start converting a new JS file by first compiling it individually to CS, then manually editing each line as we saw fit, improving style along the way, and making it more idiomatic. One example: the compiler isn't smart enough to convert a JS three-clausefor into a CSfor/in. Instead it outputs a CSwhile withi++ at the end. We switched each of those to simpler loops. Another example: using string interpolation instead of concatenation in places where it made sense.
To make sure we didn't break the site, we used a few different approaches to test: