Modern Frontend Workflow in a .NET World Part 4: ECMAScript 2015 and Babel

Originally posted on the DCS Innovation Labs Blog.

All code for this post is available on github.

Programming languages evolve over time thanks to user feedback that exposes pain points in current implementations. JavaScript of course is no exception to this and recently received a major update in the form of ECMAScript 2015 (also commonly known as ES6 and ES2015). This update includes all kind of things that make JavaScript a "big boy" language such as constant variables, and classes. It also introduced a ton of great syntax sugar in the form of arrow functions, and string interpolation. Finally, we got some exciting new features such as generators and promises. All of this plus much more make now a great time to be a JavaScript developer.

There is a problem innate to web development though, the fact that we can only use shiny new features as soon as the oldest browser we're willing to support implements them. For ECMAScript 2015, things don't look so good with (at the time of writing) Safari 10 being the only browser with 100% compliance, and it's still in beta. The majority of us have to support IE11 still and it's only at 11% compliance.

It's obvious that we can't just go and develop a production site using these new features. Thankfully, there is a way that we can "transpile" our shiny ECMAScript 2015 code down into much more widely accepted ES5 code with a brilliant tool called Babel.

ECMAScript 2015 adds a plethora of cool new features, and unfortunately there just isn't time to cover them all in any sort of detail. Today I'm going to focus on the features that I use most often and find most helpful. We'll look at:

  1. Installing Babel
  2. Using Babel
  3. let variable declarations
  4. const variable declarations
  5. Arrow functions
  6. Template literals

Like in my last post on getting up and running with Sass, we'll be using NPM to install Babel. If you aren't familiar with NPM, check out my post on installing and using it.

For this post I'm going to be using PowerShell on Windows but the same steps apply on Bash, ZSH, and pretty much any other Bash-like shell.

1) Installing Babel

Let's get Babel up and running on your machine.

Just like in my last post, we're going to be installing a command line tool to do our transpilation. So run npm install -g babel-cli.

C:\Users\JDarling> npm install -g babel-cli  
C:\Program Files\nodejs\babel-doctor -> C:\Program Files\nodejs\node_modules\babel-cli\bin\babel-doctor.js  
C:\Program Files\nodejs\babel-node -> C:\Program Files\nodejs\node_modules\babel-cli\bin\babel-node.js  
C:\Program Files\nodejs\babel-external-helpers -> C:\Program Files\nodejs\node_modules\babel-cli\bin\babel-external-help  
ers.js  
C:\Program Files\nodejs\babel -> C:\Program Files\nodejs\node_modules\babel-cli\bin\babel.js  
C:\Program Files\nodejs

...

C:\Users\JDarling>  

You may get an error message when installing babel-cli:

npm WARN optional Skipping failed optional dependency /babel-cli/chokidar/fsevents:  
npm WARN notsup Not compatible with your operating system or architecture: [email protected]  

This is an optional dependency that doesn't work on Windows. I haven't had any troubles using Babel without this so it should be safe to ignore.

Babel should now be available as a command line executable (restart your shell if it isn't).

2) Using Babel

The Babel CLI works similarly to the node-sass CLI that we saw in my previous post.

Let's test it out by creating a simple JavaScript file called test.js with some ECMAScript 2015 code in it:

(function () {
  'use strict'

  let printName = () => {
    console.log('Jonathan Darling')
  }

  printName()
})()

Now let's run Babel on this file using babel test.js --o test.es5.js. If I look at my directory after running this I will see the new test.es5.js file:

C:\Projects\es6_playground> ls


    Directory: C:\Projects\es6_playground


Mode                LastWriteTime         Length Name  
----                -------------         ------ ----
-a----        8/18/2016  10:16 AM            152 test.es5.js
-a----        8/18/2016   9:13 AM            126 test.js


C:\Projects\es6_playground>  

Take a look at the contents of that file:

(function () {
  'use strict';

  let printName = () => {
    console.log('Jonathan Darling');
  };

  printName();
})();

Nothing really changed except that we got some extra semicolons.

