minimal stack

My minimal stack and approach for writing professional single page applications (SPAs).

Introduction

When writing a web application, one has to choose from a vast collection of options. You may deploy it as PHP enriched with some jQuery (don’t laugh, that’s probably the majority of contemporary web apps).

PHP usually means server side rendering. I opt for single page applications instead, because in my experience it is a similar effort but if done right, you end up with a superior architecture, better user experience and better maintainability. But the expertise required for getting this right is quite different and in some regards goes far beyond what’s required for server side rendering.

A major point to consider is that you’ll need a server API if you want a SPA. API design goes wrong more often than not. So if you are unsure and don’t have somebody with the required experience, go for server side rendering.

Modern SPA development usually involves pulling in hundreds of thousands or millions of lines of third party code. This is no exaggeration. Webpack is the go-to solution for building and bundling. Its latest release as of this writing is a 20 million byte download. That’s millions of lines before you even started to choose a framework.

The problem with all that third party code is: you use it, you own it. That means you will have to invest effort into keeping your third party code patched and running. A couple of years down the line this effort will likely become significant. It also means that in that future you’ll need to find developers willing to work with your dusty weird code base instead of the hot new stuff others are having fun with.

If you are unwise and unlucky enough to choose third party code that becomes abandoned upstream, you’ll really own it – you’ll not merely have to patch it, you’ll have to develop these patches. If that codebase is substantial, you’re toast.

Thus the best third party library is the one you did not include – just as your best code is the code you never wrote. However, that latter wisdom does call for library code. Best established practice has you organize your code in a certain way and without using any library that means you’ll write a considerable amount of repetitive boilerplate. I don’t want that, it’s bad.

Over many years I went from pure vanilla via libraries and frameworks to my current compromise. The rest of this article lies out the principles on which I build my current approach. I hope it enables you to figure out your own minimal stack.

Two Kinds of Code

Actually more than two, but for simplicity’s sake: you’ll write user facing code that runs in the browser (tier one code). You’ll also write code for building, testing, deploying and whatnot this code (tier two code). You don’t control the environment (browser) in which the user facing code will run. You do control the environment that the latter life-cycle automation and management code will run in. Thus the former code should be held to stricter standards than the latter.

One of my guiding principles is: keep my tier one code standard conforming. It’s code that a modern browser can execute without any ado. That means, the tier two code is – for a large part – exchangeable!

For tier two code I try to restrict myself to stuff that is either exchangeable or such a widespread standard, that chances of it being abandoned in the next decades is minimal. In web projects NPM is such a standard. Thus life-cycle management happens as far as reasonably possible in NPM scripts (calling shell scripts or gulp if they become too complex).

Here’s a list of the NPM tier two dependencies in a project I’m currently working on:

	"devDependencies": {
		"@rollup/plugin-node-resolve": "latest",
		"browser-sync": "latest",
		"chai": "latest",
		"chrome-coverage": "latest",
		"eslint": "^8.4.1",
		"eslint-config-google": "^0.7.1",
		"eslint-plugin-html": "^1.7.0",
		"jsdoc": "latest",
		"jsdoc-to-markdown": "^4.0.1",
		"mocha": "latest",
		"mocha-headless-chrome": "git://github.com/schrotie/mocha-headless-chrome.git",
		"rollup": "latest",
		"rollup-plugin-terser": "latest"
	},

These do 5 things:

  1. provide a development server (run the code) browser-sync
  2. lint the code eslint-*
  3. build the code rollup*
  4. test the code mocha* chai & chrome-coverage
  5. document the code jsdoc*

1-3 are exchangeable and require very little configuration. Should they become obsolete/abandoned it’s very easy to skip or replace them. 4 and 5 are the most widespread standard tools for what they do. Should Mocha become obsolete, that would be a major pain in the behind, but it is rather unlikely to happen in the foreseeable future. I’ll get into more detail about testing below. JSDoc has been around this whole century, yet it feels slightly more obscure. Still the best available doc-standard to my knowledge.

require import

If you are still using require, that’s a hallmark, that your code is soon going to be legacy. You absolutely should be using Ecma Script modules with import by now. Personally I’ve been using import for years now, and I’ve been using it without a build step, and you should, too!

