How to write ES6 code that’s safe to run in the browser

Intro

Since being introduced in 2015, browsers have been gradually supporting more and more of the handy features of ES6. This is music to the ears of developers who want a move away from the constraints and quirks of ‘old JavaScript’.

But there are some features (notably module imports/exports) that aren’t quite fully compatible with all browsers and certainly won’t ever be implemented in Microsoft Edge or Internet Explorer.

Also, what if you want to start using new features of the next-generation of the ECMAScript standard ES7/8 and beyond?

I used to be of the opinion that you should write your code in plain old ES5 JavaScript so that you can support the maximum level of users. That is, until I got involved in a new Microsite project at work that was using totally new concepts to me at the time: transpilation and bundling.

What is transpilation and bundling?

So, transpilation is the process of converting code from one language to another. This could be something like converting TypeScript to JavaScript but in our case, it means converting ES6 code to another version, such as ES5.

If transpilation converts our code to another, more compatible, version of JavaScript, where does the bundling term come from?

Well, in order to transpile our code we’re going to need some tool to carry out the process and we might as well merge all our code files together at the same time by ‘bundling’ them together. Hence where the term bundling comes from.

This tutorial will take you through the process of transpiling and bundling your code using Babel (transpiler) and webpack (bundler) so you’ll be able to write your application code with all the features of ES6 and be sure that it’s safe to run in most browsers. It can also be a good project to add to your portfolio if you’re looking for JavaScript project ideas to show off your tooling skills.

For the tutorial, you’ll need to have NodeJS installed and be relatively familiar with running a few commands on the command line (although i’ll take you through each part, step-by-step).

Setting up the project

In order to get going, we’ll set up our project by creating the necessary files, install the project dependencies then add a little bit of boilerplate code.

First off, from the command line, create a new folder to hold your project and navigate to it e.g.

mkdir es6-build
cd es6-build

Next, create an index.html file and a folder called src with a file called index.js inside it. You can use your text editor or run the following commands if you like:

touch index.html
mkdir src
touch src/index.js

Your project structure should then look like this:

├── index.html
└── src
└── index.js

Open your index.html file in your text editor and put in a simple bit of HTML boilerplate, you can copy the below if it helps:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>ES6 JavaScript</title>
  </head>
  <body>
  </body>
</html>

Finally, just so you have a bit of JavaScript code to work with, add the following to your index.js file:

const hello = (msg) => console.log(`Hello ${msg}`);
hello('World');

All this is doing is creating an ES6 arrow function that includes a template literal which basically just prints out ‘Hello World’ to the console when the file is loaded. If the above looks totally unfamiliar to you, take a look at the explanation of ES6 arrow functions and how they differ from the function keyword in JavaScript below:

Installing dependencies

Our simple project will require webpack and Babel to build and transpile our code successfully so we will install these packages as dependencies.

To do this, create a package.json file in your project.

The easiest way to do this is to run npm init in your project directory and accept all of the default options.

Once you have your package.json file setup, you can install webpack and Babel with the following command

npm install –save dev @babel/core @babel/preset-env babel-loader webpack webpack-cli

Let’s just run through quickly what each of these dependencies are:

  • @babel/core – the main Babel library which takes care of the transpilation process
  • @babel/preset-env – a configuration for Babel which makes sure our code will be transpiled to the right version
  • babel-loader – Is actually a kind of plugin for webpack to allow files to be passed to the Babel transpiler
  • webpack – The core webpack bundler code
  • webpack-cli – Allows webpack to run directly on the command line

We’ll now add a build script to your package.json file so we can call webpack from our project.

If you open up your package.json file in your text editor and edit the scripts property to add a new script of “build”: “webpack”. So your scripts section should look like this:

"scripts": {
  "build": "webpack",
  "test": "echo \"Error: no test specified\" && exit 1"
},

Now, from the command line if you type npm run build you’ll see some output from webpack and, once the build has completed, you will see a new folder dist in your project folder. This contains your processed code, ready to be used in your project.

You can now link to that JavaScript code in your HTML file. Open your index.html file and add the following script tag:

...
<head>
  <title>ES6 JavaScript</title>
  <script src="dist/main.js"></script>
</head>
…

If you then open the index.html file in your browser you will see the Hello World message in your console:

At this point we have got webpack running and bundling our code however it isn’t actually transpiling our ES6 code to ES5 yet. You can check this by inspecting the processed code either in your dist folder or inside your browser.

