The ES6 Conundrum

ECMAScript 6 or ES6 is the evolution of JavaScript and it is the future. It is the technology innovation for the web we’ve all been waiting for. It is full of great functionality we always craved and it finally enables us to build huge applications for the web in a scalable and easy to maintain manner. It empowers thousands of developers coming from Java or C# or other “higher” languages to finally write JavaScript and bring all the goodness of their experience to a market in dire need of organization and direction. That’s the sales pitch and – to be fair – a lot of it is true.

However, it also makes a lot of assumptions as to what needs fixing on the web. And there is a real problem with evolving a language as ubiquitous as JavaScript without having control of the environments it is executed in. The web is a distributed platform. It is much harder to change the wheels on a vehicle that is constantly in motion than on one you can bring back to the shop, lock up, upgrade and drive out again.

JavaScript doesn’t seem to cut it any longer

The, shall we say, lenient architecture of JavaScript has always been a source of confusion for programmers coming from other languages. Especially the lack of classes and prototypical inheritance is a no-go for a lot of people. It feels counter-intuitive and it doesn’t represent what is being taught in schools about computing. To make matters worse, we have the beautiful, but confusing construct of closures and a lack of constants and type safety. All that meant that JavaScript got a reputation for being badly architected and not to be trusted with large code bases. Despite all this, it managed to evolve into one of the most used languages in software development now – to a large part thanks to the rise of the web.

ES6 and the following versions of the language are meant to do away with these annoyances and make JavaScript a better, more reliable and more efficient language.

This, to be fair, is nothing new. We had a few languages that built on JavaScript in the past and tried to fix these issues. TypeScript, Dart, CoffeeScript and even Flash’s ActionScript all tried to solve the same issue. The difference was that they all needed some kind of conversion or container to show up in the browser. ES6, now that it is finalized, is intended to be native to the browser and run just like JavaScript using a script element pointing to it. ES6 has a lot of impressive features:

  • Arrow functions as a short-hand version of an anonymous function.
  • Block-level scope using let instead of var makes variables scoped to a block (if, for, while, etc.)
  • Classes to encapsulate and extend code.
  • Constants using the const keyword.
  • Default parameters for functions like foo(bar = 3, baz = 2)
  • Destructuring to assign values from arrays or objects into variables.
  • Generators that create iterators using function* and the yield keyword.
  • Map, a Dictionary type object that can be used to store key/value pairs. and Set as a collection object to store a list of data values.
  • Modules as a way of organizing and loading code.
  • Promises for async operations avoiding callback hell
  • Rest parameters instead of using arguments to access functions arguments.
  • Template Strings to build up string values including multi-line strings.

Considering what we’re doing with JavaScript these days, this seems to be a real need. Gaming, App Development and even server side development all are possible in JavaScript. And with each environment we have developers that are used to different tools and methodologies. The wild west web development of the past seems to be counterproductive to the performance and maintenance needs of today. This reminds me of the time when we moved from DHTML to DOM Scripting. Order, we need order and reliability.

Where this falls apart is that currently ES6 is far from ready to be deployed on the web. That is not the fault of the language, but of the nature of the web. We can’t and shouldn’t dictate what people use to surf the web. But the support for ES6 in different current browsers is not encouraging.

36_es6_conundrum_img01

A comprehensive ES6 support grid maintained by Juriy Zaytsev – red is bad. (click to enlarge)

The bigger problem is that ES6 breaks the device and browser-independent idea of the web for the first time in the history of JavaScript.

Breaking the Web With New Syntax

The problem with ES6 is not what it does to the language – JavaScript has always been extended with new methods and APIs. All we needed to do to use them safely was to test if the current browser or environment knows about this functionality before applying it. This is called progressive enhancement and means that we never deliver broken experiences.

When you test for the existence of a certain method before calling it, you’re safe. Much like checking that a river is deep enough before jumping in is a good idea. The problem with ES6 is that it breaks backwards compatibility as it introduces many syntax changes to JavaScript instead of just new methods and objects.

That shouldn’t be an issue, but unlike all the other languages used to build “things” on the web (consciously skirting the sites vs. apps debate here) JavaScript is not fault tolerant.

