Experimenting with ES6

As part of the Yell Blog’s tech series, Junior Front End Developer Stevie Burrett road tests ES6, the latest version of JavaScript. He finds cleaner, more manageable code and native module support, but tackles transpilation, performance and browser support issues along the way.


After years of wrangling, the final specification for the latest version of programming language ECMAScript was approved by Ecma in June last year. ES2015, previously called ES6, is the latest iteration of the language commonly referred to as JavaScript, and it brings all sorts of improvements and new features to the language, including native support for modules, classes, arrow functions, template literals, iterators, promises and more.


It’s one of the largest updates to come to JavaScript in years, and will pave the way for cleaner, better-written, more manageable code that’s less reliant on libraries like jQuery, RequireJS and more. Hot on its heels is ES2016 (also known as ES7), set to be released later this year, as Ecma hopes to increase the cadence of updates to the language going forward.


With more and more interesting new features and applications available, it’s an exciting time to be a web developer.

Too bad we can’t use any of it, yet

It may have been nearly a year since the specification for ES6 was finalised, but browser support for its spec is still a mixed bag. The current generation of web browsers have fairly respectable levels of support for the new standards. According to the ECMAScript 6 compatibility table, which compares the ES6 support of all major browsers and JavaScript runtimes, as of writing this post Chrome has the best support at 91% of the specification, Firefox comes in second on 85%, new kid on the block Microsoft Edge is just behind on 79%, while Safari is bringing up the rear at 53%.

ES6 compatibility of various browsers and compilers. Source: kangax.github.io

Updates to each of these browsers are in development, which will fill in the remaining gaps over the coming weeks and months, but how well this generation of browsers support the ES6 spec doesn’t really matter for one key reason: legacy.


A large percentage of global web traffic still comes from older web browsers with limited or non-existent support for these new programming standards. IE11, the most recent version of Internet Explorer, (the mortal enemy of web developers everywhere), has only 15% support for ES6, while older versions like IE8 offer no support at all.


All of these versions of Internet Explorer were introduced well before ES6 was finalised, but their level of usage remains significant – tracking from StatCounter shows 18% of all webpage views in 2015 were done via Internet Explorer, while NetMarketShare calculates IE usage to be 43% of global web traffic.


That’s just Internet Explorer. There are a wide variety of browsers in use today with limited or no support for ES6. Around one third of our overall traffic at Yell.com still comes from users on browsers with little or no support for ES6, including older versions of Safari on iOS, the Android Browser, Opera and more.


While it would be nice for us to concentrate on the bleeding edge technologies, the fact of the matter is it’s currently not possible for us to cast aside such a significant portion of our user base so we can use something new and shiny. As a result, for the moment, we’re unable to use any of the exciting new features that ES6 brings.


There is a way for us to write shiny new ES6 code while still supporting legacy browsers, thanks to a process called transpilation. A transpiler takes the ES6 we write and converts it back into ES5 code which is fully supported by every browser. Several tools exist to do this – some even do it on-the-fly in the browser itself. The tool I’ve been experimenting with is Babel.


Babel is very straightforward – you give it some ES6 code, tell it what features to convert, and it spits out the converted ES5 code at the other end. A fresh install of Babel won’t do anything however  – you need to specify what to convert by adding plugins. A large library of plugins exist to convert a large variety of features, from experimental ES7 syntax all the way down to legacy ES3 code. Thankfully you don’t have to collate a list of all the plugins needed thanks to pre-prepared presets: the ES2015 preset automatically loads all the required plugins to convert all ES6 features into ES5 code – perfect for our needs.


Babel can be used directly from the command line, or integrated into a wide variety of build systems and frameworks – you can customize your install to suit whichever tools your project uses. At Yell we use Grunt, and adding Babel to our build process was as simple as adding a new task:

//Grunt task to transpile ES6 scripts 
module.exports = function (grunt, options) { 
    return {
        options: {
            sourceMap: true,
            presets: ['es2015-without-strict']
        desktop: { 
            files: [ { 
                expand: true, 
                cwd: path/to/javascript/, 
                src: [**/*.es6], 
                dest: path/to/javascript/, 
                ext: '-6.js'
            } ] 

Running that task tells Babel to search for all files with a .es6 extension, transpile it from ES6 to ES5, and output to the same directory a file with the extension -6.js. Simple!


Once the code has been transpiled to ES5, it can be served and executed just like any other JavaScript code. From there you can test and debug it. But since you’re not running the same JavaScript that you wrote (it can look very different, in fact), how are you supposed to do that?


Thankfully, Babel natively supports producing source maps during the transpilation process. When you run your application in the browser, you can load the source map file and then add break points, step through and inspect your code just as if the ES6 were being executed directly.

How does Babel fare?

Yell.com’s JavaScript code is broken down into various modules using the Advanced Module Definition (AMD) API. These modules may be specific to a particular page type, or may be stand-alone modules loading in various locations across the site. Many modules are lazy-loaded when required in order to limit the amount of data a user agent has to download when initially visiting the site.


We also use a browser’s local storage to cache our JavaScript. We have a variety of unit tests run against our site to check functionality, and performance testing functions to monitor the performance of each module. In order for us to use Babel in Yell.com, all of this must continue to work in all our modules, old and new.


To test Babel, I converted two of our modules to use ES6 syntax – the SendEmail module, which handles sending email messages to merchants on Yell.com, and the AdvancedSearch module, which displays and handles our Advanced Search form across the site. I selected these because they are small, stand-alone modules with only a small number of dependencies. It’s always a good idea to walk before you can run.


The main change I made was to construct the modules using the ES6 syntax rather than AMD. Each of these modules had various dependencies written in our old AMD format. Rewriting all of our existing code to use the ES6 style isn’t really a practical option to let us run new ES6 code – the new modules have to work alongside the old ones seamlessly. I didn’t expect that this was going to be easy – I wasn’t completely  sure it was possible. But,  much to my surprise, it was.


Babel is able to convert ES6-style modules into AMD style, which can then be imported by other modules as normal. In the ES6 modules themselves, import statements can be used to import ES6 or AMD modules. The converted AMD modules are structured differently from the ones we write ourselves, but RequireJS, the library we use to load modules, handles their interaction seamlessly. Don’t use AMD? Not a problem, Babel can be configured with plugins to convert into other module styles such as CommonJS and UMD.


When loaded on the actual site, the ES6 modules function exactly like their AMD counterparts, lazy-loaded just as we expect. The ES6 fairy dust I’d sprinkled over them, like string templates and constants, were converted into ES5 equivalents and ran without issue. Loading the source map into the browser let me add break points and step through functions just as I would expect.  It was working perfectly.

Debugging ES6
Debugging transpiled ES6 code in Chrome using source maps
Not exactly…

Well, not quite. There are a few quirks and limitations I encountered. Some were minor, some more probably deal-breakers. First, Babel’s restructuring of the code in the module resulted in the time tracking functions used to monitor performance being relocated inside the module, making the results incomparable. This is a pretty minor issue, this functionality could be injected by the build process, and it didn’t affect the module’s functionality, but it’s worth noting that transpiled code may not be an exact lexical equivalent of the code you initially write. It’s easy to imagine this resulting in arcane, hard to track errors further down the line.


Then there’s the significant limitations of ES6 modules themselves. The main one is that import statements must be at the top level of the module. They cannot be within function scope or conditional, which means lazy loading (something which we uses extensively at Yell) is not possible using pure ES6 syntax. You’ll still need to use your own library to do this.


In fact, a library is still required even if running ES6 code natively – the specification defines the syntax for modules, but the loader API was dropped from the final version – it likely won’t appear until ES7 emerges. Until it does, I would stick with whatever module system you’re currently using to prevent having to mix different syntaxes together.


There are also limitations from using transpilation in this manner. The key one is that all users will be served transpiled ES5 code even if their browser supports all ES6 features. This will mean that your site will continue to rely on libraries and polyfills to support many features, even if the browser could deal with natively, resulting in unnecessary code being downloaded and the loss of any performance improvements you would have gained from using ES6 features directly.


Babel’s modular nature provides some help here. Due to its ability to selectively enable and disable which features are transpiled, you can remove features from Babel’s configuration when a critical mass of users have updated to modern browsers that support a given feature. Slowly over time more and more features can be ignored as support improves, until eventually we can serve ES6 code unmodified without a variety of polyfills and libraries to back it up.

What about performance?

A major argument against using ES6 code in any form right now is performance. As ES5 has been around since 2009, there has been plenty of time for the developers of all the major JavaScript engines – V8, Spidermonkey, Chakra and JavaScriptCore – to optimise their JavaScript runtimes as much as possible.


ES6 hasn’t been around anywhere near long enough for the same optimisations to be made to the browser engines or the transpilers. As a result, operations carried out using ES6 code, whether directly or transpiled, will run far slower in general than equivalent ES5 code.


In some cases this may only be slight, but in others it can be as much as two orders of magnitude. That kind of performance hit alone will make using ES6 completely unsuitable for a lot of applications right now. On the flip side, some operations can run the same amount faster, depending on the compiler and browser combination used.

Extract of kpdecker’s chart showing the relative performance of various ES6 functions compared to ES5 and transpiled code. Source: kpdecker.github.io

This chart keeps track of the relative performance of various operations between native ES5, ES6 and transpiled code. It really depends on the nature of your code and its usage scenarios whether or not ES6 is right for you. Performance is improving across the board over time, but there’s still a long way to go before ES6 will be suitable for everyone.

We’re not there yet

Given how long it took for IE6 to fade away, legacy browsers like IE8 are likely to hang around for a few years yet and it’s going to be quite a while before support for ES6 is universal. But just because we can’t serve ES6 code to everyone today doesn’t mean developers should just ignore it altogether.


Transitional technologies like Babel give developers a chance to get ahead of the curve and look to the future, rather than concentrating on the past.


Currently, ES6 has some significant limitations, particularly around performance, which mean it’s too early to go all-in right now. But given how simple setting up transpilation can be, it’s well worth experimenting with, if only so you’re prepared for ES6 when it is ready.