Can you see how the arrow function is still there? If this were running in an older browser, we might encounter an error at this point.

Adding transpilation to the webpack build

So far, what you have isn’t that impressive – webpack is simply taking our input JavaScript file in the src folder and running it through it’s build process and creating an output file in the dist folder.

So we will need to tell webpack to transpile the code by making a configuration file.

By providing webpack with a configuration, you can customise how it works including the version of the JavaScript produced in the output which is exactly what we want to create our browser-safe code.

Start off by creating a file in the root of your project and call it webpack.config.js

Inside it, add this boilerplate code which is the absolute basic configuration for webpack:

const path = require("path");
module.exports = {
  mode: 'development',
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "main.js"
  },
};

This is actually what webpack uses by default so we’re not adding anything new here.

You can see inside this configuration that you can change the default ‘input’ JavaScript file by updating the entry property but you can leave it as /src/index.js for this tutorial Also, you can update the output folder name and the bundled JavaScript filename in the output property if you wish.

In order to tell webpack to use Babel to transpile our code to be ES5 compatible, we need to create a rule. We’ll create one that matches any .js file and loads the file in to Babel for conversion. This is done by the babel-loader package we installed earlier on.

Our completed webpack config will look like this:

const path = require("path");
module.exports = {
  mode: 'development',
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "main.js"
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules)/,
        use: {
          loader: "babel-loader",
          options: {
            presets: ["@babel/preset-env"]
          }
        }
      }
    ]
  }
};

You can see we have added a rule to check for any .js file (except those in the node_modules folder) and then load these files into Babel and process them with the @babel/preset-env package which will allow us to use the latest JavaScript without having to worry about compatibility.

Save the above configuration and run npm run build again.

When you inspect the bundled JavaScript file either in the dist folder or inside your browser you will see the ES6 arrow function has been converted to an older-style ES5 function declaration.

Great stuff! Our ES6 code is now being transpiled and bundled by webpack and Babel.

A Look at Javascript Nodelist vs. HTMLCollection vs. DOMTokenList

Working with the DOM using vanilla Javascript has never been easier. You no longer need JQuery to select parts of a website.

Still, there are a few things you should know before you can confidently select and modify HTML elements.

The Nodelist

When you use document.querySelector, you get back a Nodelist. A Nodelist is simply a list of nodes. It can be in the DOM, but not necessarily.

A node is the basic building block of a webpage.

A Nodelist not only contains elements but also textnodes. It’s not live most of the time, meaning that e.g. if you add a new node after you have got your Nodelist, your Nodelist will not necessarily update.

A live Nodelist is, for example, .childNodes. It means that if you append a new child, your childNodes Nodelist will be updated to reflect the change without you having to get the Nodelist again.

You can loop through a nodelist via forEach or a for loop using its index.

The HTMLCollection

An HTMLCollection is always live and it’s always in the DOM.

document.getElementsByClassname and document.getElementsBytagname both return an HTMLCollection.

The main differenve between a Nodelist and an HTMLCollection is that an HTMLCollection can only contain HTML elements. So for example line breaks you put into your HTML code will not show up in an HTMLCollection but it will in a Nodelist.

You can loop through an HTMLCollection using either it’s index or the tag name.

The DOMTokenlist

The DOMTokenlist is a special type of list. You mostly encounter it as the return value of .classList. It is a space separated list that is also index.

A DOMTokenlist has its own methods, like add(), remove(), replace(), and so on.

In contrast, .className will also return all classes but in String format and you don’t have those special methods available. You do have String methods though.

Javascript Limitations

When a programming language changes as fast as Javascript, browsers need time to catch up with it. That’s why support for some of the ES6 (ECMA2016) is spotty on some browsers.

The biggest limitation I see with Javascript is the way it handles numbers. If you do anything with floating numbers, the result won’t be correct. Javascript will add some random decimals to the result.

There is also a limit to how long numbers can be. It’s around fifteen characters. Above that what happens?

Javascript is also an (arguably) interpreted language. Although there is some Just in Time compiling, from a performance point of view, it’s safe to say that it won’t ever be as performant as some of the compiled languages.

Leaked globals are often an issue. Some think it’s a design fallacy that Javascript allows it.

let myVar = "some text";

let myFunction = function () {
  ...
  myvar = "some new text";
  ...
}

In this short example, I have made a typo by using a lowercase v in myvar inside the function. Javascript won’t give an error message and sets up a new myvar variable in the global scope.