The following HTML is not a problem for a browser:

1
<p><span>Nesting</p></span>

Internally this gets fixed and the browser goes on its merry way to render the rest of the page:

36_es6_conundrum_img02

Firefox Developer tools showing how the HTML5 parser fixed the wrongly nested HTML

This fault tolerance was one of the main ideas of HTML5. The HTML5 parser is extremely forgiving as we knew that developers make errors and our users shouldn’t suffer for them. An XHTML document with a single error would fail to render. This wasn’t good enough – we needed a sturdier web as the end user experience trumps over everything else. This is even defined as a design principle of HTML as the Priority of Constituencies:

In case of conflict, consider users over authors over implementors over specifiers over theoretical purity. In other words costs or difficulties to the user should be given more weight than costs to authors; which in turn should be given more weight than costs to implementors; which should be given more weight than costs to authors of the spec itself, which should be given more weight than those proposing changes for theoretical reasons alone. Of course, it is preferred to make things better for multiple constituencies at once.

CSS parsers have the same attitude towards code. For example the paragraphs in a document with this CSS applied to it will be a light orange.

1
2
3
  p {color: microsoftblue;}
  p {color: peachpuff;}
  p {colour: powderblue;}

“peachpuff” is a valid name for a CSS colour, whereas “microsoftblue” is not. Whilst “powderblue” is also a valid colour, the gramatically properly spelled “colour” needs to be “color” in CSS, which is why it doesn’t get applied. In essence, whatever line of code the CSS parser can’t deal with is skipped.

This fault tolerance doesn’t work in JavaScript, which is why it is the most fragile part of the web stack. Any issue that causes a JavaScript error means that the whole script doesn’t get executed – there is no tolerance in the browser.

When people discuss the need to progressively enhance on the web this often gets forgotten. It is not about end users that have JavaScript disabled – those are a very small minority. It is about all the things that can go wrong before JavaScript is executed and when the browser finally tries to run it. Stuart Langridge maintains an interesting decision tree telling you all the things that can go wrong from requesting your script to its execution.

ES6 introduces a lot of changes to the syntax of JavaScript. Whilst an ES6 compatible browser or preprocessor has no problem with the following code, for a non-ES6 browser this is nothing but a syntax error.

1
2
3
  function showperson(ismember = 0, hasphoto = 0, ...moar) {
  /* … */
  }

This presents us with a big problem. Using ES6 is not possible unless we limit ourselves to the few browsers that already support it.

Microsoft Edge, Firefox, Chrome and iOS Safari all have a good sub-set of ES6 implemented. However, not all these browsers are what our users have, and we can not assume people upgrade all the time. Sadly enough, there is a lot of hardware out there that comes with a non-upgradeable OS that has an outdated browser hardwired in it.

Feature Detecting Syntax Support?

One very interesting approach to this issue is Featuretests.io by Kyle Simpson. It is a very small JavaScript library that allows you to test for ES6 features and thus only load your ES6 scripts when and if the browser supports them. So to say, progressive enhancement on a syntactical level.

Using this library, you can test the browsers you have and see what they support. A quick check on my machine right now shows this:

Firefox 39 (OSX)

Microsoft Edge (Windows 10 VM)

Chrome 43 (OSX)

