How to start using ES6 today?

Tags:

When you want to use ES6 you’re faced with a list of questions: Transpilers, shims, browser support…

In this article, I’ll give you a detailed guide on making sure you get started with ES6 the right way, and instructions on how you can set up a workflow for using ES6 in production easily.

Considerations before deciding on ES6

Before you decide to use ES6 in your app, there’s a few considerations.

  • Do I care about the ES6 spec changing?
  • What are my target platforms?
  • Do I need to debug on older devices or browsers?
  • Am I developing a performance-critical application?

Let’s address each of these before we go further.

ES6 spec changes

The ES6 specification is not finished. The way certain features work can change in future versions.

This means the tools you need to use in order to make ES6 features work in the current JavaScript version may also change, and break your existing code.

If you’re worried about potential changes to the specification and your code breaking, you should think twice before you jump in the ES6-wagon.

Target platforms

Your target platforms – devices, browsers, etc. – should be kept in mind when deciding whether or not to use ES6.

Using ES6 requires either the use of a transpiler or a shim. We’ll talk more about this later in the article, but if you need to use a transpiler, it will affect both your ability to debug the code and the performance of the code.

If you are one of the poor people who still have to deal with IE6 or even IE7, then you may be stuck on using a smaller subset of ES6 features.

Debugging

If you end up using a transpiler, it will generate compiled JS files for you. Sometimes this output can be difficult to follow, and this can make debugging difficult.

Thankfully modern browsers support source maps, which means you don’t need to worry about this so much. A source map will allow you to see the original ES6 code, rather than the compiled ES5 code.

However, if you target some older platforms, they may not support source maps. Many older browsers support ES5 just fine, and the transpiled code will work in them, but if you need to debug on those browsers it may prove to be more difficult.

This is less of a problem if you use a shim. The code in those is written by a human rather than generated, and should therefore be easier to understand.

Performance

This is also a primarily transpiler-related consideration. The ES6 transpilers don’t make any performance optimizations based on the browser running the code.

In some cases, this can result in code that doesn’t perform so well in all browsers. If you are working on something that needs to have very high performance, such as games, you may want to stick to “standard” JavaScript. However this doesn’t mean you shouldn’t use ES6 at all – simply avoid using ES6 features in performance critical sections of your code if you have problems with it.

Choice: Use a transpiler, or use a shim?

If you decide to use ES6, you need to make a choice: Which features of ES6 do you want to use?

ES6 provides both new syntax, such as let or the module system, but it also provides new objects and functions on existing objects. If you want to use the new syntax, you need a transpiler. If you only want to use the additional objects and functions such as Promises, the new String or Math functions or such, then you can use a shim.

Shims are simpler and easier to work with, as you only need to include it in your page or node app before other scripts. However, a shim cannot support syntactic features. If you want those, you need a transpiler.

Setting up ES6 Shim

If you only want to use the new objects and functions, you can use a shim. If you want new syntax, skip to the setting up traceur -part.

For this, we will use ES6 Shim.

ES6 Shim in nodejs

Setting the shim up in node is very simple:

Run npm install es6-shim

Then, include require('es6-shim'); in your node-scripts.

ES6 Shim in the browser

ES6 Shim has a reasonably wide browser support, even down to Internet Explorer 8.

The simplest way to get started in browser is just include a script tag in your page:

<script src="path/to/es6-shim.js"></script>

You can download the file from the ES6 Shim GitHub page. You can also install it using one of the alternative approaches listed on the same page.

Using the shim

Simply make sure that the shim file gets loaded before any scripts that depend on the ES6 features. Production use requires no changes.

Setting up the Traceur transpiler

There are two ways to use Traceur:

  1. Include Traceur compiler in your page/node
  2. Pre-compile the code on the server

The first option is good for quickly testing things, but it comes with a performance hit. The JS code is compiled on the fly on the page, so it’s not as fast as running pre-compiled code. If you’re planning on running ES6 code in the browser, I recommend using approach #2, but #1 is OK with nodejs.

Traceur workflow with nodejs

In node-land things are reasonably easy. First, we need the Traceur transpiler module:

npm install --save traceur

We have two typical scenarios for using ES6:

  1. Load the whole app using ES6 modules, a typical case with new applications
  2. Mix and match node modules and ES6 modules, a typical case with existing applications

Warning: Instead of Traceur, you might be tempted to use node’s --harmony* flags. These are experimental features and not recommended.

Load the whole app as ES6

If your app is written mostly in ES6, you will probably want to use this method. You can still use node’s own require loading with this if needed!

The way this method works is you write an entry point for your application as a normal node module. In this entry point, you load an ES6 file, which bootstraps your application.

