Front-End Web Architecture

2017-05-28 // 13 minutes

Introduction

Working in front end web development, you will quickly realize that JavaScript is a fairly broken (though slowly improving) language. I want to establish some common understanding. so here is a quick synopsis that covers some of the commonly discussed broken features of the language.

A wide range of programming fields have begun adopting JavaScript as a preferred language. Despite this post focusing on front end web architecture, I think it's important to touch on each of these fields briefly to provide resources for why JavaScript more often than not isn't the right tool for the job. Establishing that, we can than discuss JavaScript in the context of making the best of a programming language we're stuck with when working in the browser.

  • Server-Side JavaScript: Node.js - the flagship server side JavaScript offering - is fast compared to options like Ruby on Rails, but still painfully slow compared to languages like Go, Rust, or C. Moreover, after several years of working in Node.js, I feel comfortable saying the community library offerings are a complete mess, filled with poorly documented modules, abandonware, inexplicable deep dependency trees, or all of the above. Sadly, those same libraries are what we're going to see in the front-end as well. Using Go as a comparative example, I found more detailed coverage with this article and this other article. The takeaway is that Go is a more powerful, scalable language with a better concurrency model. Node.js might be better than many popular frameworks, but that doesn't make it inherently good - just less bad.
  • iOS/Android/IoT: If you're building a mobile app that handles a lot of data or user interactions, you're better off going native. Here's one good read on the issue from an engineer at Mozilla that definitely wants the web to win against native, but doesn't think it will. Here's another post from an iOS developer arguing that we're effectively at peak JavaScript (in 2013) and we shouldn't expect JS to improve much further in terms of performance.
  • Desktop Replacement: Insomniac Games spent significant resources trying to make web tools work for their production pipeline. You can read the fascinating postmortem here, but the short takeaway is that you're going to hit a performance ceiling where web tools simply won't get the job done anymore. Insomniac ultimately abandoned their web tooling after years invested into the effort.

Goals for a new web project

In the above section, I argued that JavaScript isn't the right tool for a wide range of applications. While I would love to do away with JavaScript altogether in my life, the browser makes that impossible. No amount of transpilation options will hide the fact that you're ultimately writing some JavaScript.* Since the browser makes your app more readily accessible to your community, can shorten the time needed to build a minimum viable product, and has a lower skill barrier for developers, the browser is an entirely appropriate choice for many small to medium projects - assuming that you offload any intensive processing to a different part of your pipeline.

Accepting that JavaScript is only ever going to be "good enough" is hardly inspirational, but it does allow you to eschew the constantly changing bleeding edge of JavaScript in favor of establishing a reliable set of tools. So when I set out to build a new browser project, I keep the following tenets in mind to ensure my codebase remains reasonable in its implementation:

  • Readability: Does the physical layout of your project make sense? Would a new team member (or you 2 weeks removed from writing the code) be able to easily figure out what functionality lives where? Do your variable names make sense? Is your logical flow explicitly laid out such that I can easily follow or does your codebase live in the 7th level of abstraction hell?
  • Maintainability: JavaScript makes it incredibly easy to write spaghetti code that can become impossible to safely modify and know precisely the impact your changes will have. How easy is it to modify your codebase?
  • Speed: The code will never be actually fast, but it should avoid dumb things like touching the DOM more often than it needs to.

* The only caveat I would offer here is when you're transpiling from a native code base to a browser offering. Emscriptem provides an interesting option here, but you're still going to hit the performance problems I outlined above.

Project Pipeline Components