It has been a minor pain to do that, because one needed to use full qualified paths everywhere, which comes with its own set of maintainability drawbacks. It was still better than using require or a build step, as far as I’m concerned. Note: you are certainly going to use a build step for production, see below. However, now you can also use nice module names and paths in Chrome and Firefox, and very likely stick to the future standard with importmaps.

For my liking skipping a build step during development is a major gain. It gets a huge layer of complexity out of my way during development and it speeds up development. With something like browser-sync and without a build step, iteration/feedback is instantaneous. I know and love stuff like vuex state persistence. But in my experience, this can usually be trivially mocked in development and the speed and simplicity of working without builds and codemaps is much preferable to me.

One significant drawback is, however, third party dependencies often do not yet play well with this approach. The situation is improving, but as of this writing, if you tried this approach, you would find yourself throwing out some dependencies because they are too difficult to get running, and considerably bloat your importmap in order to still get others to work.

If you are me, though, this is manageable, because you radically restrict the use of third party dependencies in tier one code anyway.

no God Framework

God frameworks are big frameworks that do everything for you (Angular, Vue, React and others). I got the term from this excellent article, which I also recommend to persuade yourself, that you may want to avoid them. Also consider this excellent and balanced analysis.

I’d like to add one essential point, though, that the aforementioned article misses, because it concentrates on technology: the problem that god frameworks solve is not a technical problem, but an organizational problem – they drastically limit the degrees of freedom of implementing your project. Thus they get all involved developers on track, reduce the need for communication, improve coherence and high level structure of a project.

All of these are good things! If you do not have a good organization, good communication, if you are unsure about how to structure code, by all means, choose a framework. If you don’t you’ll have to do this work yourself. It is not more work, you’ll have to do, but you better know what you are doing. Skipping frameworks increases the number of ways in which you can fail. However, when you succeed, you’ll be rewarded by a more fulfilling job – because of a much improved feeling of self agency – and a superior result.

The platform is getting better and better, continuously reducing the need to resort to any third party or library code at all. However, there is a few things, where you should resort to library code.

API microservices are mostly pretty trivial. What you need there (if you are not coding in Go – its standard library has you covered), is a request router to help you structure your code in a transparent reproducible way. Try to find something that does routing and nothing else. I wrote prouty because it allows me to write very concise modern code, but that’s a matter of taste. Just find a router or write one yourself, it’s no magic. You also need a DB driver. That’ll mostly be determined by the DB you use.

The frontend needs two things: state management and DOM manipulation. If you work on a somewhat complex SPA, just use Redux for state management. It’s minimal, completely independent from React and does its job. Redux is as good as popular libraries get.

On a side note: Redux’ value lies in nudging you to write stateless state manager code. What sounds like an ironic remark is a major gain in maintainability. If you are not familiar with functional programming and/or if you are not used to think in terms of stateful/stateless code, I suggest to rather use a framework and familiarize yourself with these concepts before embarking on min-stacking.

If your software is not that complex, Redux will add considerable complexity overhead. I wrote xt8 as a minuscule toolkit for putting together your own state management. But again: just choose or write some state manager that suits your needs. If you know what you’re doing, writing your own alongside your project is also perfectly fine.

DOM manipulation is not so much about manipulating DOM as it is about structuring your code. This is a pattern that repeats all through this section as you may have noticed. The DOM is organized in a tree, and from looking at the DOM it should be easy to find the place in your code, that deals with that DOM. That’s about it.

Nowadays this job is usually taken by the data binding facilities of your favorite god framework. But you can also do this yourself, just do it the same way anywhere in order to remain maintainable. Cut your DOM up into web components (likely without shadow DOM) and use these for structuring. I wrote bindom for my own needs and it does structuring and data binding at pretty low cost and non-intrusively.

With all the tools I mentioned above, my personal tier one stack clocks in below 2K lines of code for front- and backend, gives me great structure, great performance and no bloat. This makes a world of difference. With this stack I’m at least a freaking order of magnitude below the code size any of the shelf framework can offer – probably it’s more like two orders of magnitude and possibly even three (if you go full angular and stuff …).

This is a huge gain and for that it is okay to compromise. 2K lines instead of 200K means you don’t have to worry at all about whether that code will be maintainable ten years from now. Sure it will be. The stack becomes abandoned? Just own it, 2K lines will be less than 10% of your whole app-code, even if your app is on the simpler side. Only real simple stuff will be on par with 2K lines of code, and in that case you don’t need to worry about structure and should absolutely go full native without any helper libraries.

