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.
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,
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.
The project’s structure
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
package.json usually contains relevant meta data for the project. Here are the ES6
The modules used for this are:
babelify: Babel.js transformer for Browserify
grunt-browserify: Browserify Grunt task
grunt-eslint: ESLint Grunt task
grunt-contrib-watch: Grunt task to watch over changed files
npm install from the projects directory to make sure all dependencies are installed and you won’t run into any errors.
Define tasks in
We defined two tasks at the beginning:
grunt build: Compiles your ES6 code to proper ES5 code and tests it via ESLint.
grunt dev: This is basically the same as
grunt build, just with an additional
watchtask. So when anything changes in your code, everything will be compiled and linted automatically.
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
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.
My ES6 specific configurations are:
Take a look at the rest of my
.eslintrc configuration here.
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
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.
module.js contains a class called
Timer, which takes an HTML element as argument in its constructor function. The
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.
This is pretty straight-forward. We imported the class, assigned it to the
timeElement variable and called
update() to initialise the module.
Pretty cool, isn’t it? We don’t have to worry much about the global scope anymore and can eventually think in a more modularised way.
The ES5 output
Since all this will be compiled to proper ES5 code, let’s take a quick look at how the output looks like:
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
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:
What to keep in mind
As mentioned earlier, some features not supported yet. Subclassing
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 has a short table of its caveats.
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
There is also a
babel-plugin-object-assign plugin which replaces all occurences of
Object.assign with an extend helper.
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.
$ 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:
- ideally a linter such as
grunt-watchto 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.