The modern web; no framework required
JavaScript frameworks dominate the front-end web technology conversation. But do they really make it easier to build web applications?
There is an incredibly low barrier to creating a web page. With just a text editor, pictures, words and buttons can be summoned with little effort. This is a wonderful experience and has undoubtedly drawn many to the art. But attempt to create anything sophisticated, even just adding responsive behaviour, and suddenly a simple endeavour becomes exponentially difficult to understand and modify.
This is a familiar problem in programming and often solved with encapsulation. Build components — behavioural or visual elements — that are isolated and the strangulating effect of interdependent objects eases. A decade or so ago, these problems on the web began to be solved by the use of a JavaScript framework. To the web community at the time an opinionated approach to common programming patterns — classes, dependency injection, events, DOM operations — was a great step forward; with frameworks like Prototype and then Angular.
Live with a framework for a while though and some cracks appear:
- As the application grows there is a shift in focus from building on the Web Platform to understanding the framework itself. Of course, programming against a library will inform your design decisions, but programming around a framework can be frustrating.
- Application logic in markup. At first it seems like a great idea to describe behaviour in the markup, hey no coding! But any declaration in markup is programming by string matching. Brittle, difficult to debug (stepping into the framework!) and with little IDE assistance.
- Rendering all the time. Models of data are bound to a view; change the data and the view is rendered again with that data. This relieves the programmer somewhat from managing the state of the view but can have terrible performance issues, a tendency to pack application logic into parent classes and some loss of control of the event flow in the application.
- Learning the framework as well as the Web Platform. Some framework patterns are just common design patterns by a different name.
- In many cases, some degree of over-engineering.
- Lock in
Over the last five years, the relentless evolution of the web has delivered a platform and an eco-system that has standardised many of the patterns that frameworks provide. What would you need to build a modern web application without a framework?
Classes
JavaScript has always had encapsulation, just not the traditional class pattern that we are familiar with from other languages. Belatedly, ES2015 introduced classes, interfaces, extends and implements and, if you decide to build an ES2015 application, you can use these concepts in much the same way as other languages. If you are shipping an ES5 application, you can still use class syntax and transpile. There are many ways of breaking up a JavaScript codebase into smaller files whether they are called “classes” or not. And with ES2015 modules (again available with a transpile step), no need for framework dependency injection techniques.
DOM operations
Often we want to associate JavaScript classes with HTML markup. Angular, for example, sees a label on a HTML element and attaches an Angular “class” to it. Alternatively you can hold the HTML fragment within the JavaScript class. This is React’s .jsx pattern, you don’t need to tag your markup if you already have a reference to it in JavaScript.
Both of these approaches are trivial to implement with the DOM API and have been around for a long time, referencing HTML elements with something like document.getElementById()
and writing markup directly to the DOM with document.createElement()
For many device and browsers these are now incredibly fast operations (http://vanilla-js.com/) and don’t necessarily need a framework to abstract over.
Events
Events provide a very powerful method of orchestrating application behaviour; promoting encapsulation and separation of concern. Events are first class Web primitives and well understood in software engineering.
Complicated event flows can be hard to manage so some frameworks abstract over event emitting and capturing, binding model changes to other actions in the application, usually DOM renders. Binding is powerful and does remove some explicit listener / callback code but is not always efficient or transparent. Further, if it is decided that the application requires this functionality, a focused library can be used rather than a whole framework.
Vanilla JavaScript
Up until recently frameworks offered features and opinions that did not exist or weren’t readily available in the Web Platform and eco-system. This is no longer the case. Common design patterns can be implemented with standard JavaScript. Transpilation, polyfills and stand-alone libraries can provide many of the features and developer conveniences of frameworks.
The following is a example of such a library to build all DOM elements without any HTML.
const title = “BIG HEADING 🤩”;
const h1 = new Container({
type: “h1”,
class: “a b c”,
text: title
});
this.appendChild(h1);
The library (https://www.npmjs.com/package/shiva) provides an abstraction over document.createElement()
to reduce boilerplate by instantiating a class called Container
.
There are a number of advantages to this technique:
- No binding between a markup template and a JavaScript class. You are already in JavaScript. Go crazy. 💥
- No templates written in a different language (HTML).
- Program! No friction between DOM element representations. Easily turn repeated patterns into classes or factories, use inheritance or composition or any other design pattern.
- Type safety and autocomplete for DOM methods.
Interoperable vanilla libraries built in this way — that target the Web Platform — can be used with or without a framework. Here are some examples that provide abilities usually considered to be the domain of frameworks:
- State management: https://github.com/reactjs/redux/tree/master/examples/counter-vanilla
- Virtual DOM: https://github.com/Matt-Esch/virtual-dom
- Server side rendering: https://www.npmjs.com/package/prep
- HTML templates: https://github.com/polymer/lit-html
- General purpose: http://jquery.com/
Great software composes functionality, progressively adopting new ideas and technologies to reflect the priorities of the project. This is always a challenge, but much more difficult with a codebase built upon a framework rather than against libraries. The majority of web applications can be constructed with Web Platform APIs, common design patterns and a few focused libraries (http://microjs.com). Let’s build on the modern web; no framework required.