Quality Assurance

Professional software comes with automatic quality assurance. If you deliver anything without automatic QA, you have not done your job and you wasted your time and your clients money. Simple as that. And because that is so fundamentally true, don’t let your client or anybody tell you, you don’t need automatic QA. That is your decision not theirs, you should be qualified to decide that, not them. Take pride in your work, deliver quality and the tools to prove it.

The most basic kind of JavaScript QA is a linter. This is an exchangeable tool, so don’t ruminate over it, just choose eslint 😉 Whatever – since you should have at most a few linter specific comments in your code, swapping it out for something else later should be doable. Just choose a linter and make good use of it.

Then there’s unit tests. Mocha is the most common test runner and it also underlies other tools. Thus Mocha is a good bet for a test runner. The runner may be your most critical third party software choice. The reason is that much of your test code will be more or less specific to your chosen runner and exchanging the runner will imply a major test rewrite.

I also use Chai for assertion in Mocha tests. Chai is pretty popular but the case for Chai is not as strong as the case for Mocha. You can go without an assertion library without excessive overhead, while going without a runner is less advisable.

Look for clear structure and good reports in your runner. Unit tests are also an essential part of your documentation because the tests articulate very precise expectations for your code’s behavior. Thus you want to be able to find the test for a given part of code.

I found that aiming for a unit test coverage of 95%+ is realistic and sensible. You really want most of your code tested but there are cases, where it’s not worth the effort (like browser specific code and some integration code and sometimes error handling).

The key for getting good coverage with reasonable effort is writing your tier one code to be testable. All code in your state manager should be trivially testable. It should be stateless and utterly DOM-independent, so just pull it in and run it. Your DOM manipulation code should ideally be mostly setting bound (as in data-binding) variables, then testing that is also trivial. This leaves little code that’s more effort to get to.

I usually write mocha tests that run in the browser. That way I can rely on the browser’s debugger when developing my tests. But tests should also run in automatic pipelines where the test report should be logged and evaluated in the console. I use mocha-headless-chrome for this. I can thus use the exact same tests for debugging in my favorite browser and in the command line in pipelines anywhere.

Ensuring that the unit tests cover the code sufficiently should also be automatically enforced. I found that only browsers reliably ship the newest features I tend to use and I want my code to be unit-testable without polyfilling and/or transpiling – thus I can also enjoy minimal stack complexity when developing tests.

However, that means test coverage has to be determined in the browser on the raw source code. That is not something that istanbul – the goto coverage solution – does. However, Chrome comes with built in coverage reporting. I modified mocha-headless-chrome to allow me to extract Chrome’s coverage report from its runs. I also wrote a collection of simple tools to work with Chrome’s coverage report.

This setup allows me to significantly reduce the complexity of my test-environment (as compared to e.g. instanbul). At the same time I get superior coverage evaluation and reporting because Chrome’s report catches unexecuted statements in lines that have executed statements, too. My own coverage report that parses Chrome’s report is superior to Chrome’s own report inside its dev-tools, which has some problems. Mine does not account for source maps, though.

Should you need coverage reporting for node.js projects, you should look into C8 or something like it. It gives you configuration free exchangeable coverage reporting with a lot less complexity than istanbul (since you use node anyway).

In most cases you also want integration and possibly end to end tests for your frontend (and the latter also testing your backend). There are several frameworks for this (here you absolutely need a framework) and I recommend Selenium. It’s standard and it’s the only widespread solution that I’m aware of, that has wide cross browser support.

Apple’s Safari is the new Internet Explorer and you likely want to test for it. You may also want to test on desktops and mobiles in order to assert the responsiveness of your app. Selenium allows all of this, possibly with the help of something like browserstack.

Writing Selenium tests has also become a lot more straightforward with the advent of async/await. So these days I consider it a reasonable choice. As a runner you can still use Mocha with Selenium.

Build & Deploy

I aim for keeping my JavaScript files below a hundred lines or so, and I belief so should you. But that means even rather small projects usually comprise dozens of files. When there is few fonts and graphics I aim for delivering my SPA as one single HTML file. That way I can occasionally just send the file to stakeholders for review without them requiring a server. Again: minimal complexity.

