Resumability, compilers and event delegation
- Published at
- Updated at
- Reading time
- 3min
Lately, there's been a lot of ground-breaking noise in the JavaScript ecosystem. Don't get me wrong, the "let's invent the next big thing" mantra in JS land isn't new, but for a year or so, it feels like fundamental interactivity patterns are shaken up.
React finally shipped a working implementation of Server Components in Next.js. I haven't played with them yet, but it seems they involve heavy bundler magic to make them feel like good old PHP.
All the new stuff is about bundling code differently to restructure function exports and using newly invented data formats just to get rid of all the complexity invented to render some HTML and attach event handlers.
It's a lot!
And while we're seeing the React ecosystem reinventing itself yet another time, underdogs like the Qwik framework are also pushing for new ways to build UIs.
Qwik is all about resumability. And it can't just be taking a deep breath thinking — "what's that now?".
I think it finally clicked for me because Miško Hevery does a fantastic job explaining the concept. Here's a quick summary of the new fancy term.
The standard way to make your pre-rendered HTML react to user actions is to rerun all the code in the client. Ergo, the browser starts executing <App />
code, traverses down to do <Something />
and then <SomethingElse />
just to figure out which event handlers should be slapped onto which elements.
Qwik, on the other hand, bets on event delegation to avoid running all the code used to generate the HTML. The idea is that an event triggered somewhere in the DOM bubbles up to the root to capture it in a single place. Then, you only need to figure out what code needs to run and what state the application need to update.
But how would you figure that out with framework code like this?
export const MyApp = component$(() => {
console.log('Render: MyApp');
return <Counter/>;
});
export const Counter = component$(() {
console.log('Render: Counter');
const count = useSignal(123);
return (
<button onClick$={() => count.value++}>
{count.value}
</button>
);
})
The answer: tooling magic. Qwik compiles everything to the following output:
<button on:click="./someBundle.js#onClick[0]">
<!-- id="b1" -->
123
</button>
<script type="state/json">
[
{signalValue: 123, subscriptions: ['b1']}
]
</script>
Compiling the framework code to something else gives you all the necessary information: event handlers define what code should be run in on:click
, while HTML comments specify which elements (id="b1"
) are bound to a pre-defined JSON state. A single event capture function can then react to user interactions without rerunning all the component code. Magic made possible by code wrangling.
Miško's post is an excellent explainer if you want to learn more.
And while this is cool and all, I'm still skeptical about all this added complexity to attach event listeners and update some state.
Suppose you're building Gmail, sure! There'll be a lot of interactivity to manage. But I doubt that many web developers build apps on that scale and are still building good old websites that rely on CRUD operations.
As Dave puts it:
The more you leverage a compilation process, the more you start writing code for the compiler that has no chance of portability and that I feel has problems down the road.
All these tooling steps aren't about minifying and optimizing code anymore. It's all about writing code for a particular framework compiler.
And it doesn't matter if we're talking about Qwik, React, Svelte, or whatever's hot tomorrow. I keep circling around the question: is all this effort worth it to change the DOM after a button click?
I don't have the answer, but I guess we'll find out...
Join 5.5k readers and learn something new every week with Web Weekly.