Being a “naked” web developer

Using modern web standards, we can add new features/powers into the browser in a snap. No React, No Angular, No Nothing. Is this too good to be true? Can it be that we are actually at the point where all the shiny component frameworks are disposable? Can we all be freed from the framework fatigue?

js_framework_timeline.png

Probably not (yet) #

customElements.define('my-tag', MyCustomHTMLTag);

I was playing around with the idea that front-end developers can drop React or Vue.js in favour of new standard APIs implemented in all evergreen browsers (and soon even in that brownish-green one). I am a real web components fan, and a very lucky one, that actually works on a production web-application that is written completely with web components, zero bundlers, zero external tools whilst high performance is the top priority for all features. About 90% of the codebase is in-house built with javascript, relying partially on Lit-Element and my own slim.js.

I have proven, at least for myself, that web components, web workers, CSS apis and other goodies are blazing fast, robust and feature-rich without much sweat. Choosing very carefully where you rely on 3rd party code (in this case, Lit-Element and slim.js, the rest is in-house development), working without the common tooling (no bundling/packaging/minifying/babelifying…) can achieve both top performance and clean (and fully tested) code.

But when I talk to people about it, they won’t easily accept the concept. They all mention how React/Vue is easy to use with a small ramp-up time (which is true), and that they are reactive and easy to use (also true) or that Angular provides all their needs (mostly true). I keep arguing that I can achieve even better performance and still code easily keeping everything tidy and clean without big efforts… I simply cannot convince them.

So I question myself why? #

I think that the main reason is that developers love the idea that they have safety nets when they work. Probably the feeling that a stable framework and libraries, backed by top software companies and supported by community developers, are a sure safety net. I think in many use-cases they are simply wrong.

I tend to look at things from a different angle: my safety net is the browser. The browser provides me most of the features that are provided by any framework, natively. The browser vendors are following web standards while libraries become obsolete over time and they tend to be replaced by hypes and trends.

Digging in #

Assuming that browsers support the modern standards, either with polyfills or natively, we get the following for free:

Things that are not to be found in the browser API and needs to be implemented with sophisticated techniques or compiling:

If I take a look at the good things that the browser does not provide (and probably never will) - Then I have to rely on a server-side rendering or compiling the code to achieve all of these.

Digging deeper #

Observables #

Observables can be achieved on runtime, depends on the paradigm that suits the project. Either go for pub-subs (as in RxJS) or use a middleware approach (Redux stores), or simply create any model behind object proxies, and 99% of the use cases are covered. Neither approach will hook my application into a specific framework. I have even created my own simple and fast observable system, oculus-x that can even work with non-observable objects and be reactive, no proxies required. It’s as light as 1K and faster than MobX.

Dependency Injection #

Now this can get tricky, but can still be achieved. Dependency Injection suits the OO approach (functional approach can simply ask for a context to work) and can be solved using the class mixin pattern. Javascript is the runtime for mixins. Anything that requires dependencies can inherit some DependencyInjectionMixin class with a static property that describes the dependencies, and during the construction of the object it sets everything up using some injector. I first met this approach while working on an AngularJS project somewhere around year 2010.

Quick in-house implementation for the DI problem:

// /path/to/lib/DepInjMixin.js

const factory = (dep) => {
  // handle it, could be a string or a class or a factory function (or anything)
  // can be configurable if you prefer to expose the libraries to multiple projects
}

const inject = (subject, deps) => {
  try {
    Object.entries(deps).forEach(([prop, dep]) => {
        const entity = factory(dep);
        subject[prop] = entity;
    });
    subject.depsReady();
  }
  catch (err) {
    throw new Error('Invalid Dependencies'); // handle it properly
  }
}

export const DepInjMixin = (BaseClass = Object) => Class DepInjMixin extends BaseClass {
  constructor () {
    super(...arguments);
    const { deps } = this.constructor; // Getting the static property
    inject(this, deps);
  }

  depsReady () {} // abstract
}

Now we can use it:

import { DepInjMixin } from './path/to/lib/DepInjMixin.js';

class SuperPowerComponent extends DepInjMixin(HTMLElement) {
  // inherit both HTMLElement and DepInjMixin
  static get deps () {
    return {
      appModel: AppModel, // could be a singleton observable object
      eventBus: 'EventBus', // the factory decides what should I get
      // ... more stuff
    }
  }
  depsReady () {
    this.appModel.someValue = 42;
    this.eventBus.emit('Hello');
    // and so on
  }
}

Very little code to achieve very powerful behavior. Delegating the readiness of the dependencies onto an async function can speed up responsiveness when heavy stuff needs to be moved to client-server connection or web-worker doing some preparations.

Dealing with environment variables #

There is no proper way without relying on a server, either during build time (webpack/rollup/other for CDN) or deliver the values on the HTML file (nginx/web server). If we rely on the runtime approach, we can expect the HTML file to contain something similar to that:

<meta environment-variable key="apiEndpoint" value="https://my.production.server" />
<meta environment-variable key="buildVersion" value="3.14.15729" />
<!-- as many variables as you want -->

<script>
  global.getEnv = function (key) {
    const elem = document.querySelector(`meta[environment-variable][key=${key}]`;
    if (elem) {
      return elem.getAttribute('value');
    } else {
      return null;
    }
  }
</script>

I have used this approach and proved it’s doable while consulting at E8 Storage and Sarine Technologies.

This can be retrieved during the application startup. These tags needs to be injected somehow before deployment.

JSX #

Well, this one is tough. I personally prefer to use string literals as they provide a very similar developer experience and there is no need for a custom parser and more weight to carry on the back of my application. Can’t be replaced if you really fancy using XML in JS.

Bundling #

ES6 modules + HTTP2 (+gzip) does the thing. Today we even have HTTP2 CDN services all around the web. The difference between minified and gzipped code is so small that sometimes a whole application’s overhead is less than 1K difference. Code splitting and lazy loading can be done with imports and dynamic imports (polyfill if you have to) and it simply works.

Type safety #

Since typescript is not yet supported there are two approaches (both I have tried)

Recap #

  1. Most moving parts of a framework are provided by the browser.
  2. Missing pieces can be achieved in a very thin layer of code (less than 0.5K) therefore are quickly parsed and do not add weight to the application.
  3. Some fancy features are nothing but fancy. In my personal opinion, JSX is an example for abusing the great idea of supporting XML objects in JS as part of the syntax into processing XML to JS objects and back to HTML (subset of XML). It’s feels like going around to world just to travel 1 mile.
  4. Frameworks provide safety nets for conventions, configuration and define the way the developers work on a project in the business vector, not in the software perspective. I truly believe there is room for using frameworks to help us keep structure and order, but we can’t let them take over WHAT and HOW we like to do things.
 
50
Kudos
 
50
Kudos

Now read this

Micro frontends, in practice

“Microfrontends” is a hot topic nowadays. Since front-end projects become big and feature-rich, as the codebase gets bigger, tooling and organization are essential for a successful project. I would rather think of micro-frontends in... Continue →