Progressively

Progressively is about downloading code as the application needs, without having to download the entire codebase eagerly.

This connect with Qwik's core tenant which focus on delaying the loading and execution of JavaScript for as long as possible. Qwik needs to break up the application into many lazy loadable chunks to achieve that.

Current state-of-the-art

Existing frameworks suffer of two problems:

  • Lazy loading boundaries are 100% delegated to the developer
  • Frameworks can only lazy load components that are not in the current render tree.

The issue is that the frameworks need to reconcile their internal state with the DOM. And that means that at least once on application hydration, they need to be able to do a full render to rebuild the framework's internal state. After the first render, the frameworks can be more surgical about their updates, but the damage has been done, the code has been downloaded. So we have two issues:

  • Frameworks need to download and execute components to rebuild the render tree on startup. (See hydration is pure overhead) This forces eager download and execution of all components in the render tree.
  • The event handlers come with the components even though they are not needed at the time of rendering. The inclusion of event handlers forces the download of unnecessary code.

Solution

Qwik architecture takes full advantage of modern tooling to automate the problem of entry point generation. Developers can write components normally, while the Qwik optimizer will split the components into chunks and download them as needed.

In addition, the framework runtime does not need to download code that is not required for interactivity, even if the component is part of the render tree.

Optimizer

Optimizer (described in-depth here) is a code transformation that extracts functions into top-level importable symbols, which allows the Qwik runtime to lazy-load the JavaScript on an as-needed basis.

The Optimizer and Qwik runtime work together to achieve the desired result of fine-grained lazy loading.

Without the Optimizer, either:

  • The code would have to be broken up by the developer into importable parts. This would be unnatural to write an application, making for a bad DX.
  • The application would have to load a lot of unnecessary code as there would be no lazy-loaded boundaries.

Qwik runtime must understand the Optimizer output. The biggest difference to comprehend is that by breaking up the component into lazy-loadable chunks, the lazy-loading requirement introduces asynchronous code into the framework. The framework has to be written differently to take asynchronicity into account. Existing frameworks assume that all code is available synchronously. This assumption prevents an easy insertion of lazy-loading into existing frameworks. (For example, when a new component is created, the framework assumes that it's initialization code can be invoked in a synchronous fashion. If this is the first time component is referenced, then it's code needs to be lazy-loaded, and therefore the framework must take that into account.)

Lazy-loading

Lazy-loading is asynchronous. Qwik is an asynchronous framework. Qwik understands that at any time, it may not have a reference to a callback, and therefore, it may need to lazy-load it. (In contrast, most existing frameworks assume that all of the code is available synchronously, making lazy-loading non-trivial.)

In Qwik everything is lazy-loadable:

  • Component on-render (initialization block and render block)
  • Component on-watch (side-effects, only downloaded if inputs change)
  • Listeners (only downloaded on interaction)
  • Styles (Only downloaded if the server did not already provide them)

Lazy-loading is a core property of the framework and not an afterthought.

Optimizer and $

Let's look at our example again:

// the `$` suffix for `component` indicates that the component should be
// lazy-loaded.
const Counter = component$(() => {
  const store = useStore({ count: 0 });

  // the `$` suffix for `onClick` indicates that the implementation for
  // the handler should be lazy-loaded.
  return <button onClick$={() => store.count++}>{store.count}</button>;
});

Notice the presence of $ in the code. $ is a marker that tells the Optimizer that the function following it should be lazy-loaded. (For a detailed discussion see $ and Optimizer Rules.) The $ is a single character that hints to the Optimizer and the developer to let them know that asynchronous lazy-loading occurs here.

Made with โค๏ธ by