With it's default settings, Babel actually won't do anything to a file when it's run on it. This is because Babel uses transform plugins to transpile files and by default, it doesn't come with any plugins installed. It's perfectly possible to go through and specify exactly which plugins we want to run individually, but with there being 19 ECMAScript 2015 plugins at the time of writing, specifying all of these is a pain. Luckily, there are presets that are available for us to require as NPM dependencies.

To try this out, let's go ahead and initialize NPM with npm init. Next we are going to install the "ES2015" Babel preset with npm install --save-dev babel-preset-es2015. The --save-dev parameter works the same as --save except that is saves the dependency as a "dev dependency", keeping it separate from production dependencies.

So now we have the preset downloaded, but we still have to tell Babel which plugins to use. You can do this by creating a file called .babelrc or by adding a "babel" entry to your package.json. I prefer to use package.json myself as it keeps my configuration centralized. To specify that we will use the "ES2015" preset, add the following entry to your package.json:

"babel": {
  "presets": [
    "es2015"
  ]
},

So your package.json should look something like this:

{
  "name": "es6_playground",
  "version": "1.0.0",
  "description": "",
  "main": "test.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "babel": {
    "presets": [
      "es2015"
    ]
  },
  "devDependencies": {
    "babel-preset-es2015": "^6.13.2"
  }
}

Let's run Babel again with babel test.js -o test.es5.js. We should now see that our file has been transpiled to use ES5 syntax:

'use strict';

(function () {
  'use strict';

  var printName = function printName() {
    console.log('Jonathan Darling');
  };

  printName();
})();

There's one last tip I want to share before we start diving in to these ECMAScript 2015 features. Just like with node-sass, Babel also has a --watch flag that can be used like so: babel test.js -w -o test.es5.js. Using this, Babel will automatically transpile your file when you make changes.

3) Let variable declarations

Prior to ECMAScript 2015, we would use the var keyword to declare our variables. There is one obvious problem with var and that is the fact that it is function scoped instead of block scoped. This means that if I declare a variable in an if block for example with the same name as a variable outside of the block, it will overwrite the original:

(function () {
  'use strict'

  function varTest () {
    var x = 1
    if (true) {
      var x = 2  // This overwrites the variable declaration above.
      console.log(x)  // 2
    }
    console.log(x)  // 2
  }

  varTest()
})()

Adapted from MDN Example. Code available on github.

let is truly block scoped, so the variable you define in your if block won't be overwritten:

(function () {
  'use strict'

  function letTest () {
    let x = 1
    if (true) {
      let x = 2  // This does not overwrite the variable declaration above.
      console.log(x)  // 2
    }
    console.log(x)  // 1
  }

  letTest()
})()

Adapted from MDN Example. Code available on github.

As I mentioned earlier, this ECMAScript 2015 code using let won't work in older browsers. Running Babel gives us:

'use strict';

(function () {
  'use strict';

  function letTest() {
    var x = 1;
    if (true) {
      var _x = 2; // This does not overwrite the variable declaration above.
      console.log(_x); // 2
    }
    console.log(x); // 1
  }

  letTest();
})();

Code available on github.

Babel accomplishes the same thing as let by simply renaming the variable in the block scope.

4) Const variable declarations

Constants in JavaScript have always been a convention but haven't been enforceable. You have always had to hope that another developer would see var MY_CONST = 5 and do the nice thing and not modify it. ECMAScript 2015 introduced the const keyword allowing us to actually have constant values in JavaScript.

If you run the following code, you will get a Type Error and execution will stop:

(function () {
  'use strict'

  const MY_CONSTANT = 5

  MY_CONSTANT = 10 // This throws an exception "TypeError: Assignment to constant variable.".
})()

Code available on github.

Babel will not actually let you transpile this code as it is smart enough to know that you can't reassign a constant.

5) Arrow functions

If you have written much JavaScript before, you're probably familiar with callback hell and the hundreds of anonymous function declarations that you are bound to have in even a mildly complex application. Arrow functions simply provide a more concise syntax for anonymous functions. While using them here and there won't save too many keystrokes, the saving add up quickly as apps grow larger and readability becomes an issue.

