You've done it, you've built your first web application. It does everything you expect. You've tested it rigorously. It works everywhere, your Macbook, your iPhone, your friend's android. You're so proud of yourself, you send it to your parents; they tell you it's just a blank page.

You poke around and realize they're still using that Dell desktop they bought in 2006. It's running Windows 7. They're using Internet Explorer 10. Your web-app doesn't work on this machine.

So you Google how to make your app work in IE 10, or 11, or whatever. You're quickly flooded with acronyms and terms you've never heard before ES5, ES6, ESNext, ES2020, TC39, ECMA. What's a transpiler? People are debating about polyfills and ponyfills; is there a difference?

If you're overwhelmed, that's okay, it's a lot. This article will attempt to cover the entire ecosystem around shipping new-ish JavaScript to older browsers and how the community decides what features get added to JavaScript in the first place; let's dive in.

Let's Talk About ECMA

ECMA is the European Computer Manufacturers Association. They have many committees that handle many things. One of these committees is TC39 or Technical Committee 39. They maintain a specification, or spec, called ECMAScript. Whenever you hear ES5, ES6, or ESAnything, the ES refers to ECMAScript.

ECMAScript is the spec which JavaScript is built upon. It's a formal in-writing agreement of how JavaScript should work. Each browser has their own implementation of JavaScript but they all conform to the same spec (or they do their best). This is why your code may run slightly differently in Safari or Firefox or Chrome. The browser vendors are implementing an engine that takes your JavaScript and runs it on the machine.

ES5, ES6, ESNext

Now, this underlying spec is constantly changing. JavaScript gets new features and browsers evolve. In 2009, ES5 was standardized across browsers. This was the largest release for a long time. If you've ever read JavaScript: The Good Parts it was based on ES5.

It wasn't until 2015 that ES6 was released with major changes to the language. Some of the bigger features in ES6 included Promises, Arrow Functions, and Classes.

ES6 was a massive release. It was so big, and took so long, that TC39 decided to stop numbering ECMAScript altogether. At this point they decided to rebrand ES6 as ES2015 and brand all future versions by year. Examples include ES2016, ES2017, etc.

They also introduced the concept of ESNext, a living breathing version of the latest JavaScript. ESNext includes all the features that have been formally agreed upon by the committee.

A Note On ES6

You may be thinking: I thought ES6 was the latest version of JavaScript. This is a misnomer. For a number of reasons, people often refer to anything after ES5 as ES6. This is mostly due to the fact that ES6 was a very large release, and the last one to have a simple version number.

If you hear someone say ES6, they likely mean ESNext.

TC39

Technical Committee 39 meet semi-regularly, usually on the West Coast of the United States, but sometimes elsewhere. You can see all their meeting agendas here. They're made up of individuals from major tech companies who are members of ECMA. For most large companies, for example Google or Facebook, it costs about $70,000 for membership. There are significant discounts for very small or non-profit companies. You can see the full list of members here.

New JavaScript Features

Anybody can submit a proposal for a new JavaScript feature, but every proposal eventually needs a "champion" from the committee in order to be adopted. Proposals are submitted by opening a pull request on the proposals repo.

Features go through stages from 0 to 4. At the end of the year, all features in Stage 4 are considered in that year's version of ECMAScript. You can see the full list of finished proposals here.

Stage 0: Strawperson

These are strawperson proposals. This is the idea stage. The only requirement to get to Stage 0 is the initial PR.

Stage 1: Proposal

This is the stage where the committee agrees they will spend time discussing on the feature. They assign a champion to the proposal, determine the high level API, and potentially implement a demo. It is expected that a feature will spend a good chunk of time being worked on in Stage 1 before reaching Stage 2.

Stage 2: Draft

At this point, a there should be a formal specification for the feature. It may still have lots of TODOs and room for improvement, but it's mostly there. If a significant issue is discovered, the feature may have to go back to Stage 1.

This is when the feature is mostly complete. The committee agrees that they are unable to find any issues until someone attempts to implement the feature. If a browser vendor attempts to implement the feature and finds an issue, the feature may go back to Stage 2.

Stage 4: Finished

A feature is considered done when it's been implemented in not one, but two different browsers. For example, if a feature becomes stable in both Firefox and Chrome, it's considered finished. The committee will then merge in a pull request to the ECMAScript spec.

The full ECMAScript spec is available here.

Running Your Code

We often want to use the latest features in our code, but older browsers won't support them. To do this, we use transpilers and polyfills.

Transpilers

A transpiler is a type of compiler that takes source code written in a high level language and produces equivalent source code in another high level language. This is in contrast to a compiler, which takes source code written in a high level language and compiles it to a low level language like assembly.

Transpilers can also be used to convert from one version of a language to another one. This is how we can write JavaScript using the newest features but still execute our code in browsers that don't support them natively.

For example we can compile an arrow function to a traditional one.

const func = () => { console.log('foo'); } const func = function () { console.log('foo'); }

Babel

One of the most popular transpilers is Babel. Babel is the go-to test environment for early JavaScript features. It does this by implementing a rich plugin architecture that allows developers to test syntax while it is still in it's earliest stages.

TypeScript

TypeScript goes a step further and adds type-checking to JavaScript. As part of this, it offers a built in transpiler to build your ESNext code for older browsers. While Babel has the ecosystem to support Stage 2 and below features, Typescript does not.

Sometimes developers will chose to use both so they can get type-checking and the latest features.

Targets

When building our code we'll pick a target version of JavaScript. This is most commonly ES5 however we could compile to any arbitrary version of the ECMAScript spec. The tradeoff is that older targets generate more source code, but newer targets will run in fewer browsers.

The reason we often want to generate less source is performance. Fewer bytes means less to send over the network, less to parse, and less to execute.

Polyfills

Transpiling is enough for syntax changes, but what about new functions and classes? That's where polyfills come in. A polyfill is the implementation of a newer feature written in an older version of ES. The polyfill usually check if the browser has it's own version of the feature and if not inject it.

For example a fetch polyfill might look like this:

if (window.fetch === undefined) { window.fetch = function() { // Implementation of fetch here } }

Babel and Typescript will support a configuration to automatically include any necessary polyfills in your source code.

Under the hood, most most polyfills rely on a project called core-js.

Ponyfill

While polyfills automatically inject an implementation of the feature, ponyfills will treat them as any other dependency. There are a few advantages to this. Ponyfills don't polute the global scope. In addition, the implementation of a feature may vary between browsers and the polyfill. This allows engineers to use a consistent implementation of a feature.

For example a fetch ponyfill might look like this:

const fetch = window.fetch || require('fetch-ponyfill');

It is commonly debated which approach is better. I'll leave that exercise up to the reader.

Where You Run Your Code Matters

When deciding what version of ES to target and what polyfills to include, it's important to consider where your code is running.

We often focus on which browsers and which versions of those browsers we support, but JavaScript can run many places. For example, if you're building an Electron application, you may not need to transpile/polyfill at all. The same thing applies to node applications. If you're building for one specific environment, you only need to worry about how your code runs in that environment.

Thanks

I hope this post was useful! Follow me on twitter @MatthewGerstman for more content like this.

References