So You Want to Build an App in ES6, Part Three

In the previous article in this series, we discussed building our baseline from a git repository and using gulp tasks to turn ES6 code into ES5 as well as perform our unit tests. In this article, our goal is going to be to write some simple ES6 and get it converted into ES5 with the three main tools: Browserify, Babel, and Paritalify.

  • Browserify

    Browserify lets you require('modules') in the browser by bundling up all of your dependencies.

    Our goal is to use Browserify to handle ES6 imports. ES6 import syntax is excellent, maintainable, and readable.

  • Babel

    Babel is a JavaScript compiler. Use next generation JavaScript, today.

    Our goal is to use Babel to transpile ES6 to ES5. We need to transpile from ES6 to ES5 until all supported browsers allow use of ES6.

  • Partialify

    Partialify require()-able HTML, CSS and (potentially) more

    Our goal is to use Partialify to allow us to include HTML template and CSS right in our javascript source code in order to reduce the number of asset requests. This also compartmentalizes our modules so that we don't have to worry about conflicts.

We've already got these all installed and ready to build our application. Let's get started!

ES6

ES6 brings a large number of things to the table: imports, classes, symbols, generators, arrow functions. I could go on and on and on. There are plenty of resources available on the web to help find every little bit of functionality available. My suggestions would be to look at the Mozilla Developer Network and Babel. Both of these resources contain tons of information. Many times, the MDN will also have polyfills for functions that may not be available for the browsers you have to support.

We are going to leverage Browserify to handle imports for us. We can use Browserify to do ES6 imports as well as require() calls to include HTML templates and CSS with Partialify. In the current ES5 world, in order to import additional items, you have to either hard code all of your includes:

<script src="./js/script1.js"></script>
<script src="./js/script2.js"></script>
<script src="./js/script3.js"></script>
<script src="./js/script4.js"></script>

Another option is to use RequireJS to include other files:

requirejs(["js/script1", "js/script2", "js/script3", function (script1, script2, script3) {
    
}]);

All we really want to do is support a simple ES6-style import:

import Script1 from './script1';

Browserify lets us do this quickly and easily. Additionally, you can easily find and debug the sections that are causing the issue, without having to dig through thousands of lines of javascript at a time. No more will you have to write ten million functions and classes in one file. No more do you have to figure out which dependencies you have to inject or remember what files contain code and which contain other items. No need to make sure you include your scripts in the correct order.

This brings me to the second point: using Partialify to include HTML templates and CSS. This puts the burden on the module to have everythign in order. No need to have to write really specific targeting code to get things to act the way you want them to. Write it, template it, add your CSS, and include it with Partialify. Dead simple. No strings attached.

var myTemplate = require('./myTemplate.html');
var myCss = require('./myCss.css');

These are just the basic tools you can use in order to get started building ES6 applications. The best part of all of this is that you can quickly and easily add additional tools to your build step. If you want to write LESS? Install Less2Css. Want to use SASS? Install Node's SASS module. Need to minify everything? Gulp-uglify handles all of this for you. Want to include Source Maps? There are ten excellent libraries for doing this.

AngularJS

The end goal of this series is to get you familiar with building an application in ES6 using Browserify, Babel, and Partialify. Additionally, you should end up with a pretty good start on building AngularJS applications using these tools and a technique known as fractal design. Let's start by writing some pretty basic code. In the next article in our series, we will focus more on building Controllers, Services and Directives correctly and allowing our tools to assist us in creating our application quickly and easily.

In ES5, you end up having to do things like this:

main.js

(function () {
    var app = angular.module('myApplication', []);

    var MyController = function MyController ($http) {
        this.$http = $http;
    };

    MyController.prototype.ClickMe = function () {
        this.count++;
        this.message = "Clicked me " + this.count + " times!";
    };

    MyController.prototype.GetAjaxData = function () {
        this.$http.get('/path/to/api')
            .success(function (response, headers) {
                this.message = "Successfully loaded api data!";
            })
            .error(function (response, headers) {
                this.message = "Error loading api data!!!";
            });
    };

    MyController.$inject = ['$http'];

    app.controller('MyController', MyController);
})();

It's a bit messy. It requires you to use IIFE's in order to not pollute the global namespace. Sure, it can do everything you want it to do, and that's all well and good. But in order to really build this type of thing out, you have to either spend time building up code snippets that you can use to write all of these things, or you have to write them by hand. Either way, it's time-consuming and error-prone. One of ES6's goals was to help make things easier for developers to, well, develop. Here's similar code in a basic ES6-style:

main.js

class MyController {
    constructor ($http) {
        this.$http = $http;
    }

    ClickMe () {
        this.count++;
        this.message = "Clicked me " + this.count + " times!";
    }

    GetAjaxData () {
        this.$http.get('/path/to/api')
            .success((response, headers) => {
                this.message = "Successfully loaded api data!";
            })
            .error((response, headers) => {
                this.message = "Error loading api data!!!";
            });
    }
}

MyController.$inject = ['$http'];

angular.module('myApplication', [])
    .controller('MyController', MyController);

I know what you are thinking: That looks almost the same, outside of the class declaration and replacing .bind(this) with arrow functions. It certainly does. But here's the power of our baseline build chain.

main.js

import MyController from './myController';

angular.module('myApplication', [])
    .controller('MyController', MyController);

myController.js

class MyController {
    constructor ($http) {
        this.$http = $http;
    }

    ClickMe () {
        this.count++;
        this.message = "Clicked me " + this.count + " times!";
    }

    GetAjaxData () {
        this.$http.get('/path/to/api')
            .success((response, headers) => {
                this.message = "Successfully loaded api data!";
            })
            .error((response, headers) => {
                this.message = "Error loading api data!!!";
            });
    }
}

MyController.$inject = ['$http'];

export default MyController;

Now your controller function is separate from your application module. You can do your configuration in your application module definition folder, or even better, split it off into multiple pieces. Why should we do this, you ask? Well, for starters, it's easier to build small, distinct pieces of functionality. Each function should do one thing perfectly. Each class should have a number of tightly coupled functions. Each module should share a broader range of functionality. Secondly, let's say you have MyController and you come to realize that you need another controller. You can just build another controller and inject it. Deprecate the original if you have to, but this way you can keep building and extending and maintaining without worrying that you are breaking things. As long as your controllers share functionality, their implementation details should be separate.

Next Steps

In the next article, we are going to build a simple controller, service, and directive. We are going to use Browserify to import AngularJS for us so we don't have to add a script tag in our HTML to include it. In fact, we are going to end up building our application and only needing one file to include all of our application views and logic. You can swap out master stylesheets to your heart's content, without needing to manage any dependencies you might have.