Safari 8.07 (OSX)

  • ArrayStatics
  • MathStatics
  • NumberStatics
  • Promise
  • Proxy
  • TypedArrayMethods
  • TypedArrays
  • arrow
  • collections
  • computedProperty
  • conciseMethodProperty
  • constRedef
  • defaultParameter
  • destructuring
  • forOf
  • generator
  • letTDZ
  • numericLiteral
  • objectProto
  • oldOctalLiteral
  • parameterDestructuring
  • spreadRest
  • stickyRegExp
  • symbol
  • symbolImplicitCoercion
  • templateString
  • ArrayMethods
  • ArrayStatics
  • MathStatics
  • NumberStatics
  • Promise
  • Proxy
  • StringMethods
  • TypedArrayMethods
  • TypedArrays
  • arrow
  • collections
  • computedProperty
  • conciseMethodProperty
  • constRedef
  • forOf
  • letConst
  • letTDZ
  • numericLiteral
  • objectProto
  • oldOctalLiteral
  • spreadRest
  • symbol
  • symbolImplicitCoercion
  • templateString
  • unicodeEscape
  • unicodeIdentifier
  • unicodeRegExp
  • MathStatics
  • NumberStatics
  • Promise
  • StringMethods
  • TypedArrays
  • “class”
  • classes
  • collections
  • conciseMethodProperty
  • constRedef
  • extendNatives
  • forOf
  • generator
  • letConst
  • letTDZ
  • numericLiteral
  • objectProto
  • objectSuper
  • oldOctalLiteral
  • symbol
  • symbolImplicitCoercion
  • templateString
  • MathStatics
  • Promise
  • TypedArrays
  • computedProperty
  • constRedef
  • forOf
  • letTDZ
  • objectProto
  • oldOctalLiteral
  • parameterDestructuring
  • symbolImplicitCoercion: true

I am not trying to compare browsers here – this is a waste of time, seeing how quickly that changes. I want to show is that when it comes to support of ES6 in browsers, there are quite some differences in between them. This makes feature testing awkward as it is only safe when you test all the things you want to use. Assuming support for other features when you test for only one is a slippery slope.

If you go all the way and test for each and every feature you want to use your code will not break. It can, however, easily turn into overkill. The point of ES6 for a lot of developers is not to use the features piecemeal but write the whole app in ES6 from the start.

That’s nothing new. When HTML5 and CSS3 was a whole hype word and we couldn’t wait to use it, we got a lot of “this cool thing only works in Safari” or “you need to use Chrome to see this site” as a lot of the features were still in flux. These products are still on the web, largely unmaintained and browsers need to include a lot of cruft into their codebases to not break the web. We wanted too much, too fast and we didn’t clean up afterwards.

A lot of effort went into allowing developers to feature detect everything automatically using Modernizr. For legacy browser support, this is still a very good idea, but in a more defined environment, developers found it to be an unnecessary overhead. Instead, we started to set a baseline of support and only give scripting functionality and advanced styling to those browsers that make the cut. The developers at the BBC called this “cutting the mustard” over three years ago and their baseline is these few checks:

1
2
3
4
5
  if ('querySelector' in document &&
  'localStorage' in window &&
  'addEventListener' in window) {
  // bootstrap the javascript application
  }

Maybe this is something we could think of for our ES6 work, too? Define a few gatekeeper features that get checked and go forward from there?

If you don’t want to be dependent on browser support and all you want is to use ES6 in its entirety, you’ll need to use something that converts your code. This could be an own language like TypeScript or a so called Transpiler (as it translates and compiles ES6 to JavaScript).

Transpiling to the Rescue?

The good thing about JavaScript these days is that it escaped the confines of the browser and is also usable server-side. node.js uses the V8 engine of Chrome as a standalone binary and also can now use Microsoft’s Chakra JS engine. This allows us to use any JavaScript conversion tool to take ES6 code and convert it to good old JavaScript that runs in any browser. If you’re already using task management using Grunt or Gulp, this could be just another task to run before deployment.

There are a few options out there. The most known transpilers are Traceur that originated from Google and Babel, which originally was called 6 to 5, but as ECMAScript evolves even as we try to implement it, there was a need for a more generic name.

Transpiling seems to be the safest way right now to use ES6 in real projects without worrying about support discrepancies across environments. It also ties in nicely to the workflow of developers who are used to stricter and class based languages. But, on closer introspection, there are some weird drawbacks:

  • First of all, transpiling in most cases doesn’t do any feature detection at all – the ES6 code gets fully converted to ES5 (or in some cases even down to ES3, should you want to do that). This means that browsers that natively support ES6 will never get the code. It makes the implementation in the browser a bit redundant, and – even worse – it doesn’t allow for testing the performance and validity of the ES6 implementation in the browser.
  • Traditionally the web was view-source. That’s how a lot of us in the past learned how to code for it. We looked at the source and found out what others used, and our debugging process was doing the same, after seeing in an error console that something broke. Nowadays we have developer tools for that, with much more advanced features. However, if we transpile the code, we never write the code that gets executed. The code we debug in the browser is what the transpiler created, which is optimised for performance, not for readability. In order to debug our code we need to find a way to connect the generated code with the written code. For this, we use source maps. This also applies to CSS that gets generated by Sass or less.
  • Generating code from other code could lead to massive files. That the original code we write is compact, clean and structured is not necessarily the important thing when the end result means the user has to download megabytes of JavaScript for an interface that could be much more lightweight.

