Version Info & Repository
(This tutorial is written using Babel v7 however the general concepts will apply to any version)
You can find the official repository for the Understanding the Modern Web Stack tutorial series here.
This includes the final versions of the code examples from each tutorial to help make sure you haven't missed anything. You can also submit pull requests for any errors or corrections you may find (and I will update the blog posts accordingly).
Table of Contents
- What is Babel
- Prerequisites
- Initializing the Project
- Installing Babel
- Transforming Your Code
- Polyfills
- Wrapping Up
What is Babel?
Babel is a tool that lets you write your Javascript code using all the latest syntax and features, and run it in browsers that may not support those features. Babel is a transpiler that will translate your modern JS code into an older version of Javscript that more browsers are able to understand.
Babel is often built into the tools that we use every day to built modern web applications (like create-react-app for example) so many developers don't have a full understanding of what the tool actually does. This tutorial is designed to set up a Babel configuration piece by piece and is part of a larger tutorial series in setting up your own custom development environment.
Prerequisites
You will need to have Node.js installed on your machine and available from your terminal. Installing Node will automatically install npm as well, which is what you will use to install Babel.
Open up your terminal of choice. If you see version numbers when running the two commands below (your numbers will likely be different than this example) then you are ready to go:
node --version
> v15.5.0
npm --version
> 7.16.0
Initializing the Project
Let's start by initializing a new npm
project. Run the following command to generate one:
npm init -y
The -y
flag will automatically select default values for everything, which is appropriate in our example.
Next let's create a very basic Javascript file using some modern syntax. Create a file called script.js
with the following code:
script.js
const x = 5;
let y;
const sampleFunction = () => "this is a return value";
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
const hasThree = [1, 2, 3].includes(3);
console.log(hasThree);
y ||= "a new value";
console.log(y);
In the above example const
, let
, the arrow function
, includes
array method and class
are all features of ES6 that won't run properly in older browsers such as Internet Explorer 11
(which unfortunately some organizations still use extensively even in 2021).
You may also catch the brand new (as of 2021) logical OR assignment. This will not run in versions of Firefox before 79 and versions of Chrome before 85, and it won't run in IE11 at all.
So what can we do to run this code on older browsers without having to rewrite it ourselves?
Installing Babel
There are three basic packages we need to accomplish our goal, all of them are part of the Babel ecosystem, but each has a different function. Start by running the following command:
npm install @babel/core @babel/cli @babel/preset-env --save-dev
Let's take a look at what each one is doing:
-
@babel/core
- This is the main engine that knows how to transform code based on a set of instructions it is given -
@babel/cli
- This is the actual program we are going to run to trigger the core engine and output a transformed Javascript file -
@babel/preset-env
- This is a preset that tells the core engine what kind of transformations to make. It looks at your environment (in our case it will be ourpackage.json
file) to determine what kind of changes need to be made depending on the browsers you wish to support.
We need to add a couple values to our package.json
file:
-
browserslist
- This tells Babel which browsers we are aiming to target. The older / less supported they are, the more work and more transformations Babel will have to make in order for your application to work in these browsers. The syntax is a simple array of strings. You can learn about here. -
babel
- This is where we defined all the presets that we will use, as well as any configuration options related to those presets. We will start with the simplest one,@babel/preset-env
So our package.json
file should look like this:
package.json
{
"devDependencies": {
"@babel/cli": "^7.15.7",
"@babel/core": "^7.15.5",
"@babel/preset-env": "^7.15.6"
},
"browserslist": ["last 2 Chrome versions"],
"babel": {
"presets": [["@babel/preset-env"]]
}
}
The devDependencies
should already be there from your npm install
. The other two properties described above you will need to add yourself.
Transforming Your Code
At its most basic configuration babel
will transform your modern syntax into the much wider supported ES5.
Let's begin with a simple example. Run the following command in your project root directory containing your package.json
file and your script.js
file:
npx babel script.js --out-file script-transformed.js
Presuming you have followed all the instructions so far you should see a new file created called script-transformed.js
that looks like this:
script-transformed.js
"use strict";
const x = 5;
let y;
const sampleFunction = () => "this is a return value";
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
const hasThree = [1, 2, 3].includes(3);
console.log(hasThree);
y ||= "a new value";
console.log(y);
Not much different right? Aside from adding strict mode nothing has actually changed.
The reason for this is because of how we have configured our environment in package.json
, which is where @babel/preset-env
looks in order to decide what it should do.
package.json
...
"browserslist": [
"last 2 Chrome versions"
],
...
Since we are only targeting the most recent 2 versions of Chrome, Babel knows that we have no problem including all the modern JS syntax that we want, it will work fine in those modern browsers.
But let's say that we are required to support Internet Explorer 11
. We don't want to have to change the way we write our code just to accommodate that browser, but fortunately that's where Babel saves the day. Update your package.json
to add IE11 to your browserslist
array:
...
"browserslist": [
"last 2 Chrome versions",
"IE 11"
],
...
Now run this command again:
npx babel script.js --out-file script-transformed.js
Take a look at the output this time:
script-transformed.js
"use strict";
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var x = 5;
var y;
var sampleFunction = function sampleFunction() {
return "this is a return value";
};
var Person = function Person(name, age) {
_classCallCheck(this, Person);
this.name = name;
this.age = age;
};
var hasThree = [1, 2, 3].includes(3);
console.log(hasThree);
y || (y = "a new value");
console.log(y);
This looks a lot different from our original file! Notice that almost all the ES6
terms we discussed above are gone, const
is replaced with var
, our arrow function is replaced with function
syntax, and our class
has been transformed into a basic Javascript object. We can now take this script-transformed.js
file, serve it up to Internet Explorer 11
and it would run just fine... almost!
We still have one small problem: The includes
method was not transformed. Why is that? To understand the reason, we first need to understand polyfills
.
Polyfills
To understand why we need polyfills
we have to understand the difference between what is new syntax
and what is new functionality
. The includes
method is new functionality
. It has its own logic behind it, and simply changing the syntax of how the code is written won't explain to older browsers how the logic of the includes
method is supposed to function.
For new features that introduce new functionality we need something called a polyfill. Polyfills are simply just the source code for a method like includes
that you bundle along with your application to essentially teach older browsers how it works.
You do not need to write polyfills yourself, polyfills for just about every feature of JS already exist and are easy to include. In future tutorials we will get into bundling and only including the specific ones that we need, but until then we can simply include a library called core-js and instantly give our app access to all modern JS features even on older browsers.
To test it out let's load the entirety of the core-js
library into our app. Since we are still not yet using a bundler, we will simply load the already bundled and minified
version from the web into our app. If you don't already have an index.html
template, create this file in your project root directory:
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<script src="script-transformed.js"></script>
</head>
<body></body>
</html>
(If you aren't sure how to serve this file run and view the output check out this tutorial first)
We'll begin by trying to load the file in IE11. If your machine doesn't have Internet Explorer 11, you can simply follow with the example here. This example is running on Windows 11
which has removed IE11 entirely. Fortunately Microsoft Edge
comes with an IE 11 Mode for testing applications that require backward compatibility.
When we run Babel and try to load our script-transformed.js
file in IE11 we get the following error on the console:
Now let's add the core-js
library to a <script>
tag inside the <head>
in our index.html
. You can find the most updated minified bundle URL here.
index.html
...
<head>
<meta charset="UTF-8" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/core-js/3.18.1/minified.js"></script>
<script src="script-transformed.js" defer></script>
</head>
...
When we reload the page in IE11 we get:
It works! We're writing modern Javascript and running it in an old dinosaur browser! That's great!
Wrapping Up
You should now have a solid grasp of the fundamentals of what Babel is, and how it works. Of course there's a lot more to discover. In future tutorials we'll go deeper into two more of the major presets that are supported by Babel for transpiling supersets of Javascript: JSX and Typescript.
When we start working with webpack
we will also look at how to configure Babel so that it only imports those functions from the sizable core-js
library that you are actually using in your application (like Array.includes()
) so that you don't need to include the entirety of the library itself.
Please check out the other entries in this series! Feel free to leave a comment or question and share with others if you find any of them helpful:
-
Webpack
@eagleson_alex on Twitter
Thanks for reading, and stay tuned!
Top comments (6)
It's so nice to be able to not care about any of this when I'm building stuff just for fun. Who cares if something doesn't run on anything other than the latest browser versions with experimental features enabled? xD
Nice article though; absolutely bookmarking this :D
It's so easy to follow along your articles!
Keep up with being so clear!
Looking forward for more content from you in this topic :D
btw, maybe I'm wrong but
@babel/plugin-env
should be replaced with@babel/preset-env
in the following sentence:"which is where @babel /plugin-env looks in order to decide what it should do."
Glad it's helpful!
You're totally right on
babel-preset-env
, I've made the correction, thanks.Nice article,
Well as u said Transformation is needed in older browser, as a dev let's we need to supporting older browsers as well.
Will this not be an impact for a user running latest browser?
Because ES5 syntax and lot of overhead functions on top of it in your bundle.
What iam basically trying to understand is is there any way that we can give ES5 bundle for not supported ones, and ES6 bundle for latest browsers based on User Request during run-time?
That's a great question! Yes doing that is absolutely possible, it's just one of those things that really comes down to a decision about whether the effort is worth the payoff.
Basically the process would be to have two separate babel configuration files, and generate separate builds, one aimed at modern browsers and one aimed at legacy browsers. Your
target
would decide the definition of what modern and legacy means to you.You would then upload both bundles to your server and have custom code on the server side that decides which one to serve to the user based on the User-Agent header of their HTTP request:
developer.mozilla.org/en-US/docs/W...
So it's very do-able, just adds extra overhead so it's a matter of weighing the pros vs cons. In my experience personally, the extra ES5 overhead has never been enough to impact performance to the point where we've bothered to run two separate builds, but that's not to say the requirements might be different for another organization.
Cheers!
Nice articles, very helpful!