How to setup an ES6 production-ready project

Published27 August 2015

Categories#javascript

As ES6 is really hyped this year and the specification has eventually been finished, I wanted to start using it. I have been (okay I still am) the guy at work who took every chance talking about ES6 and all the glory it provides. Which might not have been the best idea with a lot of back-end developers joining the discussion:

Sounds cool Moritz, but when can we use it? :trollface:

Sad to say that browser support is still under heavy development, and using it in a production environment was beyond considering.
So I accepted our fate, having to wait for a couple of years. And what about all the browser who won’t have any ES6 features but still need to be supported? Sigh, let’s add another few years.

Fortunately ES6 turns JavaScript into the language for compilers. And there are already plenty of ES6 to ES5 transpilers, which make it possible to write ES6 for production-ready projects today. A dream comes true! By now I have turned almost all my private projects and a couple of client projects into a solid ES6 setup.

In this article I want to share my experience on how to create a good production-ready ES6 setup for front-end projects.

Getting started: Choosing a transpiler

Compiler or transpiler? Let me explain this here because I was also confused when I first stumbled upon this. A compiler turns a high-level programming language into a low-level programming language. Whereas a transpiler, or source-to-source compiler, remains on the same level of complexity translating from one high-level programming language to another.

The three major transpiler out there are Babel.js with currently 71% of feature compatibility, Traceur supporting 59% and TypeScript with 52%. This is already quite good and the majority of features are supported (a lot of the unsupported features refer to Subclassing and Proxying). I recommend using Babel as it provides the best support and you probably want to feel as free as possible writing ES6.

The features I tend to use the most are => arrow functions, classes, const and let, template strings and modules. And I am really trying to find a good use case for generator functions. This can quickly change based on my project requirements though, but I guess this is what most developers will want to use at the beginning.

Adding the final ES6 feeling

Babel is great, but lacks one huge feature: ES6 modules. Browserify to the rescue! With its CommonJS support that is very similiar to modules, we can require('modules') in the browser and properly put all dependencies into bundles. For me it added the final ES6 feel.

Combine and automate

Browserify + Babel is a common setup today and together they cover a lot of features which front-end developers are most interested about. Even though both have their own Command Line Interface, it is quite annoying and time consuming to type $ browserify everytime a file has been modified.
So I suggest to use a task runner, as this is part of a default front-end setup nowadays.

For the rest of this article I will use Grunt as an example, but everything is easily portable to a Gulp setup.

This is very simplified for this article and would be more advanced in a bigger project. Feel free to have a look at my example boilerplate FrontBook, where I also showcase views/, assets/ and styles/.

Create the package.json

A package.json usually contains relevant meta data for the project. Here are the ES6 devDependencies:

grunt dev: This is basically the same as grunt build, just with an additional watch task. So when anything changes in your code, everything will be compiled and linted automatically.

In the browserify task we define to use babelify with the loose: 'all' option. This tells babelify to keep the ES5 code as close as possible to the ES6 code. In browserifyOptions we add debug: true, which enables source maps for better debugging. There are a couple of more Babel and Browserify options available, but in the beginning this should be fine. The task then simply runs through all files in src/scripts/ and compiles them to dist/.

Linting

Code needs to be tested. I recently moved from JSHint to ESLint just because I like the possibility of creating my own rules and all the other available options. For me it also felt slightly easier to get it to work with ES6 code.

Let’s be honest: I just turned everything on because I want all ES6 features. If I don’t want specific features, I would just disable them.

Coding in ECMAScript 6

Our setup is ready and idly waiting to compile some ES6 code for us. Great! Let’s start then. I assume that you at least have some knowledge of the ES6 features and how they work. I will only briefly showcase a few of these, including modules.

We will create a simple module using the new class syntax, export it and then import in our index.js to use it.

We could also use a template string in getTime(), but unfortunately the syntax highlighter I use don't support them yet.

The module.js contains a class called Timer, which takes an HTML element as argument in its constructor function. The const variable INTERVAL is not accessible from outside of the file and neither is part of the global scope, even though it’s used inside of the class. The class has a simple getTime() function to return the current time and an update() function to apply the current time to the HTML element passed in the constructor.

As you can see Browserify use a helper function at the beginning to bundle all modules in one single file. But what I think is most important here, is that the code of module.js and index.js pretty much look the same. You can easily recognise your code and still understand what is going on.

This file can be found in dist/app.js and should be included in your HTML views:

<script type="text/javascript"src="app.js"></script>

What to keep in mind

As mentioned earlier, some features not supported yet. Subclassing Date, Array and DOM doesn’t work because of the limitations of ES5. It also depends a little bit on what your production environment will be like. Do you need to support Internet Explorer 8? Then keep in mind that Object.defineProperty (which is used to polyfill getters and setters) doesn’t work there.

Babel and Object.assign

This is another thing. Object.assign is only supported with Chrome 45 and Firefox 34. If you use it though, Babel won’t polyfill it and just keep it as is. In order to add the polyfill, we have to modify our transform option in the Gruntfile.js to use the runtime transformer.

Conclusion

So, that’s it. We could now move on and extend this setup with a good view handling or add a CSS preprocessor such as Sass. Whatever fits your project. Thanks to Babel and Browserify it’s already possible to write ES6 code, but still use ES5 code on the production environment. By that we can have a lot of fun and keep support for browser without complete feature support.

I have made own ES6 boilerplate called FrontBook open source and like to share it here. It also covers some more topics such as views, styles and assets. Feel free to check it out for your own projects and customise to your needs.

TL;DR

A good ES6 setup requires three main dependencies: Babel.js, Browserify and a task runner such as Grunt or gulp. Babel covers 71% of the ES6 feature set and Browserify adds the great module system.

Running $ browserify in the command line every time you modify something is quite annoying and time consuming. Let’s use a task runner for that. You will need:

grunt-browserify using the transform: ['babelify'] option,

ideally a linter such as grunt-eslint,

grunt-watch to check for any modified files.

Take care using features such as Object.assign, as this still needs an additional plugin to work in all browser.