5 min read

Frontend web development is a difficult domain for creating scalable applications.  There are many challenges when it comes to architecture, such as how to best organize HTML, CSS, and JavaScript files, or how to create build tooling to allow an optimal development & production environment.

In addition, complexity has increased measurably. Templating & routing have been transplanted to the concern of frontend web engineers as a result of the push towards single page applications (SPAs).  A wealth of frameworks can be found as listed on todomvc.com.  AngularJS is one that rose to prominence almost two years ago on the back of declarative html, strong testability, and two-way data binding, but even now it is seeing some churn due to Angular 2.0 breaking backwards compatibility completely and the rise of React, which is Facebook’s new view layer bringing the idea of a virtual DOM for performance optimization not previously seen in frontend web architecture.  Angular 2.0 itself is also looking like a juggernaut with decoupled components that harkens to more pure JavaScript & is already boasting of performance gains of roughly 5x compared to Angular 1.x.

With this much churn, frontend web apps have become difficult to architect for the long term.  This requires us to take a step back and think about the direction of browsers.

The Future of Browsers

We know that ECMAScript 6 (ES6) is already making its headway into browsers – ES6 changes how JavaScript is structured greatly with a proper module system, and adds a lot of syntactical sugar.  Web Components are also going to change how we build our views as well.

Instead of:

.home-view {
    ...
  }

We will be writing:

<template id=”home-view”>
    <style> … </style>
    <my-navbar></my-navbar>
    <my-content></my-content>
    <script> … </script>
  </template>
  <home-view></home-view>
  <script>
    var proto = Object.create(HTMLElement.prototype);
    proto.createdCallback = function () {
      var root = this.createRoot();
      var template = document.querySelector(‘#home-view’);
      var clone = document.importNode(template.content, true);
      root.appendChild(clone);
    };
    document.registerElement(‘home-view’, {
      prototype: proto
    });
  </script>

This is drastically different from how we build components now.  In addition, libraries & frameworks are already being built with this in mind.  Angular 2 is using annotations provided by Traceur, Google’s ES6 + ES7 to ES5 transpiler, to provide syntactical sugar for creating one way bindings to the DOM and to DOM events.  React and Ember also have plans to integrate Web Components into their workflows.  Aurelia is already structured in a way to take advantage of it when it drops.

What can we do to future proof ourselves for when these technologies drop?

Solution 

For starters, it is important to realize that creating HTML and CSS is relatively cheap compared to managing a complex JavaScript codebase built on top of a framework or library.  Frontend web development is seeing architecture pains that have already been solved in other domains, except it has the additional problem of the standard challenge of integrating UI into that structure.  This seems to suggest that the solution is to create a frontend service-oriented architecture (SOA) where most of the heavy logic is offloaded to pure JavaScript with only utility library additions (i.e. Underscore/Lodash).  This would allow us to choose view layers with relative ease, and move fast in case it turns out a particular view library/framework turns out not to meet requirements.  It also prevents the endemic problem of having to rewrite whole codebases due to having to swap out libraries/frameworks.

For example, consider this sample Angular controller (a similarly contrived example can be created using other pieces of tech as well):

angular.module(‘DemoApp’)
    .controller(‘DemoCtrl’, function ($scope, $http) {
      $scope.getItems = function () {
        $http.get(‘/items/’)
          .then(function (response) {
            $scope.items = response.data.items;
            $scope.$emit(‘items:received’, $scope.items);
          });
      };
    });

This sample controller has a method getItems that fetches items, updates the model, and then emits the information so that parent views have access to that change.  This is ugly because it hardcodes application structure hierarchy and mixes it with server query logic, which is a separate concern.  In addition, it also mixes the usage of Angular’s internals into the application code, tying some pure abstract logic heavily in with the framework’s internals.  It is not all that uncommon to see developers make these simple architecture mistakes.

With the proper module system that ES6 brings, this simplifies to (items.js):

 import {fetch} from ‘fetch’;

  export class items {
    getAll() {
      return fetch.get(‘/items’)
        .then(function (response) {
          return response.json();
        });
    }
  };

And demoCtrl.js:

import {BaseCtrl} from ‘./baseCtrl.js’;
  import {items} from ‘./items’;

  export class DemoCtrl extends BaseCtrl {
    constructor() {
      super();
    }

    getItems() {
      let self = this;
      return Items.getAll()
        .then(function (items) {
          self.items = items;
          return items;
        });
    }
  };

And main.js:

import {items} from ‘./items’;
  import {DemoCtrl} from ‘./DemoCtrl’;

  angular.module(‘DemoApp’, [])
    .factory(‘items’, items)
    .controller(‘DemoCtrl’, DemoCtrl); 

If you want to use anything from $scope, you can modify the usage of DemoCtrl straight in the controller definition and just instantiate it inside the function.  With promises, which are also available natively in ES6, you can chain upon them in the implementation of DemoCtrl in the Angular code base.

The kicker about this approach is that this can also be done currently in ES5, and is not limited with using Angular – it applies equally as well with any other library or framework, such as Backbone, Ember, and React!  It also allows you to churn out very testable code.

I recommend this as a best practice for architecting complex frontend web apps – the only caveat is if the other aspects of engineering prevent this from being a possibility, such as the business requirements of time and people resources available.  This approach allows us to tame the beast of maintaining & scaling frontend web apps while still being able to adapt quickly to the constantly changing landscape.

About this author

Wesley Cho is a senior frontend engineer at Jiff (http://www.jiff.com/).  He has contributed features & bug fixes and reported numerous issues to numerous libraries in the Angular ecosystem, including AngularJS, Ionic, UI Bootstrap, and UI Router, as well as authored several libraries.

LEAVE A REPLY

Please enter your comment!
Please enter your name here