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
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:
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!
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?
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.
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?
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.
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.