How to Use ES6 for
Universal JavaScript Apps

Now that the dust has settled a bit, I’m finally beginning to use ES6 for production apps — and because I write universal JavaScript, it has to work for both Node.js and browsers.

This won’t be an in-depth tutorial about ES6 features or universal JavaScript (aka isomorphic JavaScript). We’re just going to cover the basics to get you up and running.

Use Babel

Babel.js is a great tool that lets you transpile your ES6 code into ES5 code that runs in today’s JavaScript environments, including Node.js and browsers, but it isn’t obvious how to get it set up.

Install Babel

Nearly every tutorial you find will tell you to install Babel globally. That’s fine if nobody has to share your code, but if you’re on a team or producing open-source libraries, install it locally per-package, as well:

$ npm install -g babel-cli
$ npm install --save-dev babel-cli babel-preset-es2015 babel-preset-stage-0

Now you can launch the `babel-node` CLI/REPL:

$ babel-node
> Object.assign({}, {msg: 'wow!'}); // => { msg: 'wow!' }

For Browserify workflows, you may need these, as well:

$ npm install --save-dev babelify browserify

This will let you use all the cool new ES6 syntax, like arrow functions:

(x) => x + 1;

But it won’t let you use the new built-in methods like `Object.assign()`, ``, etc…

This isn’t immediately obvious, because these features work great using the `babel-node` REPL:, NaN); // => true

Lucky for us, this is easily solved with a polyfill:

$ npm install --save core-js

Then, at the top of your entry file:

import 'core-js';


Worried that you’ll have to give up linting your code? No worries. ESLint has you covered!

$ npm install --save-dev eslint babel-eslint

The ES6 love is in the `env` and `ecmaFeatures` keys. You’ll need those to prevent errors when ESLint encounters your ES6-specific code.

If you also want object rest & spread (an ES7 proposal commonly used in React code), you’ll also need `babel-eslint`.


For most cases, you can simply replace your `node` calls with `babel-node` calls. `babel-node` caches modules in memory, so there’s a significant startup delay and potentially a lot of memory usage. Consider compiling to to ES5 for production. Read on.

Babel’s docs make compiling look like a breeze:

$ babel script.js --out-file script-compiled.js

Couldn’t be easier, right? Well, if you don’t try to import any modules, sure, this will work fine. But if you do anything non trivial, you’ll want to compile your whole code base, not just one file. For that, you want to use the `-d` option.

$ babel -d build-dir source-dir

Note that the output directory comes first.

If you want the debugger to work properly, you will want to add source maps with the `-s` option:

$ babel -d build-dir source-dir -s

Doing so will tell Babel that for each file it compiles, it should also produce a source map file that will tell debuggers where to find the original source code while you’re stepping through the live code in the engine. In other words, you’ll see the code that you wrote, instead of the compiled output that Babel generated. That’s usually what you want.

To compile for the browser, you want to use Webpack, or the Babelify Browserify transform. I typically use babelify for quick compiles at the terminal. For instance, to run some unit tests:

npm install -g browserify browser-run
browserify -t babelify script.js | browser-run -p 2222
  1. Install `browserify` and `browser-run` so that you can use them anywhere in your terminal.
  2. Create a bundle from `script.js` and run the script in chrome. Hit http://localhost:2222 from your favorite browser, and the script will run in the browser. Console log output will get piped back to the console.

Compile a bundle:

$ browserify script.js -t babelify --outfile bundle.js

Configuring Webpack is too much for this quick tutorial, but if you want to skip all the busywork, you can use this boilerplate for production modules.

Using Existing Modules

Using the above mentioned tools, you can import both ES6 and Node-style modules using ES6 syntax:

import 'core-js'; // Node module
import harness from './harness'; // ES6 module

So you can keep using all the standard modules you’ll find in the massive npm ecosystem in your modern ES6 codebase — but since so many people are still using ES5, I recommend that you publish compiled versions of your modules to npm.

For public libraries, I put the source in a `source` directory, and compiled ES5 in `dist` or `lib`. For now it’s probably a good idea to point to the compiled ES5 version with the `main` key in `package.json`.


I like to put these commands in my npm scripts:


Bonus for React users: you’re covered, too. Both Babel and ESLint support JSX. Nice!

EDIT: As of Nov 8, 2015, changes to the Babel 6 API have broken babel-react-transform. If you need it, you can install the latest compatible version of Babel:

npm install --save-dev babel@5.8.29

Party Time

Congratulations! You’re ready to start using ES6 for your universal apps. In case you’re wondering, here are a few of my favorite ES6 things you should explore:

  • Compact object literals
  • Destructuring
  • Arrow functions (Great for one-line lambdas)
  • Default params
  • Rest params
  • Generators

You should memorize the new defaults/overrides pattern right now:


Eric Elliott is the author of “Programming JavaScript Applications” (O’Reilly), host of the documentary film-in-production, “Programming Literacy”. He has contributed to software experiences for Adobe Systems, Zumba Fitness, The Wall Street Journal, ESPN, BBC, and top recording artists including Usher, Frank Ocean, Metallica, and many more.

He spends most of his time in the San Francisco Bay Area with the most beautiful woman in the world.