I any case production frontend code should be comprised of few files, thus a build/bundling step is absolutely required in the end. But since everything I do runs in modern browsers without build, building is just bundling (and possibly including some extra polyfills) and can be done with an exchangeable toolchain. I chose rollup years ago, when there were less available alternatives. You should just choose a minimal standards compliant ESM bundler that suits you, and have an eye on exchangeability.

I already wrote a bit about JsDoc. I use jsdoc-to-markdown in order to be able to deploy docs in tools like GitLab or GitHub and have them display nicely there. Up to date docs should always be deployed automatically.

Finally the act of deployment is completely dependent on where your pipeline runs and where you want to deploy. I believe everything, including deployment, should also run locally on any developer’s computer just using NPM. That means you try to restrict your use of platform specific tools on e.g. Azure or AWS.

Still, for complete lifecycle management, NPM alone may be too limited, especially if you want to share lifecycle management code between projects. I much prefer writing code for that instead of configuration and thus I gravitate towards shell or gulp for such tasks instead of things like grunt.

Conclusion

One can write professional SPAs and microservices with minimal dependencies in tier one code. The key is leveraging the standard libraries of the browsers and of node/deno/Go/… to their fullest. Tier two code is harder to free of dependencies and I argue that in case of tier two code dependencies are more acceptable.

When you embark on this journey of figuring out your very own minimal stack, you’ll be rewarded with a more fulfilling work and better long term value for your customers.

The 30 Year Web App

How to build to last – the PERI Web Framework

“We built our current ERP which lasted 30 years and we want you to build our next ERP to last for another 30 years.”

The current ERP is built with COBOL and the next one will be a micro service suite with web UIs. My part is mostly the user interface. I’m paraphrasing and condensing the introductory quote from my memory of the original German utterance. Yet that sentence captures the primary and driving inspiration that motivates the work I do for my current customer.

Mind you: I will be writing about somewhat complex web applications here. Those are quite different beasts from your standard web pages (or even shopware). The latter may have a lot of coded bells and whistles. But the former require a level of architecture and code structure that no “mere” presentational page needs.

Eternity Squared

In case you missed the outrage in the introduction: in web development 3 years is the timeframe in which the whole technology stack of web development used to be redefined. Things are just slightly slowing down these days but 30 years is still, for a web user interface, eternity squared.

So how do you even attempt that? This article describes my take on building web tech to last. Only time may tell whether my approach to this problem is worth a dime.

However, I believe that some considerations I will be presenting here are well worth considering by many businesses. It is not essential that my approach is great in order for the time thinking about building to last to be well spent. I will keep this article on the PERI Web Framework high level so that non technical folks may have a chance to follow.

Own It

I’m pretty convinced, there is one aspect that is hard to avoid, if your aim is to build for a time frame of decades: You own it. All of it. If you now pull in megabytes of libraries, 20 years from now you will have to maintain megabytes of libraries. And that’s not going to happen. 10 years from now you will have a very hard time finding developers willing to maintain old versions of framework x. And thus, in one fell swoop, Angular, React, Vue, and all their smaller brethren are out.

The Arvo (Angular, React, Vue, and Others; take a second to memorize “Arvo”, it’ll occur dozens of times below) approach does one thing very well: Provide a technological framework and low level technical process to guide a web project to success. We all know, tech projects fail. More often than not. Arvo reduces the failure rate.

The Best, Established Framework

Before I move on, I’d like to mention a related aspect: I evaluated frameworks to use for PERI. The clear winner is React. Still talking about long term viability here.

Angular arrived on its decline. It is still very strong but many indicators point to it loosing significance in the long run. Vue is great and promising but as of this writing it is still too young to tell whether it will keep its appeal and whether its lack of backing by strong business entities will allow it to thrive in the long run. Jquery is technologically 90% obsolete and does not address questions of architecture and code structure. Everything else is too obscure to consider.

So if you think about building to last and are not going to be convinced by what follows, I suggest to have a very good look at React. It is extremely popular, still rising, has strong business support and it has more things going for it in terms of long term viability than mentioned here.

Don’t Fail

So, if your business happens to not be Google, you probably should strive to try to not own Angular. There’s more to it than its eventually inevitable demise: Whatever Arvo you use, you’ll need to update every year or so to keep up with your framework. If you don’t, you’ll have a hard time hiring developers five years from now, that are willing to work with a long outdated tech stack.