While the final product is just plain HTML, CSS, and JS, the number of tools used to manage that complexity has ballooned over the last decade (and a half?). As evidence, check out the pie chart provided in this article. Indeed, I am not going to cover every topic but this will provide a handy guide to my thought process as I set up a new project.

  • Process Automation: I use Grunt for my build process automation. Here's a useful writeup of Grunt versus other tasks runners. You definitely want to use a task runner, since it saves you a lot of tedium and manual work. However, as long as your choice has all the modules you need, your choice does not matter. Gulp and Broccoli purport to be faster than Grunt, but I've only worked on one project where Grunt's speed was a problem. And for that project, the problem was actually the static site generator Jekyll (which you should avoid and use something that scales well like Hugo).
  • Build Tools: Since JavaScript loads code asynchronously, just using some plain old link statements to load your code is a recipe for disaster. You'll need a tool to manage module dependencies. Your main choices are requirejs, browserify, and webpack. Skip all the religious arguments and refer to this useful comparison between the three. For the record, I prefer requirejs. Again, your choice doesn't matter so pick the one your team is most comfortable with. Just as example of religious arguments, many people hype up the hot reloading capabilities of webpack, but you can totally do that in require as well.
  • JS Preprocessors: I'm personally waiting for the features of TypeScript, CoffeeScript, Babel, etc to be more readily available through the JavaScript version that ships with the browser. Proper types and variable scoping are very nice features, but don't outweigh the added complexity of the transpilation step. Also, other features like the arrow functions break my readability requirement. If you're going to use one of these options, TypeScript seems the most promising.
  • JS Frameworks: JS frameworks save you the work of tedious tasks such as data-binding to a viewmodel or controller, templating, etc. You want to select one that isn't opinionated about how you structure your project and that you will spend a minimal amount of time fighting. I personally use Knockoutjs. That said, Angular (v2 only, v1 is a dumpster fire and was completely rewritten in v2), Aurelia, and a few others are good alternatives. If you do decide to try Knockout, I'd recommend this blog as a good resource on avoiding some of the gotchas.
  • Testing: I tend to use a combination of Karma for my test runner and Mocha for my testing framework. If you're unclear on the difference, the accepted answer for this stack overflow question is worth a read. Pick a library that has the easiest to read output. They're all accomplishing the same goal.
  • Package Managers: The idea of NPM is nice - a centralized distribution method for JavaScript modules akin to yum, apt-get, or emerge. However, it's presently one of the worst aspects of doing front end work - if not the worst. As an alternative, Bower's flat dependencies are nice, but you should just use NPM as I have found several modules that were supported by npm but not bower. I'd rather use one shitty package manager than deal with the added complexity of loaded my code through multiple package manaagers. Also, please use npm shrinkwrap or you run the risk of having your dependencies change unexpectedly - which tends to break everything.
  • CSS Preprocessors: LESS or SASS are both very capable CSS preprocessors. I found LESS simpler to use, but SASS will help you reach your goals with a similar learning curve.
  • HTML Templating: I was at first very excited about the Polymer project until I tried to set up a very simple template as a quick test. It's overly complicated, comes with a lot of code weight for features I don't need, and felt very opinionated about how I structure my code. I settled on this grunt task to manage html templating.
  • Debugging: I sadly don't have any good recommendations here. Chrome's Developer Tools is lackluster when compared to something like Visual Studio's C/C++ debugger - which itself has plenty of room for improvement. Specifically, finding a js variable that I want to watch involves digging through so much crap that I don't need. My go to debugging method has been mentally working through the code with selective console logs.

Considering Alternatives

React and Vue are currently capturing the next wave of front end zeitgeist. In the interest of full disclosure, I have yet to build anything non-trivial with React or Vue. However, Vue seems to suffer from the "yet another framework" problem when compared to Knockout. Indeed, Vue's own documentation states that Vue is very similar to Knockout. It also argues that Knockout's development has slowed, but that currently isn't a compelling reason to switch as it has all the features I need. Put another way, Knockout is stable - which is very appealing to me.

From the writeups I've read on React, people are excited about it as a replacement (or at least a viable alternative) for native code. As I covered above, I do not expect this to come to fruition to the extent that people want. Indeed, some developers have written at length why they would recommend skipping React. That said, there are some features that are potentially compelling for me. For your consideration, Slant has again provided a very useful comparison. If you are building a project that will manipulate a large number of table rows (think 1000+), Knockout may no longer be an appropriate choice. It is worth noting that I've worked around this by adding in some fancy paging functionality, but that may not be the right solution for your team.

One of the common critiques I've seen against Knockout is that it becomes overly complex in large codebases or slow with large datasets. Indeed, in this benchmark comparison Knockout does not compare well to Reacts in terms of tabular manipulation. However, I'd argue that in inexperienced hands, large codebases can become a mess no matter what tools you use. For my part, I've built a complex web based IDE using Knockout that was both performant and easily maintained.* But if I was building a new app heavily centered around manipulating tabular data, I would definitely take a second look at React.

* Sorry, I can't share. The code lives in a private repo of a former employer.

Conclusion

As a quick review, JS browser apps/sites are appropriate for small to some medium scoped projects. The toolchain I prefer to build manageable, scalable codebases includes the following foundations:

In the first section I contend that there's a limit to what you can realistically do with browser based projects. I can't stand "don't reinvent the wheel" arguments, but the deluge of new JS frameworks have offered ever diminishing returns. The projects I prefer to work on in my spare time are computationally expensive, where JS is a wildly inappropriate choice. Since JS work keeps a roof over my head, I settled on the aforementioned set of tools that allow me to build sites in a sensible manner.

Even if this pipeline doesn't work for you, I hope this post has encouraged you to make sure your selected tech stack is the right tool for the job and to not get caught up in the madness of always chasing the next framework - unless that's how you really want to spend your free time.