Pangram verdict · v3.3
We believe that this document is fully human-written
AI likelihood · overall
HumanArticle text · 1,635 words · 6 segments analyzed
It was simple back thenWhat are the roots of this Complexity? How have we arrived here?Once upon a time, at the dawn of the web, browsers and websites were simple. There were no apps really, but mostly static pages - collections of .html files sprinkled with some CSS for better look. These websites were text-based for the most part, linking to other similar documents available on the World Wide Web. Everything was plain and simple; static documents, referring to each other.Then slowly, step by step, more and more interactivity was added; first came forms and inputs, not long afterwards - JavaScript programming language (both in 1995).At this stage, Complexity was still low. Web systems developed then consisted mostly of: .html documents and templates .css file or files some .js scripts HTTP servers to make these static files available and handle state altering requests from forms databases to store system's state Crucially, the UI source code of these first websites and apps was mostly the same as the output files interpreted and executed in the browser - runtime target. Even with the use of PHP and templating languages/systems (like Mustache), it looked very similar to the target HTML files, displayed by the browser:<h1>{{page.title}}</h1> <div> <p>{{name.label}}: {{user.name}}</p> <p>{{email.label}}: {{user.email}}</p> <p>{{language.label}}: {{user.language}}</p> </div> <a href="/sign-out">{{sign-out}}</a> A templating engine - just a library available in the server runtime/environment - turns this into a specific HTML page:<h1>User Account</h1> <div> <p>Name: Igor</p> <p>Email: [email protected]</p> <p>Language: EN</p> </div> <a href="/sign-out">Sign Out</a> A little more complicated than static collections of .html documents, but still fairly straightforward. What has happened next?Then came AJAX - weird acronym for Asynchronous JavaScript and XML. It brought a completely new possibility to update HTML document content in the background, asynchronously - without reloading the whole page.
From this point onwards, more and more of websites functionality started to be delegated to increasingly complex JavaScript - especially for partial updates, triggered mostly by more sophisticated user interactions, to avoid full page reloads. Not long after that, the concept of Single Page Application (SPA) and first frameworks arrived: Backbone.js, Knockout.js and AngularJS (2010). In this model, the source code we work on is very remote from what finally lands in the browser environment. More elaborate abstractions came here as well - complexifying needed tooling as a result.That is how, more or less, we ended up with today's Complexity - where most apps are built with React, Vue, Angular or Svelte, requiring a whole toolchain to build and develop, such as Vite or Webpack. How they work is inherently different from what browsers were designed to do.Source Code vs Browser RuntimeAs the gap between source code format and browser runtime has been growing - because of these newly discovered and adapted abstractions - more tools and of increasing complexity became essential to develop, build and deploy web applications.Let's take a typical modern SPA - written in React, using TypeScript and Vite for development & building. To make it digestible and understandable by the browser: TypeScript must be transpiled/compiled into JavaScript - it is its superset and browsers do not know anything about it In our case, we also need to transform TSX to JSX - TSX is just a typed variety of JSX Take JSX files and turn them into JavaScript - browsers have no clue what to do about .jsx files It is an SPA with only one index.html HTML file and potentially tens, hundreds or even thousands of small .js files - for performance reasons, they should be packaged into a single .js bundle (or a few ones). Since at least React as a dependency has to be available, it must be added to the resulting bundle as well With the latter, there are mostly two additional optimizations: tree shaking and minification.
Tree shaking takes dependencies and leaves in the resulting bundle only actually used source code, not all of it; minification on the other hand, removes unnecessary whitespaces, shortens variable names and so on to make the final .js file as small as possible - while still keeping its functionality intact On top of that, there might be additional steps: Post CSS processing - transforming CSS in various ways: adding vendor prefixes, allowing the use of not yet widely supported CSS features or CSS modules/scopes Polyfills and transpilers - as new proposals, specifications and versions of JavaScript (ECMAScript) are developed, it takes time to have them widely supported. Polyfills and transpilers close this gap - they make it possible to use new and not yet widely supported features of JavaScript by transforming our source code to a version that works in older runtimes (browsers) as well As we can clearly see - that really is a lot! And of course, it would be highly impractical to write scripts performing all of these transformations; that is why we have build tools like Webpack, Turbopack and Vite. They of course introduce yet another dependency; something new to learn and master. But, we have gone so far away from what browsers are actually operating on at runtime, that they rather are necessary. One could make a very good case that it developed in this way purely for historical reasons, because of the browser limitations in the past (there were no native modules for a long time for example).The current ecosystem complexity is rivaling Tower of Babel. I would then ask: Can we start from scratch and figure out a much simpler approach, given how browsers have evolved in recent years? What is essentialFor most web apps, what today's users treat as given: instant load times native-like, smooth transitions between pages high degree of interactivity; most user actions should feel fast real-time validation and hints; especially for complex forms and processes What programmers want: great developer experience - ability to quickly see and validate UI changes possibility of creating, sharing and reusing configurable UI components testability - how do we know whether it works?
easy to introduce translations & internationalization A simpler alternativeHere is an idea: UI mostly server-driven, server-side rendered with the use of HTMX Single Page Application - routing is provided by HTMX, out of the box HTML Web Components - for reusable and framework agnostic components. It is an approach where Web Components mostly provide behavior, not the structure - we will see how it works and what the benefits are below Mustache for server-side rendered HTML templates - implementations are available in pretty much all major programming languages TailwindCSS to make styling easier Simple scripts to bundle it all together and prepare a package for deployment A fully working example is available in this repo. Let's go through the most important and interesting parts.ServerIn the example, I have written a server in Java, using Spring Boot framework; but, it could have been written in any other programming language and/or framework suited for web development. I call it a server, because in this approach, there is no frontend/backend distinction really; there is just an app, with views rendered mostly by the server, sprinkled with client-side JS here and there.From various endpoints, rendered HTML pages or fragments are returned as:@GetMapping("/devices") String devices(Model model, Locale locale, @RequestParam(required = false) String search) { translations.enrich(model, locale, Map.of("devices-page.title", "title"), "devices-page.title", "devices-page.search-input-placeholder", "devices-page.search-indicator", "devices-page.trigger-error-button");
enrichWithDevicesSearchResultsTranslations(model, locale);
var devices = deviceRepository.devices(search);
return templatesResolver.resolve("devices-page", devicesModel(model, devices)); } Which, depending on the context: returns a full HTML page, if the page is loaded by the browser for the first time returns an HTML fragment, if we arrived at the /devices url from some other place in the already loaded app How do we know whether to return a full HTML page or fragment?Thankfully, HTMX adds the hx-request header to each HTTP request it makes.
So, if there is no hx-request header present in the HTTP request, our response is a full HTML page:<!DOCTYPE HTML> <html lang="en">
...
<body>
{{ page-specific-html }}
</body>
</html> And if this is a subsequent request - clicking from one page to the next, without full page reload - the hx-request header is present and we return an HTML fragment:{{ page-specific-html }}:
<div class="space-y-2 flex flex-col"> ... <div class="cursor-pointer rounded border-2 p-0 flex"> <span class="px-4 py-2 flex-1">9b0d5f33-6f9e-4aef-bb81-a57a045fb1aa: iPhone 13</span> <drop-down class="relative"> <div data-drop-down-anchor class="absolute right-2 text-3xl">...</div> <div data-drop-down-options class="rounded border-2 whitespace-nowrap absolute mt-2 right-0 top-6 bg-white border rounded hidden z-99"> <div class="p-2" hx-get="/devices/9b0d5f33-6f9e-4aef-bb81-a57a045fb1aa" hx-push-url="true" hx-target="#app">Details</div> <div class="p-2" hx-get="/buy-device/9b0d5f33-6f9e-4aef-bb81-a57a045fb1aa" hx-push-url="true" hx-target="#app">Buy</div> </div> </drop-down> </div> ... </div> This is how it looks: Devices page A few interesting things to note here: various hx- attributes (HTMX): hx-get, hx-push-url and hx-target custom <drop-down> element (Web Component) lots of Tailwind CSS classes Let's start with hx- mechanics.HTMXWhen we click on the Details or Buy option, the browser url is changed by HTMX using standard History API.
At the same time, HTMX makes GET request to /devices/9b0d5f33-6f9e-4aef-bb81-a57a045fb1aa or /buy-device/9b0d5f33-6f9e-4aef-bb81-a57a045fb1aa accordingly. Content of the HTML element identified by app id is swapped with the HTML fragment, received from the server. As a result, we see a new HTML page without full page reload - in the exact same way as it works in the traditional, client-heavy & JSON-oriented SPAs. Details option
Buy option HTML Web ComponentsThis is a different strategy to develop Web Components where structure is fully or mostly defined in HTML; components just add behavior to it through JavaScript.Using <drop-down> as an example (Mustache template):<drop-down class="relative"> <div data-drop-down-anchor class="absolute right-2 text-3xl">...</div> <div data-drop-down-options class="rounded border-2 whitespace-nowrap absolute mt-2 right-0 top-6 bg-white border rounded hidden z-99"> <div class="p-2" hx-get="/devices/{{id}}" hx-push-url="true" hx-target="#app">{{devices-search-results.details-option}}</div> <div class="p-2" hx-get="/buy-device/{{id}}" hx-push-url="true" hx-target="#app">{{devices-search-results.buy-option}}</div> </div> </drop-down> As we see, there are anchor and options elements marked as data-drop-down-anchor and data-drop-down-options respectively. What then the <drop-down> is doing:class DropDown extends HTMLElement {
#hideOnOutsideClick = undefined;
connectedCallback() { const anchor = this.querySelector("[data-drop-down-anchor]"); const options = this.querySelector("[data-drop-down-options]");
anchor.onclick = () => options.classList.toggle("hidden");
this.#hideOnOutsideClick = (e) => { if (e.target !