You see: web developers maintain their labor value by keeping up with the fast evolving web tech stack. Working on old tech will degrade a developer’s business options over time. And it does not appeal to most.

However, in all likelihood, there will be little slack in your project. Deadlines will keep approaching and updates will be missed if you are not very persistent about them. If you keep missing updates the cost of updating will increase and you may fast enter a vicious circle that ends in your code becoming obsolete long before the ultimate demise of your chosen Arvo.

Arvos shine at initial success yet are mediocre at best at long term maintainability. Still, we will need to replicate that initial success in order to get to the long term part that this article is about. So what is it, that raises the success rate of Arvo projects?

Structure & Productivity

It’s two things, actually: structure and productivity – though both end up being two sides of the same coin. “Structure” is part of the very definition of a framework as opposed to a library. A framework imposes structure on your code. You can still pretty much do whatever you like, but with a given Arvo the “natural” (and in many cases pre-documented) way of doing something will likely be less fatal than what your average rookie developer will do without the Arvo.

“Fatal” here means just that: fatal for your project. No line of code and no hundred lines of code are ever fatal. But if the bad-decision-ratio in your whole code base exceeds a certain threshold, it’s better to start clean. Arvos improve the bad-decision-ratio developers make while implementing your project.

Developers are also initially more productive using an Arvo, and that is the reason they generally approve of employing one. However, the structural quality of a project’s code base determines the long term productivity.

If bad decisions keep piling up, changes and new features become ever harder and slower to implement – up to a point where development is deemed exceedingly expensive or slow and the project fails. Thus short term productivity is the initial driving appeal of Arvos while the structural superiority they impose drives the long term productivity and with it the project success.

In other words: we’ll need something that boosts initial productivity in order to drive adoption and keep our developers happy, but we really need to impose great structure in order to ensure long term productivity and ultimately success in the long run.

Initial Productivity Deemed Essential

With regards to initial productivity you may doubt that it’s a necessity. You are building your project and you are hiring your developers and you can just tell them to use your great framework. And they will.

However, they will also take shortcuts. If you want to keep things reasonably simple, you can just not technically exclude the possibility of shortcuts. And if you maintain any kind of pressure or just expectation on development, developers will take shortcuts. Always.

Thus initial productivity is also our lever to keep the number of shortcuts down. If developers are more productive doing things the right way than doing it any other way, then they will more likely do it the right way.

This intimate connection between productivity, structure, and long term success raises the bar for replicating Arvo’s initial success quite high.

KISS

Above I scrapped Arvo for long term pushing too complex a code base into our ownership. So obviously, whatever we do – maybe you find some obscure project that fulfills all requirements, maybe you end up shipping your own or stitch something together from various projects – we must Keep It Simple, Stupid, i.e. we must adhere to the KISS principle.

This is something else to consider about Arvos: They are built to be able to cope with everything. Angular was developed by Google for its vastly complex Google web apps, React was developed by Facebook to drive Facebook. All successful Arvos are capable of driving the most complex Apps and come with modules to address the most obscure recurring programming problems.

The structure they impose (if you go for something like Redux/Vuex as you absolutely should) errs safely on the most complex side. That’s because they need to cover Facebook. If you are not developing Facebook but just some slightly complex ERP, that structure is clearly over the top overengineering, resulting in reduced productivity.

You built it or you just simply use it (for thirty years) you ultimately own it. Thus it is well worth a significant up front investment to arrive at the simplest thing that could possibly do. The PERI web framework is about 2K lines including extensive documentary (API reference) comments. This is well inside the scope a medium-sized non-software-focused company can maintain.

4 Kinds of Code

Whether you go full Arvo, full custom or something in between: you’ll end up with 4 kinds of code that you should evaluate differently. When you go Arvo, 2 of these kinds of code will come from the Arvo. I’ll go over them in the order of their level of criticality, most critical first.

The web framework code is the customer facing code that will run in all the apps you will develop. It will run on a plethora of browser engines over the decades, facing countless individuals on their screens, AR glasses, retina implants …

If that code breaks because of some future incompatibility – as it likely eventually will – then all your apps break. You must hold that code (whether it comes from an Arvo or from yourself) to the absolutely highest standards of quality and maintainability. It must be small, very intelligible, well documented, thoroughly tested – altogether nothing short of great.

