To ES∞ and Beyond: Bulletproof Front-End JavaScript

While there is a plethora of amazing open source tools out there, recent advances in the EMCAScript language specification (ES6/ES2015) have brought amazing power and expressiveness to the web for JavaScript. Language features like import, module loading, and classes have made writing JavaScript cleaner, more consistent, and less reliant on opinionated tooling, thus making code written today in ES6 immune to churn and less likely to become “legacy,” even after a few years. While these claims are bold, they are, in fact, very realistic, well-reasoned, and do not prevent teams from using their preferred build tools or any of the popular frameworks/libraries available.

The main concepts covered are:

  • Language Specification – Embrace ES6 or, ideally, Typescript
  • Dependency Management – Consume your dependencies in a universal format, be it from NPM or Github, while still being standards compliant
  • Module Loading – Load not only your JavaScript, but also your CSS, in accordance with standards compliance.

ES6 and TypeScript
ES6/ES2015 is the current iteration of JavaScript, which brings with it a number of useful features like classes, import/export statements, fat arrow, and improved support for variable scoping. This is a direct acknowledgment by the standards committee to support the needs and experience of developers, as our JavaScript applications have gotten bigger and more complex over the past few years. However, there is also a need to incorporate tried and true conventions from mature, strictly typed languages like Java, where concrete types and values can be caught before the code even reaches the browser.

While an excellent tool like Babel will make sure that one can write the ECMAScript of tomorrow today, TypeScript will do the same but will also support interfaces, member/method privacy, and member/argument types. As applications grow and become more “distributed” (component libraries/micro ui’s), and client side data management, consistency, and durability becomes more mission critical, TypeScript will ensure continuity and compile time feedback to developers.

Below is an example of a React component written in ES6. Inline comments added here to highlight key language features:

'use strict';

//we can import all our dependencies explicitly per module / component
import React from 'react';
 
//we can even load our CSS!
import './user-details.css!';

import { GithubStore } from '../../stores/github/github-store';

//here we use ES6 classes and merely extend the React library.  This is the only usage of React in this entire file
//we now align our components with a concrete language feature.  Our components are just classes at the end of the day

class UserDetails extends React.Component {

  //no longer we have init, activate or IIFE to kick off our component.  There is now consistent standard provided by the language
  constructor() {
    super();

    this.state = {
      avatar: '',
      name: ''
    };

    this.getUserDetails();
  }

  getUserDetails() {
    let store = new GithubStore();

    store.getUserDetails().then(response => {
      this.setState(response.data);
    });
  }

  render() {
    return (
      <div className="user-details">
        <img className="user-avatar img-responsive" src={this.state.avatar}/>
        <h1><span className="user-name">{this.state.name}</span></h1>      </div>
    )
  }

}

//ES6 way to expose our class for others to consume
export default UserDetails;

Below are some TypeScript code samples, based on the Angular 2 Tour of Heroes guide. Inline comments added here to highlight key language features.

//we now have support for strictly typed language features like interfaces
export interface Hero {
  id: number;
  name: string;
}
 
//an example of an Angular 2 Component
import { Component, OnInit } from 'angular2/core';

import { Router } from 'angular2/router';

import { Hero } from './hero.interface';
import { HeroDetailComponent } from './hero-detail.component';
import { HeroService } from './hero.service';

//here we are using ES7 decorators (supported in TypeScript) to annotate our component

@Component({
  selector: 'heroes-list',
  templateUrl: 'src/components/heroes/heroes-list.component.html',
  styleUrls:  ['src/components/heroes/heroes-list.component.css'],
  directives: [HeroDetailComponent]

})

export class HeroesComponent implements OnInit {

  //with TypeScript, we are able to enforce member types, as defined by our Hero interface
  heroes: Hero[];

  selectedHero: Hero;

  //with TypeScript, we are able to enforce privacy and argument types

  constructor(private router: Router, private heroService: HeroService) {

  }

 getHeroes() {

    this.heroService.getHeroes().then(heroes => this.heroes = heroes);

  }

  ngOnInit() {

    this.getHeroes();

  }

  onSelect(hero: Hero) { this.selectedHero = hero; }

  gotoDetail() {

    this.router.navigate(['HeroDetail', {
      id: this.selectedHero.id 
    }]);
  }
}


Dependency Management
Dependency management for JavaScript applications have conventionally been managed through Bower, and more recently through just NPM itself alone. Either way, package management has relied on ultimately delivering all static assets (concatenated or not) to be included via  <script> and <link> tags in an HTML file.  But!  No more!  JSPM was specifically developed to support SystemJS (covered in the next section) as the de facto package manager for client side JavaScript.  It can:

  1. Install a package / module / library / etc from NPM or Github.
  2. Save a single configration file (js) that captures the entire dependency graph for an application, such that all dependencies can easily be loaded in the browser with just a single JS file in a <script> tag.
  3. Bundle all dependencies to be loaded into the browser; either for development or production (minified and concatenated).

Module Loading
Module loading and dependency management, although separate concerns, often go hand-in-hand in the build process and the runtime of an application.  Though it didn’t make the final cut of the current ES6 spec, a module loader specification called System, which can be polyfilled today using SystemJS will be coming in ES7.  This allows us to greatly reduce the overhead in our code by allowing us to use ES6 import today with all our client side static assets, in addition to be able to load all our third-party vendor dependencies and the underlying dependency graph needed to support them all with a single <script> include.

Although there are tools like Webpack or Browserify which aim to encapsulate the entire build and packaging lifecycle of an application, they are not specifically built around ES6 like SystemJS is. For that reason, the choice to use SystemJS over Webpack or Browserify is specifically because of its primary motivation to support ES6 / ES7 and forgoing any sort of “vendor lock-in”.  Both SystemJS and JSPM are also authored by the same developer, Guy Bedford.

Below is an example of what the index.html of a SPA looks like when using SystemJS / JSPM.  Inline comments added here to highlight certain features.

<!DOCTYPE html>

<html lang="en">
 <head>
    <title>Github Dashboard</title>    <base href="/">
    <meta charset="utf-8">
    <meta name="description" content="Github Dashboard"/>
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>

    <!-- single line to import the System polyfill -->

    <script src="./jspm_packages/system.js"></script>

    <!-- import our vendor dependencies, as generated for us by JSPM-->

    <script src="./config.js"></script>

    <!-- kick off our application, and that's it! -->
    <script>
      System.import('src/bootstrap.js');

    </script>

  </head>

  <body>

<div class="container-fluid">

       <div class="col-md-*">
        <section id="content"></section>
      </div>

    </div>

</body>


</
html>

Rounding Things Out
As mentioned, this guide is opinionated only as it relates to future-proofing of how one writes and maintains their JavaScript in order to be as accommodating to standards and specifications as possible. Intentionally left out of this article are two key layers of the stack:

  1. Build / task runners
  2. UI library / frameworks

While there is no opinion on these, there are of recommendations and considerations.  For your build it is recommended to use Gulp via Keystone, as it is fast, composable, and has a robust ecosystem of plugins and can be installed and managed with NPM.  For the UI, any library or framework that plays nicely with the underlying stack here, like ReactAngular 2, or Aurelia will work as long as it is the right one for your application’s needs. What matters is that whatever you substitute, the only requirement should be that it plays well with ES6 and SystemJS / JSPM, so as to make the next few years of your JavaScript development as frictionless as possible.

So putting it all together, here is our stack of the future, today!

frontend-stack

This post was written by Owen Buckley, a software engineer based out of Kenzan’s Rhode Island office.

Leave a Reply

Your email address will not be published. Required fields are marked *