First, let’s create the bootstrap file:

//bootstrap.js
export function run() {
  console.log('Hi there');
}

Next, create the entry point file. This is what we will run using node:

//app.js
var traceur = require('traceur');
traceur.require.makeDefault(function(file) {
  return file.indexOf('node_modules') == -1;
});
 
require('./bootstrap').run();

Now we can run the above code using node: node app.js.

The second line of code makes traceur’s require override node’s own. We use a simple check against the file path to make sure we don’t run any files in node_modules through traceur, as it would serve no purpose.

The beautiful part about this is that Traceur becomes essentially transparent: We can use node modules within ES6 modules, we can use require, etc.

Using node modules within ES6

We can use either require or ES6 module import syntax for this.

For example, using require:

//bootstrap.js
var express = require('express');
 
export function run() {
  console.log('Hi there');
 
  var app = express();
}

Or, using ES6 module imports:

//bootstrap.js
import * as express from 'express';
 
export function run() {
  console.log('Hi there');
 
  var app = express();
}

Mixing and matching node modules and ES6 modules

This approach can be used if you have an existing app where you want to start using ES6 modules.

With this approach, we just include traceur within our module, and use the require function from it directly:

var traceur = require('traceur');
 
var example = traceur.require('./example.js');
 
//example.js is an ES6 module, we can use it from here as we'd normally use it:
example.whatever();

Traceur workflow for browsers

Of course in browser-land life isn’t as easy. We need to install the Traceur as a command-line tool, and need another file from it as well.

Setup

First, install the tool:

npm install -g traceur

You can now run it using traceur from the command-line.

We will also need the traceur-runtime.js file. Once you’ve installed traceur, you can find it in your npm directory. This is typically located in /usr/local/lib/node_modules/traceur/bin/traceur-runtime.js. Copy the file so that you can include it within the HTML page.

If you can’t find the file, you can install traceur into a local node_modules using npm install traceur. Then, the file will be in the current dir, under node_modules/traceur/bin/traceur-runtime.js

Setting up an ES6 module on a web page

Let’s assume a simple project structure, where source code files are placed in src/, and the build output will be in build/. The traceur-runtime file is assumed to be in lib/traceur-runtime.js

First off, we’ll create a simple example file:

//src/app.js
export function run() {
  console.log('Hi there');
}

Next, we can use traceur to convert it into ES5:

traceur --out build/bundle.js src/app.js

Using the --out flag, we’re telling traceur to bundle all dependencies of our app.js script into the bundle.js file.

Finally, the HTML:

<!DOCTYPE html>
<html>
  <head>
    <script src="lib/traceur-runtime.js"></script>
    <script src="build/bundle.js"></script>
 
    <script>
      var m = System.get('src/app.js');
 
      //m is our ES6 module, with all the exported things
      m.run();
    </script>
  </head>
 
  <body>
  </body>
</html>

We need to include the runtime and our bundle. Then, we use the ES6 System module loader to get access to the module, and call the run function we exported in it.

Using imports and other libraries

If you’re building multiple ES6 modules, using them with this setup is simple:

//src/app.js
import * as foo from 'foo';
 
export function run() {
  console.log('Hi there');
}

You can import any ES6 modules as normal. Traceur will handle the dependencies, and will automatically bundle them into the output file.

Using other libraries, such as jQuery, works exactly like it does when not using ES6:

<script src="path/to/jquery.js"></script>
<script src="lib/traceur-runtime.js"></script>
<script src="build/bundle.js"></script>
 
<script>
  var m = System.get('src/app.js');
  m.run();
</script>
//src/app.js
export function run() {
  $(() => {
    $(document.body).html('hello world');
  });
}

You simply include any libraries you want before your scripts, and the globals they provide will be available in your ES6 modules as well.

Source maps

Lastly, let’s take a look at how to get source maps to ease debugging.

Source maps for node

To enable source maps with node,

npm install --save traceur-source-maps

And include it in your script:

var traceur = require('traceur');
require('traceur-source-maps').install(traceur);

Source maps for browsers

In the case of browsers, we just need to adjust the traceur command parameters a little bit:

traceur --source-maps --out build/bundle.js src/app.js

In closing

With the right tools we can start taking advantage of the features available in future versions of JavaScript today. As usual, it does not come without its possible downsides, but often they aren’t relevant depending on your use-case.

If you want to learn more about how ES6 can improve your JavaScript, I also wrote about the practical benefits of ES6 features.

The final question remains: When can you stop using shims and traceur? This one is quite tough to answer, but most likely it will be several years. You can use kangax’s helpful ES6 compatibility tables to check for specific features, some of which are already supported by more than one browser.