The next kind of code is the actual customer facing implementation of your apps on top of the framework. This will be the bulk of the code that you’ll need to worry about. This code is also the Raison d’Être for the other three kinds of code. It may thus be your primary concern most of the time.

If that code breaks – it will; the same applies as for the framework since it will also run on countless yet unknown platforms – one application breaks. If you use the same idiom in several apps all of these will break.

While you may be more lenient with regards to its quality, this will still constitute the bulk of the code you absolutely must keep running. Thus the absolute maintainability of the whole thing is essential and exceeding thresholds of maintainability equals failure.

The third kind of code is test code that automatically tests the first two kinds of code. Furthermore there potentially are several different kinds of test code, but for the sake of this discussion subsuming them as one will suffice.

The volume of the test code should be in the same order of magnitude as the volume of the former two kinds of code. However, the complexity of test code is usually much lower than that of the implementation code.

If the test code breaks, you’ll usually lose your ability to deploy new versions of your software. But you are the master of the environment where the test code runs. Thus – while tedious – you may set aside a dedicated software environment for that code to run for the next three decades. You will not have to react to unexpected breakage immediately.

The test code must still be maintained. It will become more important with time. It constitutes the ultimate documentation of the original intent of the tested code. It will be absolutely essential for developers 20 years from now maintaining any reasonable level of productivity when addressing new features or problems in the above two kinds of code.

In short you must maintain the test code along with anything else. But due to its lower complexity and due to the fact that you have absolute control over its execution environment, you may be more lenient with regards to quality here, than with the two former kinds of code.

The fourth and final kind of code to worry about is code that you will use throughout development but that is not itself part of your projects: the build scripts for your apps, the test environment, the development server for web developers and so on. This code may come from an Arvo, but you’ll still interact with it, be it by configuring your build, injecting mock data into the development server, or running your test suite.

If that code breaks, development slows down or comes to a halt. You somewhat control the environment it runs on. However, if it is less demanding with regards to its environment, productivity will profit.

If parts or the whole thing break for good, you can swap it out completely. While this will be a pain, you will likely do it eventually as new, better tech becomes available.

For these reasons, with regards to the fourth kind of code you have more freedom than with the others. You may pull in a plethora of dependencies in order to boost developer productivity. If stuff breaks, productivity may suffer intermittently but breakage here does not equal failure.

∑ Premise

Congratulations! You just made it through the introduction – almost. Let me sum up what we learned about replicating Arvo’s initial success while maintaining long term viability of our code base. The framework we are looking for must fulfill these requirements:

  1. It must provide an initial productivity boost.
  2. It must impose a good structure on the code written on top of it.
  3. It must be as simple a possible.
  4. It must adhere to different standards of maintainability depending on which kind of code we are talking about.

Sounds great, right? Well, without further ado: let’s do this!

Structure, what Structure?

I’ve been yammering on about good code structure without going into any specifics what that might be. The first thing to note here is that virtually any structure is better than no structure. “No structure” is one endless line of code without functions, just one abominable line of spaghetti. Be aware that any program can in principle be written that way and that by default much too much code actually looks like that – with the minor bonus of some whitespaces having been replaced by newlines.

There is a really simple remedy for this, an easy management win: lint. A linter is a piece of software that checks code for various criteria. “Eslint” does this for EcmaScript/Javascript for example. So put a linter into your continuous integration chain and let a code commit fail if it does not adhere to your coding standards.

You should have a good look at what your linter offers and decide with the team, what to enforce. But one thing you’ll want to enforce for sure is: structure. So have a reasonable limit on line length (e.g. 80 characters), a reasonable limit on function/method length (e.g. 10 lines) and a reasonable limit on file length (e.g. a hundred lines). I recommend liberally permitting exceptions to these rules but in general enforcing them.

Your colleagues/developers may complain that these limits are too low. Trust me, they are not. These limits are 90% of your code documentation and thus extremely valuable for your long term success. You will also employ the good coding practice of using good speaking names for variables, functions, and files.

A developer worth her money will quickly find a well named function in a well named 100 lines file in a well named directory  and she will merely need seconds to roughly understand what that ten reasonably short lines of code do. No outdated comments or documentation required in 99% of the cases.

Divide & Conquer

The above mentioned method of enforcing any structure is not directly Arvo related. What will be discussed next goes to the very heart and soul of Arvo, though. However, in order to understand what this is about, we must discuss a coding problem very specific to user interface programming.

