Practical tips and tricks for using ES6 in today’s web applications

Practical tips and tricks for using ES6 in today’s web applications

Javascript’s ES6 upgrade has been a long time coming and brings a lot of really great features.  Since the spec was finalized late last year, we’ve jumped in with both feet and begun using ES6 in considerable parts of our new web applications.

The internet already has plenty of tutorials covering specific ES6 features and how to use them, but most of them are shown in isolation and there’s not enough examples of how to actually pick up those code snippets and get them working in a real web application.

So, this blog post is a random collection of tips, tricks & styles that we’ve begun using in real-world web applications.  In no particular order….

Running ES6 and ES5 side-by-side

Practically speaking, you are very unlikely to be able to develop an application wholly in ES6.  If nothing else, most of your plugins are still in ES5.  Specifically, we’re talking about how modules are injected into the system.  Typically, this is done using an AMD dependency injection system like RequireJS, but ES6 has a snazzy new module syntax which is designed to replace this.

The practical challenge is getting these two technologies to run side by side.  Or, from our new ES6 point of view – how do we import a non-ES6 module? The answer is SystemJS.

Although it’s technically not, we like to think of SystemJS as a kind of wrapper for RequireJS and ES6 modules.  It basically reads what the structure of your file is and:

  • if it contains ES6 module syntax, it assumes it is an ES6 module
  • otherwise, loads as if it is a RequireJS module

One gotcha that took us about an hour to work out was that you need to kick it all off using a call to System.import.  In retrospect, this is pretty obvious – I mean, something has to tell your browser how to start loading external dependencies.  So basically, it all ties together like this:

logon.es6

logon.es6 is your snazzy new logon module, written entirely in ES6.  It uses the new module and class syntax and your girlfriend thinks it’s really good.  Note that it has two dependencies – the first is lib, which you wrote yourself in ES5 and the second is a third-party plugin (in this case jQuery) which may or may not have any AMD- or module-syntax embedded.