What About Performance?

Whenever we introduce new functionality to browsers, there is the question if we make things faster or if we slow things down. We just don’t know until we gathered enough data to find out where to optimize. In the case of ES6, that data gathering can’t really happen if we transpile the code. Right now, the performance of ES6 natively in the browser does not look too encouraging as this speed report by Kevin Decker shows.

36_es6_conundrum_img03
(click to enlarge)

This report shows the performance of ES6 features relative to the ES5 baseline operations per second. Everything not dark green is slower than JavaScript. Everything not dark green is either slower or identical to ES5 performance. This means that only the spread operator, Promises, Maps and Sets bring performance benefits.

ES6 Matters and Needs our Attention

Right now, ES6 is in a weird place when it comes to web development as a whole. It is a standard, it is well supported (more than, for example Web Components) but it also breaks backwards compatibility. Instead of praising all the great things it does and show examples that may or may not work for you, I’d like to invite you to take part in the discussions around ES6. This is why I want to end this here with a few questions for you to ponder and we’d love to hear your opinions in the comments.

If our main purpose is to create web content with our coding efforts, we need to ask ourselves a few questions:

  • Do we let developer convenience trump over end user experience? Is it more important to deliver more, quicker than deliver the best performing solution for a certain environment?
  • Is it OK to build larger apps that expect more of the end user’s environment when it is easier, faster and more maintainable for us?
  • Does being able to change an app easier and extend it in a more defined way make up for locking out users?
  • Is it high time that we draw a line in the sand and make JavaScript safer and easier to extend?
  • Was the concept of ongoing backwards compatibility of the web flawed? Are we holding ourselves back by catering to the lowest common denominator?
  • Are the new features of ES6 really a benefit or do we just try to catch up and repeat what other environments did? Is this really the best we can do seeing that software as a whole might be broken?
  • Are the benefits of these changes worth the effort it needs to use them? Should new developers coming to the web have to use transpilers, preprocessors and task runners to get started?
  • How can we help browsers be better in supporting ES6 when transpilers create ES5?
  • Is using a subset of JavaScript, like TypeScript, the better option?
  • Is it the right direction to abstract the code we write away from the code the browser gets? Has view-source outlived its usefulness?
  • Is ES6 just another way of working on the web – much like converting bytecode to JavaScript using emscripten is or Flash was? A precompile step might seem weird to a web developer, but someone coming from Java is totally OK with it. In other words, is ES6 not for everybody, but we try to make it that?

The next few months will be exciting around ES6, and its very nature means there will be ongoing discussions and talks for quite a while. It appeals very much to those who didn’t like the flexibility and randomness of JavaScript. And it confuses those who are firm in their use of JavaScript and feels like a lot of overhead for not much immediate benefit. Evolution always comes with growing pains. Time to speak up and try out what works for you.

More Hands-on With JavaScript

This article is part of the web development series from Microsoft tech evangelists on practical JavaScript learning, open source projects, and interoperability best practices including Microsoft Edge browser and the new EdgeHTML rendering engine.

We encourage you to test across browsers and devices including Microsoft Edge – the default browser for Windows 10 – with free tools on dev.modern.IE:

In-depth tech learning on Microsoft Edge and the Web Platform from our engineers and evangelists:

More free cross-platform tools & resources for the Web Platform:

Chris Heilmann

Chris Heilmann is a Senior Program Manager at Microsoft and literally wrote the handbook on developer evangelism. He spends a lot of time speaking at conferences, blogging and helping people understand and make others understand technology better. You can follow him on Twitter as @codepo8 and read his blog at christianheilmann.com