Your web application consists of user interface components (aka widgets) like dialog windows. You will want your dialogs to have a consistent appearance and behavior (i.e. user experience or UX) across your applications. Thus you will re-use code that delivers consistent UX with each use of a dialog regardless of its content. You will inevitably end up writing at least some custom widget code.

Widgets constitute one (reusable) kind of application code. Another kind is the actual application logic or business logic of the app. These two kinds of code are vastly different beasts. Widget code directly interacts with the huge browser APIs for working on how things look and what the user does with the UI. Widget code is usually re-usable. Business logic is pure custom JavaScript code. It is usually not reusable.

However, business logic ties together several widget APIs and other kinds of APIs (like communication, state management and so on). 

Trouble is: (Widget-)APIs change and such changes break apps. Now almost any Arvo employs the same simple principle that does not solve but significantly alleviates this problem: Data Binding.

Data binding means: when a developer changes a (state-) variable in his business logic code, that change will “automatically” be reflected in the visualization (widget). And changes a user triggers in the visualization will “automatically” trigger specific code of the business logic.

This magic trick of data binding in itself fulfills the two essential requirements we identified above: it makes the developer’s life easier, i.e. provides an initial productivity boost, and it separates business logic from UI logic, i.e. it imposes superior structure.

The latter is achieved by redefining the API that developers use for interacting with the DOM (i.e. visualization). Instead of calling various functions with more or less complex arguments, all they ever do is change variables and register callbacks. How variables influence the DOM and vice versa, and how the DOM triggers callbacks is defined where the DOM is written down.

By defining a parsimonious yet somewhat comprehensive DOM-API, Arvo significantly reduces the pain caused by Widget-API changes. Since all you ever do is change single variables, it is most of the time easy to keep existing API calls working while adding new variables on you widgets, thus extending your API without breaking existing stuff.

Put another way: Arvos impose superior structure by defining a relatively small universal DOM API and thus significantly improving separation of business logic from visualization code.

Bindings

Replicating this may seem like an ambitious proposition. It is indeed not trivial to get this right and requires significant experience. Yet it is less of a feat than it might seem.

The browser’s DOM APIs show you the way. There is grand total of six ways of interacting with the DOM:

  • change DOM attributes
  • change DOM properties
  • change DOM text
  • change DOM elements
  • react to DOM events
  • call DOM methods

For your data binding needs it is totally sufficient and indeed wise to restrict yourself to four of these: attribute, property, and text changes and reacting to DOM events.

Attributes, properties and text lend themselves to plain two-way data-binding, i.e. you’ll want your framework to keep them in sync with bound state variables.

I strongly suggest following the proven React philosophy of bindings-down/events-up. That means that you use data bindings to change the state of the visualization and prefer (DOM-) events to trigger business logic code when things happen in the visualization.

It does make sense, though, to bind atomic values of form fields to business logic variables and have those changes trigger code along with updating the bound variable. This will save quite some typing and does not ruin the code structure. However, if you have changes (especially in more complex widgets or nested sub-modules of business logic) then don’t propagate those upward through bindings, but use events instead!

As for the binding syntax I strongly oppose the almost universal approach of whisker bindings. The reason for this is, that it introduces magic right into the DOM itself. Very few developers understand how whisker bindings work. Using stuff that few developers understand is bad now for many reasons and will be worse decades down the line, when the syntax may be forgotten or (which may be even worse) has become part of some standard.

It is my strong conviction, that the DOM that a developer writes should be plain and valid standard DOM. No strings attached. The reason for this is: your DOM will thus be plain and valid standard DOM for decades to come and developers not even born yet, will be sure to understand what it says – without reading decades old documentation of long dead frameworks.

That approach leaves you with precisely one (sensible) way to express your binding syntax: put it in (custom-) attributes. I chose to use one universal attribute: data-bind. Therein one or more bindings can be expressed, the syntax clearly shows the direction of the binding (i.e. from:to) and by prefixing with special characters expresses what kind of binding we have. 

data-bind=$variable:@attribute;.property:$variable;$variable:§;methodName(domEventName)

Note that this is valid HTML without quotes, with quotes you could use line-breaks instead of semicolons in order to improve readability. The paragraph (§) character indicates binding to the element’s text.

This is really all you need. I added a very little bit on top of this, but the most important thing when building to last is KISS!

Widgets