For example:

function (thing) { console.log(thing) }  

becomes

thing => { console.log(thing) }  

and

function (x, y) { return x + y }  

becomes

(x, y) => x + y

A few more examples showing off the syntax:

(function () {
  'use strict'

  function callAnonymousFunction (fn) {
    fn()
  }

  function callAnonymousFunctionOneArg (fn) {
    fn('test2')
  }

  function callAnonymousFunctionTwoArg (fn) {
    fn('test3.1', 'test3.2')
  }

  function callAnonymousFunctionLogReturn (fn) {
    console.log(fn())
  }

  callAnonymousFunction(() => { console.log('test1') }) // 'test1'

  callAnonymousFunctionOneArg(thing => { console.log(thing) }) // 'test2'

  callAnonymousFunctionTwoArg((thing1, thing2) => { console.log(thing1, thing2) }) // 'test3.1 test3.2'

  callAnonymousFunctionLogReturn(() => 'test4')
})()

Code available on github.

Of course, Babel can translate this back to ES5 no problem:

'use strict';

(function () {
  'use strict';

  function callAnonymousFunction(fn) {
    fn();
  }

  function callAnonymousFunctionOneArg(fn) {
    fn('test2');
  }

  function callAnonymousFunctionTwoArg(fn) {
    fn('test3.1', 'test3.2');
  }

  function callAnonymousFunctionLogReturn(fn) {
    console.log(fn());
  }

  callAnonymousFunction(function () {
    console.log('test1');
  }); // 'test1'

  callAnonymousFunctionOneArg(function (thing) {
    console.log(thing);
  }); // 'test2'

  callAnonymousFunctionTwoArg(function (thing1, thing2) {
    console.log(thing1, thing2);
  }); // 'test3.1 test3.2'

  callAnonymousFunctionLogReturn(function () {
    return 'test4';
  });
})();

Code available on github.

6) Template literals

Template literals, synonymous with string interpolation, allow for a variable to be inserted in the middle of a string. While quite handy in many languages, JavaScript in the browser is commonly used to build large templates, a task that is rather painful and requires a ton of manual concatenation, making it even more valuable.

The syntax is very simple to grasp:

'I love to program ' + language + ' every day!'  

becomes

`I love to program ${language} every day!`

Simply use backticks instead of quotes, and surround your variables with ${} and you're good to go!

Here's another sample showing off another awesome feature, multiline strings:

(function() {
  'use strict'

  let myInfo = {
    firstName: 'Jonathan',
    lastName: 'Darling',
    occupation: 'Frontend Developer',
    company: 'DCS Innovation Labs'
  }

  let myInfoTemplate = `
Hi! My name is ${myInfo.firstName} ${myInfo.lastName}.  
I am a ${myInfo.occupation} at ${myInfo.company}.  
`

  console.log(myInfoTemplate) // Hi! My name is Jonathan Darling.
                              // I am a Frontend Developer at DCS Innovation Labs.
})()

Code available on github.

Babel will transpile this down to:

'use strict';

(function () {
  'use strict';

  var myInfo = {
    firstName: 'Jonathan',
    lastName: 'Darling',
    occupation: 'Frontend Developer',
    company: 'DCS Innovation Labs'
  };

  var myInfoTemplate = '\nHi! My name is ' + myInfo.firstName + ' ' + myInfo.lastName + '.\nI am a ' + myInfo.occupation + ' at ' + myInfo.company + '.\n';

  console.log(myInfoTemplate); // Hi! My name is Jonathan Darling.
  // I am a Frontend Developer at DCS Innovation Labs.
})();

Code available on github.

All of this and so much more

ECMAScript 2015 has done a ton to make JavaScript more enjoyable to program in. I just scratched the surface with some of the basic functionality adds in this article but there are many many more great features worth checking out. I found this concise guide incredibly valuable when I first started learning ECMAScript 2015 and I still reference it all the time. I hope that this post got you to a point where you know how to get Babel working and has sparked your interest in learning more of the great features that you can use today in ECMAScript 2015.