import $ from 'jquery';
import lib from 'lib';
export default class Logon {
/*
AttemptLogOn
Grabs the username and password provided and calls a service to authenticate
*/
AttemptLogOn(){
let params = {
username: $(#TxtUserName’).val(),
password: $(#TxtPassword’).val()
};

lib.CallService(/secure/login’, params);
}
};

lib.js

lib.js is that old library file which you’ve built up over the last few years.  It is written in ES5 and full of helpful utility methods which you really can’t be bothered upgrading to ES6.  It uses RequireJS syntax to declare its dependencies at the top of the file.

require(['anotherdependency'], function(anotherDep) {
    return function(){
/*
CallService
Makes an ajax request to the given URL
*/
var CallService = function(relativeUrl, params){
// Details omitted…
};

// Return public methods
return {
CallService: CallService
}
};
});

Logon.html

Your regular HTML page.  It uses System.import to get the ball rolling:

<script src="systemjs.js"></script>
<input type="text" id="TxtUserName"/>
<input type="password" id="TxtPassword"/>
<script>
System.import('logon.es6').then(function(l){
    var log = new l();
    log.AttemptLogin();
});
</script>

One more thing – you’ll probably need to use System.config call to tell it how your files are organized etc:

System.config({
    baseURL: '/scripts/',
    paths: {
        'Views/*': '/views/*.js'
    },
map: {
“jquery”: “lib/jquery”,
“jqueryui”: “lib/jquery-ui.min”
},

meta: {
“lib/jquery-ui-min”: {
deps: [‘lib/jquery’]
}
}
});

 

Writing a re-usable base class using ES6 inheritance

Along with modules, this is the feature we most appreciate in ES6 – a tidy way to create a re-usable base class for our controllers.  See, here is how our projects are typically laid out:

  • The application is divided into heaps and heaps of modules, like ‘logon’, ‘view chart’, ‘render menu’ etc etc
  • Each of these modules has a Javascript controller class which is bound to a view (using RivetsJS – we have an in-depth tutorial here)
  • There is a lot of common code which is repeated in our controllers, such as:
    • an Init() method to kick things off
    • a property called ‘model’ where we store the data for our view/controller
    • a reference to the view, in case we have to do something nasty like use jQuery to animate an element

Using ES6, we’ve now been able to create a tidy little BaseController class which encapsulates this once:

basecontroller.es6

Our Base Controller class is written exactly like a regular ES6 class…

import $ from 'jquery';
import lib from 'lib';
export default class BaseController{
constructor(m) {
this.IsLoading = false;

this.model = m;

// Our models all contain a reference to our view ID
if (this.model !== null) this.view = $(#’ + this.model.UniqueID);
else this.view = $(

); // Create an empty object so that we don’t have to keep doing null checks if there is no model

this.Init();
}

/*
Init
This method can be overwritten by base classes
*/
Init(){

}

/*
UpdateModel
Helper method to replace our model (for example, if we update a database record)
*/
UpdateModel(newModel){
$.extend(this.model, newModel);
}

/*
Calls our web service to get the given JSON
*/
CallJSON(url, params){
var p = new Promise((success, fail) => {
// Adjust model state
this.view.addClass(‘loading’);
this.IsLoading = true;

// Call our web service
lib.CallService(url, params).then(result => {
// Adjust this model state
this.view.removeClass(‘loading’);
this.IsLoading = false;

// Pass back to the specific handler/caller
success(result);
}, err => {
console.log(“Error”, err);
this.view.removeClass(‘loading’);
this.IsLoading = false;
fail();
});
});

return p;
}

}

logon.es6

Our re-written logon file may now look like this:

import BaseController from 'basecontroller';
export default class LogonControl extends BaseController {
    AttemptSignIn(){
        alert('Your current PersonID is ' + this.model.PersonID);
        let params = {
            username: this.view.find('#TxtUserName').val(),
            password: this.view.find('#TxtPassword').val()
        };
// Use base method to make a JSON call
this.CallJSON(‘signin’, params).then((newModel) => {
this.UpdateModel(newModel);
alert(‘Your new PersonID is  + this.model.PersonID);
});

}
};

And of course, you kick it all off by instantiating logon.es6 with a model in the constructor (note that the constructor is in the BaseController class, and accepts one parameter):

System.import('logon').then((l) => {
    let model = {
        PersonID: 0
    };
var log = new l(model);
log.Init();
});

Using traceur to make your ES6 code backwards compatible

Currently, most browsers only support a tiny subset of the ES6 standards and we doubt that we could rely wholly on them coming up to speed for at least another 12 months, likely much longer.  So it becomes necessary to run a transpiler which converts your ES6 code back into ES5.

As far as we can tell, the most complete transpiler out there is Google’s Traceur.

There are two ways of doing this, the lazy way and the proper way.  The lazy way is to just include a script file in the <head/> of your application, but we’re not even going to show a demo of that here because it is short-sighted.  (If you’re asking, the thing we hate most is not that the transpiling is done in real-time in the browser, but the fact that you have to decorate your <script/> tags with type=”module”)

The better way to do this is to setup a task which runs traceur against your ES6 files at compile time and then point your browser at the generated ES5 files.  For this, we’ve used the new Gulp integration supported by Visual Studio 2015.  This is not the place to give a Gulp/VS tutorial, but once you’ve got your head around it, here is how we at Blackball do our transpiling:

gulpfile.js

/// <binding ProjectOpened='watchjs'/>
var gulp = require('gulp');
var watch = require('gulp-watch');
var traceur = require('gulp-traceur');
var rename = require("gulp-rename");
// Helpful error handler to display error messages in our Gulp console window
function onError(error) {
console.log(“ERROR:  + error.toString());
this.emit(‘end’);
}

// Watch runs the traceur task automatically each time our es6 files are edited
gulp.task(‘watchjs’, function () {
gulp.watch(**/*.es6’, [‘compiletraceur’]);
});

// Our ES6 files are indicated with a .es6 file extension, so we just grab them all then save the transpiled .js file alongside each
gulp.task(‘compiletraceur’, function () {

return gulp.src(‘scripts/**/*.es6’)
.pipe(traceur())
.on(‘error’, onError)
.pipe(rename(function (path) {
path.extname = .js”;
}))
.pipe(gulp.dest(‘scripts/));
});

Read it slowly and it kind of makes sense.

One problem with traceur is that your browser is running code which you didn’t write, so error logs do not match your ES6 files one-to-one.  This is surprisingly okay though – even though the structure of your files differs, then lines that cause errors are generally pretty similar and practically speaking we haven’t had any problems understanding what part of our ES6 code the error pertains to.

Summing up

Whether you like it or not, you’re all going to be coding in ES6 in the next five years so you better get on board.  Due to the lack of support (tooling, blogs/forums, browsers….), it is not really practical to use it today, however if you like to play with new toys then hopefully this article will save you a few hours…

Bridging the client-server boundary – An experiment in architectures for next-generation web applications

Bridging the client-server boundary – An experiment in architectures for next-generation web applications

Best practice front-end architecture using Microsoft ASP.Net MVC and Rivets.js

Best practice front-end architecture using Microsoft ASP.Net MVC and Rivets.js