We are still missing two DOM interaction modes: changing DOM elements and calling DOM methods. You may disregard them in your data binding, but you will still need to use them occasionally. So if you need to get down to that nuts-and-bolts level of DOM interaction: write a widget.

For this you do not need any framework because the web standard defines everything you need to write widgets. They are called web components. I’ve written about those in previous articles, so I’m not going to repeat that here.

A tiny DOM manipulation helper will come in handy though (i.e. boost productivity). I use ShadowQuery about which I have also written extensively already. ShadowQuery can also provide 50% of the reusable code you need for data binding.

I learned – over many years of working with web components – one fundamental lesson about web components: don’t use shadow DOM but do use custom built-ins.

Shadow DOM is an extremely powerful feature of web components and it makes a lot of sense, too. However, if you are writing your own suite of web applications (or even just one sizable app) steer away of shadow DOM. It’s more trouble than it’s worth. Shadow DOM is designed to write universal widgets that are independent of a given (suite of) applications and will be re-usable across companies. This is likely not your primary concern. Shadow DOM also has unresolved issues with regards to styling/theming.

Custom built-ins on the other hand allow you to add custom functionality to existing HTML elements like <input>. This is extremely valuable. In particular it allows you to add custom behavior to the HTML <template> element.

I won’t go into details here, but a customized template (along with your data binding) is all you need for conditional rendering (i.e. much of the missing “change DOM elements“ part) and rendering arrays right from your business logic. It also gives you dialogs and more.

If you take this route of custom-built-ins-but-no-shadow-DOM then you will have an easy time even supporting legacy browsers like Internet Explorer. The reason for this is that without the shadow DOM the rest is reasonably easy to polyfill. And the resulting DOM structure can easily be debugged in IE (which shadow DOM cannot).

Scope

Your business logic should be compartmentalized in all but the most simple apps. Remember: Divide & Conquer! And one business logic module should have a definite DOM binding scope. That means if you nest business logic modules inside of each other, you absolutely do not want parent modules to change anything in the nested module’s DOM – and vice versa.

Luckily the DOM tree yields all the structure you need here. I implemented binding in special <peri-bind> widgets and attach business logic scopes to binding elements through special attributes. Thus the DOM tree automatically yields sensible and self explaining binding scopes. As a bonus the binding syntax isn’t universal but limited to where you use the special binding elements. This makes the whole concept even more self explaining to developers to come.

For business logic modules I recommend using pure EcmaScript classes without providing any direct access to the bound DOM. That way developers are strongly incentivized to follow the division into business logic and widgets.

Thus your business logic code will be much easier to unit test. The reason for this is, that you can mostly disregard complex DOM APIs and just call the methods of the business logic modules and check return values/state changes. Better, simpler and more comprehensive unit tests constitute a huge value down the decades.

This approach also yields another level of code structure improvement: the bound variables of the business logic classes constitute the module’s state. Thus you gain an implicit separation into model (state variables), view (widgets), controller (business logic) with a dead simple API.

Tooling

In olden days you could write an HTML file, have it load some CSS and JavaScript from separate files and run that in your browser without any tooling. Today the common practice is to have some build tools between your code and the browser. The reason for this is that many frameworks do not work with valid code but translate your framework specific code to something valid. Obviously I strongly advise against this practice. Exclusively write valid standard conformant code. Only that has a chance to be intelligible in the long run.

EcmaScript modules (i.e. import, export and friends) are supported by modern browsers and allow you to load your raw modular code into your development browser and have it run as intended. For legacy browsers you can do whatever build steps you like, and for deployment some minification, bundling, gzipping and possibly polyfilling is certainly advisable.

∑mary

So that’s it:

  • reduce the amount of code running in your user’s browsers at all significant cost
  • optimize structure of said code
  • provide simple data binding for your developers
  • push for clear separation of business logic from visualization code (and ideally app state)
  • leverage standard technology, then leverage it some more, and then some
  • wish me luck 🙂

I am thankful to PERI for giving me the opportunity to implement a framework built to last.

Scale Me!

Today’s technology is a lot about scalability. That means you have built something that works for you and a few people and now you want to scale the solution to work for thousands, millions, possibly billions of people/sensors/client-systems. Scaling technology is still tough but essentially understood. But what about scaling the people who make that technology? What about scaling me?

Continue reading “Scale Me!”