However, attaching event handlers to the document object model (DOM) isn’t the hardest or most expensive part of hydration.
In this article, I’ll explain why I believe hydration is an overhead. This is not the solution; it is a hack that consumes memory and slows down startup, especially on mobile. For the purposes of this article, let’s define overhead as work that can be avoided and still leads to the same end result.
Miško Hevery is the Chief Technology Officer at Builder.io. As CTO, Miško oversees the technology division that powers Builder.io apps and software. Prior to joining Builder.io, he built open source platforms for Google, including Angular and AngularJS, and was co-creator of Karma. At Google, he brought a testing culture with his blog. Before focusing on improving the web, he believes testing is the key to success.
The hard part of hydrating is knowing which event handlers we need and where they should be attached.
- WHAT: The event handler is a closure that contains the behavior of the event. This is what should happen if a user triggers this event.
- WHERE: The location of the DOM element where the WHAT should be attached (includes event type)
The added complication is that WHAT IS a closure that closes APP_STATE and FW_STATE:
- APP_STATE: the state of the application. APP_STATE is what most people think of as the state. Without APP_STATEyour app has nothing dynamic to show the user.
- FW_STATE: the internal state of the framework. Without FW_STATE, the framework does not know which DOM nodes to update or when the framework should update them. Examples are the component tree and references to render functions.
So, how to recover WHAT and WHERE? By downloading and executing the components rendered in HTML. This is the most expensive part.
In other words, hydration is a hack to recover the APP_STATE and FW_STATE by eagerly executing application code in the browser and involves:
- Component code download.
- Execution of component code.
- Retrieving the WHAT (APP_STATE and FW_STATE) and WHERE to get the event handler closed.
- Attach WHAT (the closing event handler) to WHERE (a DOM element).
Let’s call the first three stages the recovery phase.
Recovery is when the framework tries to rebuild the application. Rebuilding is expensive because it requires downloading and running application code.
Recovery is directly proportional to the complexity of the page to be hydrated and can easily take 10 seconds on a mobile device. Since recovery is the most expensive part, most apps have suboptimal startup performance, especially on mobile.
Recovery is also an overhead: it reconstructs information that the server has already gathered as part of server-side rendering (SSR) or static site generation (SSG). Instead of sending the information to the client, the information was deleted. As a result, the client must perform an expensive recovery to rebuild what the server already had. If only the server had serialized the information and sent it to the client with HTML, the fetch could have been avoided. Serialized information would save the client from eagerly downloading and executing all the components of the HTML code.
In other words, the re-execution of code on the client that the server has already executed as part of SSR/SSG is what makes hydration pure overhead.
Resume capacity: a no-overhead alternative to hydration
To remove the overload, the framework must not only avoid recovery, but also step four above: attach the WHAT to WHERE.
To avoid this cost, you need three things:
- All required information serialized as part of the HTML code, including WHAT, WHERE, APP_STATEand FW_STATE.
- A global event handler that relies on event bubbling to catch all events so that we don’t have to eagerly register all events individually on specific DOM elements.
- A factory function that can lazily retrieve the event handler (the WHAT).
The above configuration can be resumed because it can pick up execution where the server left off without redoing the work already done by the server. By creating the WHAT lazily in response to a user event, we can avoid doing all the unnecessary work that goes into hydration. All this means no overhead.
DOM elements retain event handlers for the lifetime of the element. Hydration eagerly creates all listeners, so it needs memory to be allocated on startup.
On the other hand, resumable frameworks only create event handlers after the event is fired. Therefore, they will consume less memory than hydration. Also, the event handler is freed after it executes, returning memory.
In a way, releasing memory is the opposite of hydration. It’s as if the framework lazily hydrates a specific WHAT, runs it, and then dehydrates it. There isn’t much difference between the first and nth execution of the handler.
The performance difference
To put this idea into practice, we built Qwik, a framework designed around “resumability” and allowing for a quick start. To show you the impact of resumability, we’ve created a demo of a jobs app that runs on the Cloudflare edge. This page is ready for interaction in approximately 50 ms.
Simply put, hydration is an overload because it duplicates the work. The server constructs the WHERE and WHAT (APP_STATE and FW_STATE), but the information is discarded instead of being serialized for the client. The client then receives HTML code that does not contain enough information to rebuild the application. The lack of information forces the customer to eagerly download the application and run it to retrieve the WHERE and WHAT.
An alternative approach is the possibility of recovery. Failover capability focuses on transferring all information (WHERE and WHAT) from the server to the client. Only a user interaction forces the client to download code to handle that specific interaction. The client does not duplicate any work from the server; therefore, there is no overhead.
Feature image via Pixabay.