Web development turning functional

July 28, 2021

web development change

Web development is based on three languages: HTML, CSS, and JavaScript. It is safe to say that the former two are domain-specific, declarative languages that describe how to present the content to the user. They are limited in expression because they are responsible for the static part of a web page. On the other hand, JavaScript is a high-level multi-paradigm language with full expressive power and thus may change web services into a fully interactive experience.

At the very beginning, JavaScript was a relatively simple language to handle essential user interaction and move DOM elements around.
Later, Ajax appeared, and we were no longer limited to the content delivered by the initial request. Instead, we could fetch additional data on demand by consuming various endpoints. That opened the way to create vast interfaces with not only a dynamic structure but dynamic data too.
The last step was a considerable execution optimization, thanks to the introduction of just-in-time compilation. From then on, JavaScript became fast and capable of doing great things, and the only limitation seems to be the code scalability.

The history of DOM manipulation

Let’s start at a time when we do not know comprehensive frameworks or visual libraries, and all web applications rely on pure JavaScript implementation, sometimes supported by utility libraries. The life of a front-end developer is peaceful, and the biggest obstacle is working with an old version of Internet Explorer.

We have static HTML and CSS alongside the JavaScript code which realizes business logic. The only way to connect this logic with static markup is to select a given element, operate over it or assign an event listener. When handling the element or an event, we may use a state and apply changes to various DOM elements. The idea seems pretty straightforward, and creating new features does not require any specific design. We can structure our code however we want.
That is the philosophy behind a scripting language in web development. Nevertheless, a problem reveals itself when we scale the application up and do not control the clarity of the logic. This direction seems inevitable because systems tend to become more complex as time goes by.

The problem I am referring to may go like this. We handle some user interaction that changes two related places, and those may be a state or DOM elements. Then, another interaction also changes two associated places, where one is related to the first interaction and the second one is not. In this case, the second interaction may break the integrity of the states that the first one established; it is also true the other way around. That kind of situation may result in an illogical state or an incoherent visual representation. The main reason for this is that logics of those two modification do not know about each other regardless of interacting with the shared state. It is often not easy to spot that kind of relation, which is the first step toward more significant trouble.
Going further and adding an extra layer of dependency, we may observe one state change and act upon it changing another state. That will make the relationship between them even harder to find.
Furthermore, we may add asynchronous logic by consuming endpoints, which changes the state in a nondeterministic manner. All in all, our application becomes challenging to debug very quickly, and when you scale this design up, you end up with a nightmare.

HTML vs. JS

It became evident that implementing more extensive applications in an imperative way is cumbersome and requires a lot of care from the programmers. All things considered, it turns out to be a very pricy approach requiring the maintenance of an extensive codebase. It seems like we have to come up with an alternative.
What may be a valuable substitute to the static markup alongside a scripting language selecting elements and explicitly changing them? Well, the different option to the imperative approach is, as you may guess, the declarative one.

The core of the declarative paradigm, in the web development world, is to define how a given state should render its visual representation using presentational entities — components.
There are two major approaches to how we can implement our components. The first is to use empowered HTML templates, and the second one is to use JavaScript. I hope we can all agree that there will be nothing like JS in CSS in the foreseeable future, thus we’ve ended up with only two ways.

The HTML approach requires us to design a specific framework because HTML is static in its nature and thus incapable of generating dynamic content on its own. The solution here is to introduce a templating system that enriches our HTML with the ability to render variables, use conditionals and loops. In addition, we need some kind of a controller that collects data and hydrates the template, hence the JavaScript that we can use alongside our extended HTML. The advantage of this approach is working with already popular abstractions and only building upon them.

Another solution is to rely on JavaScript entirely. The idea is that we have a fully expressive language capable of replacing HTML and CSS using its power of functions and data structures. It is possible to represent every element and every attribute in JS syntax. Thus, we can forget about static domain-specific languages and design a functional abstraction using JavaScript. The advantage here is that we do not need to create a framework because nothing has to happen outside the JS execution. Of course, we may consider a compilation step to handle features like JSX; still, a fully capable high-level programming language is enough.

Declarative front-end abstraction

Long story short, there are various solutions to implementing functional paradigm and introducing proper abstraction to create scalable web applications. Eventually, we end up with components that accept some attributes and render the presentation layer.
Because this is an abstraction, a lot is going on under the hood. For instance, we may need virtual DOM or update functions that await all renders that our components produce and then apply required changes to the actual elements. This implementation is undoubtedly an imperative logic, but the programmer does not see this. In this case, all one cares about is the high-level functional concept.

Let’s get back to our problem with explicit state modification by user interaction logic. Well, in a declarative way, we are truly protected from the whole category of errors.
First of all, the state may be in one place, representing the single source of truth, and the visual depiction is just a pure representation of that state. The top-down data flow provides deterministic outcomes, so the same state always renders the same result.
Second, the interactions happening in pure components do not change the state directly but rather trigger the callback passed as an attribute. Thus, the update logic resides only in one place, preferably next to the state itself.
Because of those two design decisions, we are not afraid of complex updates and render logic. Debugging a declaratively designed application is pretty straightforward. There is one place to monitor essential changes and an easy way to validate render relations.

Conclusion

In recent years, web development went a long way to simplify the construction of complex web services. All of this was possible because we were able to introduce valuable abstractions. It seems that the evolution of front-end frameworks and visual libraries based on a declarative paradigm brought great benefits and allowed to solve problems that programmers experienced before it.

Various solutions are evolving today, trying to incorporate useful features into the declarative design. It is difficult to predict the future changes in web development, but I think that higher-level abstractions and the functional approach will lead the way.