BazarJS: JavaScript Preprocessors
A new episode of our BazarJS series, this time devoted to JavaScript: its history, its past and its future. Along the way we look at the CoffeeScript and TypeScript preprocessors and get to know ECMAScript 6, only to realize that this time we can finally start taking JavaScript seriously.
A new episode of our BazarJS series, this time devoted to JavaScript: its history, its past and its future. Along the way we look at the CoffeeScript and TypeScript preprocessors and get to know ECMAScript 6, only to realize that this time we can finally start taking JavaScript seriously.
- CoffeeScript
- How to use CoffeeScript
- The rise and fall of CoffeeScript
- ECMAScript
- ECMAScript 6
- Functions: splats, default arguments, fat-arrow binding
- Comprehensions, destructuring assignments and lexical scoping
- Object-oriented classes
- String interpolation and block strings
- ...and that's just the tip of the iceberg!
- Can we use ES6 today?
- TypeScript
- What should you choose?
- That's all for today… next episode? JS frameworks!
JavaScript has a long history. It's 1995, in the middle of the browser war between Netscape Navigator 2.0 and Internet Explorer 1.0, and Brendan Eich is tasked with building a simple interpreted language aimed at amateur programmers, to complement Netscape's offering, which for more professional uses on the web was betting everything on Java applets. In Eich's own words:
Javascript had to "look like Java", only less so. It had to be Java's dumb kid brother or boy-hostage sidekick. Plus, I had to be done in ten days, or something worse than JS would have happened.
Given that starting point, it's fair to ask: how did we go from years of (well-deserved) mockery over its limitations and quirks to the latest statistics published by RedMonk, which rank JavaScript as the most widely used language in the world?
It certainly wasn't a straight path, and more than once people tried to get rid of it and replace it with something more "serious". To give a concrete example, the teams behind the first successful client-side web applications in history, Gmail and Google Maps, certainly didn't see JavaScript as an ally they could rely on.
The tools born in those years — GWT, Cappuccino, Pyjamas — clearly show that people wanted anything but to write complex web applications in JavaScript. Far better to work with typically desktop paradigms in Java, Objective-C or Python respectively, and leave it to a tool to "compile" the end result into native web technologies. JavaScript-as-bytecode, in other words, and nothing more.
CoffeeScript
As the years went by, people slowly began to trust the language, with increasingly "native" approaches to web development, free from the old logic of desktop development.
In 2009, with the release of CoffeeScript by Jeremy Ashkenas, we officially saw the birth and growth of a new, less negative and pessimistic attitude toward JavaScript, nicely captured in the project's own opening lines. What is CoffeeScript?
CoffeeScript is a little language that compiles into JavaScript. Underneath that awkward Java-esque patina, JavaScript has always had a gorgeous heart. CoffeeScript is an attempt to expose the good parts of JavaScript in a simple way.
The golden rule of CoffeeScript is: "It's just JavaScript". The code compiles one-to-one into the equivalent JS, and there is no interpretation at runtime.
In other words? JavaScript isn't the best language ever made, let's not kid ourselves... but if we look at ECMAScript 5, the latest edition of the JavaScript standard already implemented in all of today's browsers, well, it's not that bad either. By making a few surgical changes to the language to fix some of the glaring mistakes caused by the rush in which it was originally designed, we'd get something good and usable, while staying "close" to the web's native technologies!
How can we apply these surgical changes? Just like with GWT and the like, by having developers write in another language... but this time a language extremely similar to JavaScript, one that generates JavaScript code that is perfectly readable to a programmer, and as idiomatic as possible.
What kind of "mistakes" does CoffeeScript try to fix? Mostly syntax. JavaScript offers very few of the conveniences we are used to in most other modern languages:
- string interpolation;
- multi-line strings;
- default arguments;
- the splat operator;
- list comprehensions;
- destructuring assignments;
- OOP classes.
It's also extremely verbose... an endless repetition of curly braces, semicolons, this, function and return!
class Greeter
constructor: (@person, options = {}) ->
greet: (person) ->
fullName = "#{firstName} #{lastName}"
if person.name is "Stefano Verna"
"""
Hello #{fullName},
This is a multiline string, wow!
"""
else
"#{fullName}?! The hell are you?"
person =
firstName: "Stefano"
lastName: "Verna"
new Greeter(person).greet()CoffeeScript offers an elegant solution to each of these needs, introducing some fairly heavy changes to the language where necessary:
- white-space significance (goodbye curly braces, hello Python-style indentation);
- implicit returns (everything is an expression);
- arrow functions: a new, shorter syntax for defining functions (
->). - fat arrows: a variant of the arrow function that automatically binds the function to the scope in place when it is defined (
=>).
The result? Faster, more enjoyable development and terser code that makes the programmer's intent clearer, plus the guarantee of producing ECMAScript 3 compatible code (IE6-proof).
How to use CoffeeScript
You can compile CoffeeScript into JavaScript using the npm package coffee-script. Once installed, it provides a coffee binary that can optionally generate source maps:
npm install -g coffee-script coffee --compile --map --output lib/ src/
Of course, there are plugins for the main task runners (grunt-contrib-coffeescript, gulp-coffee), so you can fit compilation into a wider build process.
The rise and fall of CoffeeScript
The release of CoffeeScript inevitably led to years of doubts and holy wars over whether it was really necessary to write code in such a young language, so similar to JavaScript, just for the sake of adding a bit of syntactic sugar. Communities more open to innovation, such as Ruby/Rails, instead immediately adopted the language as one of their go-to tools.
Personal opinions aside, the fact is that CoffeeScript adoption grew enormously over time. It peaked in 2013, when it ranked 17th among the most used programming languages: a clear sign of a widespread need for modernization and renewal in the JavaScript community.
After that growth, from 2013 to today CoffeeScript usage has been slowly but steadily declining. The reason? Fairly obvious: ECMAScript 6.
ECMAScript
First of all, what is ECMAScript? It's the formal definition of the JavaScript language, maintained by a committee called Ecma TC39. JavaScript is one of the implementations of ECMAScript, just like ActionScript, for example.
The evolution of the ECMAScript standard, like JavaScript itself, hasn't had an easy life. After ECMAScript 3 was formalized in 1999, the next edition, ECMAScript 4, was supposed to bring major changes to the language. But the 2008 completion date was missed entirely and the edition was abandoned over political disagreements about how complex the resulting language would be.
After this failure, the committee decided to work on less ambitious editions with more incremental changes to the language: the first concrete result was ECMAScript 5, released in 2009, which describes the language we find implemented in all our browsers today (IE9+). The most important changes in this edition? Getters/setters and "strict" mode.
The committee's next planned step? Defining ECMAScript 6, originally scheduled for 2013 but now expected to be published around mid-2015.
And with ECMAScript 6, things get really interesting.
ECMAScript 6
Describing every language change in the new standard in detail would be a daunting and perhaps pointless task, given the excellent resources already available:
What stands out right away, though, is how almost all of CoffeeScript's syntactic helpers have been faithfully carried over into ES6, while keeping full backward compatibility with the language we know so far. ES6 is, to all intents and purposes, an extended superset of ES5.
Let's look at some of the most visible changes to the language:
Functions: splats, default arguments, fat-arrow binding
var foo = (filter = 'all', options = {}) => {
// ...
};
var bar = (first, second, ...others) => {
// ...
};
var object = {
init() {
$("a").click((el) => this.handleClick());
},
handleClick() {
/* ... */
}
};- You can finally set default values for function arguments;
- You can do globbing of parameters with a dedicated syntax (
...), instead of dealing with theargumentsobject; - The
functionkeyword is optional: inside object definitions you can use the shorthandnomeFunzione() {}; - With the fat-arrow syntax (
=>) you can force a function to bind to the current value ofthis, with an automatic return;
Comprehensions, destructuring assignments and lexical scoping
let collection = [ 1, 2, 3 ];
let [first, second] = collection;
for (el of collection) {
// ...
}
let generatePoint = (x, y) => { x, y }
let { x, y } = generatePoint(10, 20);- Creating objects and arrays, and extracting data from them, becomes much simpler thanks to destructuring assignments.
- You can iterate over the elements of an array (and more) with the
ofconstruct; - The
varkeyword is officially deprecated in favor oflet. Variable scoping in JavaScript has always been convoluted, so much so that a new term had to be coined to describe it (*variable hoisting*). Theletkeyword solves these problems by handling scoping the same way as Ruby and most other languages;
Object-oriented classes
class Lion extends Animal {
constructor(name) {
this.name = name;
this.pos = 0;
}
move(meters) {
this.pos += meters;
}
}
var lion = new Lion("Alex");JavaScript has always been a language with prototypal inheritance, a concept that is often unfamiliar to people coming from languages with classical inheritance. In any medium-sized project or library it becomes practically standard to use (or write from scratch) small JS libraries that mimic the typical behavior of an OOP class.
ES6 provides a set of dedicated constructs (class, extends, super) that achieve the same result with clearer intent and more consistent code.
String interpolation and block strings
var fullName = `${firstName} ${lastName}`;
var greeting = `
Hello ${fullName}! This is a multi-
line string!
`;Last but definitely not least, backtick string literals, which can span multiple lines and support variable interpolation.
...and that's just the tip of the iceberg!
ES6 doesn't stop at this kind of cosmetic "polish": it introduces concepts that were completely absent until now and can profoundly change the idioms and possibilities of the language:
- Metaprogramming: the
Proxyobject provides features similar to Ruby's classicmethod_missing, allowing methods and properties to be created dynamically on an object (DSL, anyone?); - Symbols: ES6 introduces a new primitive type,
Symbol, which can be used as a unique, private property name; - Generators: Ruby's
yieldlands in JavaScript too, enabling iterable objects (and more);
Can we use ES6 today?
At the moment, browser support for ES6 is very limited: Firefox currently implements the most features, but there's clearly still a long way to go before we can take full compatibility across most browsers for granted.
The 6to5 project in particular looks like the most promising and best supported by the community, with the highest compatibility with the standard.
You can use 6to5 standalone, as a REPL, through the usual task runners (grunt-6to5, gulp-6to5), and as a transform for Browserify and Webpack (6to5ify, 6to5-loader). You can even use it in Rails/Sprockets with the sprockets-es6 gem.
TypeScript
Special mention goes to TypeScript, a superset of ES5 created by Microsoft. Besides also letting you create classes with classical inheritance, the language's main feature is that it enables static typing and compile-time type checking of your JavaScript code through annotations:
// add.ts
function add(left: number, right: number): number {
return left + right;
}
add("Foo");
add(1);
var result = add(1, 1);
result.foobar;$ npm install -g typescript $ tsc add.ts add.ts(5,1): error TS2346: Supplied parameters do not match any signature of call target. add.ts(6,1): error TS2346: Supplied parameters do not match any signature of call target. add.ts(9,8): error TS2339: Property 'foobar' does not exist on type 'number'.
To define complex structures, its approach to typing relies on the concept of duck typing: an object's semantics are determined by its set of methods and properties rather than by extending a particular class or implementing a specific interface:
interface ObjectWithTitle {
title: string;
}
function printTitle(titledObject: ObjectWithTitle) {
console.log(titledObject.title);
}
var article = {
title: "This is awesome!",
publishedAt: new Date(),
content: "Foo bar"
};
printTitle(article);Getting started with TypeScript doesn't require a big investment, given:
- that static typing is optional: anything without annotations is still accepted as type
any. - the ability to accept a type definition header separately from the code, so you can add a typing layer even to third-party libraries[^1].
What should you choose?
At Cantiere we've always been big fans of tools like Sass and Slim, which make our daily work more pleasant, less repetitive and less error-prone. CoffeeScript fits right in, and that's exactly why we used it internally for almost four years (practically from its launch until today).
It's fair to say that Jeremy Ashkenas's work fully achieved its goals, even inspiring the work of the ECMA committee itself, which deserves credit for responding promptly to feedback from the outside world.
So far we've worked on just one project in ES6. It was a great success: all the annoyances and pain we used to feel with the language disappeared, and we never once missed CoffeeScript. For the first time with JavaScript, it felt like we were finally working with a mature language, highly expressive and able to properly support us in building complex client-side applications.
TypeScript is an interesting project too: compile-time type checking could reduce the number of "interface" errors between objects, and honestly the ideal would be to use the two languages together... unfortunately TypeScript is a superset of ES5, so for now the two languages are incompatible.
At Cantiere we're used to working with dynamically typed languages and protecting our code from type errors with an integration test suite that is as complete as possible. When push comes to shove, between the certain convenience gains of ES6 and the theoretical gains of TypeScript in reducing errors, we choose ECMAScript 6 without hesitation.
The TypeScript team, by the way, has already announced its plan to align the language with ES6 in its next version, 2.0, so our road test of TypeScript is only postponed by a few months.
In the coming weeks we'll keep exploring some of the most interesting and innovative ES6 features in dedicated posts.
That's all for today… next episode? JS frameworks!
The time has finally come to fire up the holy wars. The next episode will tackle the world of client-side frameworks, looking at the pros and cons of the three main contenders: Angular, Ember.js and React.