Babel Polyfills
Not all ES6 features were created equal. Some are more along the lines of syntactic sugar, meaning they were designed to make the language easier to code in and to read. Some other features can be rewritten by ES5—even generators. If we take a quick peek at the generator section of ES6 features, we see that it’s possible to convert the generator code to ES5 code.
Things get a bit more complicated with features like promise or weakMaps, which can’t be duplicated by any means in ES5. Why is this you ask? When we use promise for example, we assume that there is a global variable called Promise. WeakMap also works this way.
So if we try to compile the code with promise in Babel, we’ll see that is does in fact compile, but that promise remains. This will undoubtedly cause an error in non-ES6 browsers or Node.js versions from v1.2.0 and down.
Let’s look at this nice little piece of code in ES6—code that’s we’ve already seen actually:
let myPromise = new Promise(function(resolve, reject) {
let request = new XMLHttpRequest();
request.open('GET', 'http://api.icndb.com/jokes/random');
request.onload = function() {
if (request.status == 200) {
resolve(request.response); // we got data here, so resolve the Promise
} else {
reject(Error(request.statusText)); // status is not 200 OK, so reject
}
};
request.onerror = function() {
reject(Error('Error fetching data.')); // error occurred, reject the Promise
};
request.send(); //send the request
});
myPromise.then(function(data) {
console.log('Got data! Promise fulfilled.');
document.getElementById('container').textContent = JSON.parse(data).value.joke;
}, function(error) {
console.log('Promise rejected.');
document.getElementById('container').textContent = 'Sorry, something wrong. please refresh';
console.log(error.message);
})
It has both let and promise. If we compile it in Babel with the ES2015 preset that’s supposed to take all the ES6 features and convert them to ES5, we’ll see the following result:
'use strict';
<strong>var</strong> myPromise = new <strong>Promise</strong>(function (resolve, reject) {
var request = new XMLHttpRequest();
request.open('GET', 'http://api.icndb.com/jokes/random');
request.onload = function () {
if (request.status == 200) {
resolve(request.response);
} else {
reject(Error(request.statusText));
}
};
request.onerror = function () {
reject(Error('Error fetching data.'));
};
request.send();
});
myPromise.then(function (data) {
console.log('Got data! Promise fulfilled.');
document.getElementById('container').textContent = JSON.parse(data).value.joke;
}, function (error) {
console.log('Promise rejected.');
document.getElementById('container').textContent = 'Sorry, something wrong. please refresh';
console.log(error.message);
});
The compiler got rid of the comments and changed let to var, but didn’t touch promise! But why?
It’s because here were talking about a global object and Babel’s way of dealing with global features is slightly different if we’re working in the browser or in Node.js. There are a few way to deal with this, and the method we’ll examine here is with Polyfills. These are pieces of JavaScript that give completely new functionality to browsers and runtime environments. For example, in CSS we used to use polyfills that would simulate round corners or other HTML5 features in old browsers.
So polyfills are simply a piece of JavaScript code that simulates an inherent function in outdated browsers. The promise polyfill checks if the global object promise exists, and if so, signals that the browser or runtime environment supports promise. If not, it stops the global object promise and puts in the functionality that’s supposed to be there—for example the methods then, resolve and reject, all in ES5 and all totally kosher. This way browsers and environments that don’t support ES6 will know what to do with these methods. It doesn’t run quite as smooth as the real promise, but it does run.
In order for it to run in the browser, I just need to run the piece of code with the polyfill. This is a simple .js file that we use a regular include with. We install the polyfill module like this:
npm install --save-dev babel-polyfill
And we use include like so:
<script src="node_modules/babel-polyfill/dist/polyfill.js"></script>
If you’re using node, it’s even easier. Just execute require right when the code starts to run—make sure it runs at the first entry point:
require("babel-polyfill");
If you’re using Whatsapp or something else, there are other ways which we’ll talk about another time.
So it’s pretty easy and simple, right? But there are a few things to keep in mind:
The polyfills must run before hand
This mean we must be certain that the polyfill code runs before any code that relies on the features of that polyfill. This is because if that code runs before the polyfill, each call to promise in an old environment will fail. In other words, we need to make sure that the polyfill runs before everything. If by chance you get an error like this:
var promise = new Promise(function(resolve, reject) {
^
ReferenceError: Promise is not defined
It is a sign that there is a call to promise before the polyfill.
Polyfills are heavy, because they contain all the new features of all the standards (including ES6 aka ES2015, and all those that come after). If we only need the polyfill of one specific feature, then we can install only that one. It’s a bit more complicated, but actually there are more than a few polyfill libraries, the most common being the one that Babel relies on. We can take the polyfills we need from it.
Polyfills put in global objects/methods
The polyfill puts a global promise (or other global objects) into the code. This is likely to cause a significant problem if there is another module or code block that handles it. If you write a plugin or module, don’t assume that this is a global promise object, but rather use babel-plugin-transform-runtime, a Babel add-on. In the next article we’ll cover add-on in detail.
The issue of polyfills is likely to be a bit confusing. If you feel a bit lost, just remember to include the Babel polyfill, whether it uses promise or other global objects, and to use all the features that you want without worry. If you’re feeling brave you can check the exact features you’re using, and implement the specific polyfill assignments via core-js, or some other way. In most cases though, in order to support old browsers, we’ll just put the Babel polyfill into the application and forget about it.
You’re probably asking yourself why this isn’t done automatically. The reason is that not everyone want to use polyfills for three reasons which we mentioned in a previous article. Version 6 of Babel allows for modularity and for everyone to use what they want, without stuffing everything down your throat. If we want a polyfill and we have promise or any other global object, we’ll use it. If not, then we won’t. And don’t forget that in the future Babel is supposed to work with ES2017 and up, and is supposed to have compatibility with ES6 from ES2018, so this modular approach will be critical going forward.
Next article: coming soon! |
About the author: Ran Bar-Zik is an experienced web developer whose personal blog, Internet Israel, features articles and guides on Node.js, MongoDB, Git, SASS, jQuery, HTML 5, MySQL, and more. Translation of the original article by Aaron Raizen.
Recent Stories
Top DiscoverSDK Experts
Compare Products
Select up to three two products to compare by clicking on the compare icon () of each product.
{{compareToolModel.Error}}
{{CommentsModel.TotalCount}} Comments
Your Comment