Note: Post updated for best practices in 2017!
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.
- What are my target platforms?
- Do I need to debug on older devices or browsers?
- Am I developing a performance-critical application?
- For cutting-edge features which are not yet finalized in specs, you’ll also want to consider if you care about the specification changing
Let’s address each of these before we go further.
Target platforms
Your target platforms – devices, browsers, etc. – should be kept in mind when deciding whether or not to use ES6.
Latest versions of Node.js and modern “evergreen” browsers – that is browsers which get auto-updated – have good support for ES6. This includes browsers such as Edge, Chrome, Firefox and Safari.
Older browsers may prove to be more difficult: Using ES6 in browsers which have no native support 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. This also applies when you want to use more cutting edge features in modern browsers lacking the native support.
If you are one of the unfortunate people who have to deal with super old browsers such as older versions of Internet Explorer, 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 you wrote, rather than the compiled code.
However, if you target some older platforms, they may not support source maps. Many older browsers support earlier versions of JavaScript just fine, and the transpiled code will work in them, but debugging code on those browsers 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.
Spec changes
If you want to use more cutting edge features only currently in the planning stages for future versions of JavaScript, you may need to consider what happens if the specification changes.
If the specification changes, it can means the tools you use to make those features work 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 using the cutting edge features. To help you choose which features to use, it’s a good idea to know how the specifications are written. For that, I suggest reading this article on how the ECMAScript spec process works
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.
This only matters if you need to support browsers which don’t have native ES6 support, or if you want to use other cutting-edge features.
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 Babel -part.
For this, we will use ES6 Shim.
ES6 Shim in nodejs
Note that the shim is unnecessary in Node.js if you use a fairly recent version, since it has nearly full native ES6 support.
If you use an older version of Node.js, setting the shim up is still 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 Babel transpiler
There are two ways to use Babel:
- Include Babel in your page/node
- Pre-compile the code before loading it
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. Option #2 is the best choice for production environments as you get the best performance with it.
Babel workflow with nodejs
Similar to ES6-shim, Babel is mostly unnecessary with recent Node.js versions, unless you intend to use more cutting-edge features.
The first thing to do is we need to install Babel’s command line tools:
npm install --save babel-cli
We’ll use these utilities to run Babel.
Depending on if we want to use Babel for development or production, we should do things slightly differently. For development, we can run babel “on the fly”, but for production we should precompile the code to improve startup performance and memory use.
Regardless of which approach we choose to use, we need to tell Babel which features we want it to compile for us. This can be done easily by creating a .babelrc
file with the necessary settings. Babel offers us two ways to choose what features we want: Plugins and presets. For most cases, presets are the easier choice – they are essentially collections of plugins, such as all the plugins you need for ES6.
For example, to use ES6, we can set up our .babelrc
file like so:
{ "presets": ["es2015"] } |
We also need to install the preset to our project:
npm install --save babel-preset-es2015
Without the use of the preset, we would have to define a lot of plugins, as we need a plugin for every ES6 feature. There are a number of other presets available as well.
Using Babel with Node.js for development
The babel-cli package we installed comes with a wrapper for the node
command. We can use it for development to automatically convert our code when we run our application.
All we need to do now is simply run babel-node
instead of node
when running our app:
node_modules/.bin/babel-node src/index.js
We need to include the node_modules/.bin/ in the path because we installed babel-cli locally into our project. One good way to avoid having to remember the exact path is to create a custom start script for your project.
We can add the following into our package.json
file:
"scripts": { "start": "babel-node src/index.js" } |
With this in place, we can simply use npm start
Babel and Node.js in a production environment
Although using babel-node is convenient, it isn’t recommended for production. It’s slower and consumes more memory than if we pre-compiled our code and ran that with the normal node executable.
To do this, we’ll use babel
to compile our node app, and then separately run it.
Assuming we have a directory called build/
…
node_modules/.bin/babel src/ -d build/
This will take everything in the src directory, compile them with babel, and put the output into the build directory. Now we simply run the file from build instead of src:
node build/index.js
We can again simplify this process by adding scripts into package.json:
"scripts": { "start": "babel-node src/index.js", "build": "babel src/ -d build/", "production": "node build/index.js" } |
Now we can simply run npm run build, and then npm run production
Babel workflow for browsers
If you need to support older browsers or want more cutting-edge features, you will need to compile your browser-javascript via Babel as well.
If you simply want to do some quick testing with Babel, it’s possible to dynamically compile the code in the browser. This approach has some limitations in addition to the significant performance hit, so it’s not recommended – but we’ll cover that quickly, as it’s nice to know.
Compiling code dynamically in-browser
For this, we’ll use a package called babel-standalone
.
The quickest and easiest way to use it is through a CDN. This way, we only need to include a script tag on our page and that’s it.
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> |
After this, use type="text/babel"
for scripts you want compiled:
<script type="text/babel"> const x = 'this is compiled with Babel'; </script> |
Compiling browser-code with Babel
The recommended approach for browser-based code is precompiling it on your server. This way we can make full use of all Babel’s features, plus we’ll be able to use CommonJS or ES6 modules.
The simplest way to do this is using Browserify or Webpack. These tools allow us to bundle our code into a single file, optionally performing additional tasks such as compiling ES6 code. Since Browserify is much simpler to set up, we’ll use it here.
First off, we’ll need to install Browserify. We’ll also install the Babelify transform, which allows Browserify to call Babel for us.
npm install --save-dev browserify babelify
Assuming our browser-code is in src/browser.js
, and we have a directory called build/
, we can simply run…
node_modules/.bin/browserify src/browser.js -o build/browser.js -t [ babelify ]
This loads src/browser.js, bundles any CommonJS and ES6 modules together and runs everything through Babel. Any settings such as plugins or presets are automatically loaded from the .babelrc
file we created earlier. In order to use this code in browser, we only need to load build/browser.js
using a script tag.
The best way to avoid having to remember the long command to browserify our code is to add a command for it into package.json:
"scripts": { "browserify": "browserify src/browser.js -o build/browser.js -t [ babelify ]" } |
With this in place, we can run npm run browserify to generate the browser-bundle.
Source maps
Lastly, let’s take a look at how to get source maps to ease debugging.
When using precompiling with Babel, we simply need to include an additional parameter in the command:
babel src/ -d build/ --source-maps inline
Adding --source-maps inline
includes the source map directly in the compiled JS file. This is the easiest way to do it, since it doesn’t require serving additional files.
To use this with browserify, we simply enable the debug mode with the -d
flag:
browserify -d src/browser.js -o build/browser.js -t [ babelify ]
Note that including source maps can significantly increase the size of your compiled output. As such, you should only use them in development, and compile your browser code without source maps for production. Using a JavaScript minifier will also strip out source maps, so if you prefer you can compile with source maps, and then minify the result.
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 Babel? If you only need to support “evergreen” browsers, you might not need them even today. If you need to support older browsers, you can use kangax’s helpful ES6 compatibility tables as a guide.