Qwik is a daring rethink of how reactive UIs work. The core premise is that the framework is built from the ground up to deliver HTML with minimal JavaScript—just enough JavaScript to incrementally introduce interactivity as needed.
Qwik uses a fine-grained model for isolating the segments of the app that are hydrated on an as-needed basis. By starting from first principles, Qwik allows for otherwise unattainable performance and represents an alternative path for the evolution of front-end JavaScript.
State of Qwik
Qwik is still in early releases, but it has come a long way since we first got a look at it. There is now a full-featured example at StackBlitz, a REPL playground, and a command line tool. Qwik has also evolved to support a more developer-friendly, React-like syntax. Under the hood is still an advanced, one-of-a-kind reactive engine that defines reactive boundaries along state, templates, and listeners.
Resumability
Qwik uses a combination of clever server-side and client-side rendering to avoid the kind of double taxation that contemporary frameworks suffer in performing the hydration work twice, once on the server, and again on the client.
As Qwik creator Misko Hevery writes:
The basic idea behind Qwik is that it is resumable. It can continue where the server left off. There is but the tiniest amount of code to execute on the client.
Or, put another way: Let the server set up an HTML page as fully functional as possible, and allow the client to perform the smallest amount of work possible to continue, or resume, the process for the user.
The typical flow in reactive frameworks with SSR (server-side rendering) is to first generate a version of the app on the server, then ship it over to the client, which renders the scaffolded app. At that point, the client-side app takes over, basically having to bootstrap the same app again in order to wire together a functioning client.
This process is known as hydration. There are several clever ways to try to make hydration more efficient, but Qwik abandons those for a new process called resumability.
Resumability means the client can pick up where the server left off, without having to rebuild the app on the client.
Time to interactive
The metric that Qwik strives to enhance is time to interactive (TTI). This refers to the amount of time that elapses between when the user makes a request to a web page and when the page becomes responsive to the user’s interaction.
Whereas time to load (TTL) tracks how long it takes for the client to finish receiving all required data (and therefore is a metric determined largely by file sizes and network speed), TTI takes into account a prominent fact of modern JS frameworks: Once the data is downloaded, the client must then unpack and execute the JavaScript code required to make the page interactive.
There is a lot of work that goes into a reactive engine. The engine must unravel/parse all that markup (like JSX) shot through with variables and expressions that modify what is displayed based on changing state, and how it behaves based on the code.
At the other end of the spectrum is a straight HTML page. Once the browser has a hold of it, the page is ready to rock. This is why Google’s PageSpeed Insights gives a page like Reddit.com a 32 out of 100 score, while raw HTML scores 100.
Closures and listeners
The technical hurdle to fast TTI is described by Hevery as “death by closure.” In short, the fact that every closure must maintain the enclosing universe of information means the runtime app is a layer cake of eagerly loaded code.
The solution employed by Qwik is to use a global event listener that interacts with serialized listeners. In other words, a universal event listener is used to orchestrate listeners that are realized on demand, instead of listeners being downloaded and wrapped in closures (regardless of whether they will ever actually execute).
Qwik aims to deliver reactivity in-line with the HTML, making it all serializable. Only a small executable is required to then manifest the reactivity at runtime, based on the information encapsulated in the markup.
Code splitting, finely tuned
Another way to look at this is that Qwik is performing fine-tuned code splitting. It loads the interactive code as required, when the user demands it. Bundlers are then able to package these chunks into larger bits if it makes sense.
Qwik is built from the ground up with three separate functions for creating state, template, and listeners. This allows the framework to load only what is required for the task at hand. You can learn more about this chunking aspect here.
The three boundaries of state, template, and listener were at one time directly coded by developers. Thanks to a new Optimizer tool that converts React-like syntax into these boundaries behind the scenes, you get a fairly familiar DX. And the Optimizer does the work of turning the actual code into a set of tiny stubs that can resume the app in small chunks as necessary.
With the optimizer, templates and listeners are denoted with a dollar sign, while state is handled by the useStore
hook. You’ll see this syntax in a moment.
The final output of Qwik code looks unlike that of other frameworks, but using Qwik with the Optimizer brings it into parity with other frameworks. Qwik also has introduced QwikCity, a set of higher-order features like routing that make it easier to build full-scale apps.
Hands-on with Qwik
Now that you have an understanding of the concepts behind Qwik, let’s get a feel for coding with it. Listing 1 shows a simple component written in Qwik. (This example is from the Qwik FAQ.)
Listing 1. Simple Qwik component
import { component$ } from '@builder.io/qwik';
export const App = component$(() => {
console.log('render');
return <p onClick$={() => console.log('hello')}>Hello Qwik</p>;
});
Listing 1 shows that a component in Qwik is defined as an anonymous function, passed into the component$
function from the Qwik library. Any time you see a dollar sign $
in Qwik, it’s letting the Optimizer know it needs to do some work. The dollar signed portions of the app are where Qwik will instrument its fine-grained lazy loading boundaries.
The onClick$
in Listing 1 is another example of Qwik’s special syntax. Qwik will use some trickery to load only the JavaScript necessary to support the functionality when it is actually required.
The code in Listing 1 will be broken up by the Optimizer into several segments, as shown in Listing 2.
Listing 2. Qwik component after compilation
// The app.js file itself
import { componentQrl, qrl } from "@builder.io/qwik";
const App = /*#__PURE__*/
componentQrl(qrl(()=>import('./app_component_akbu84a8zes.js'), "App_component_AkbU84a8zes"));
export { App };
// app_component_akbu84a8zes.js
import { jsx as _jsx } from "@builder.io/qwik/jsx-runtime";
import { qrl } from "@builder.io/qwik";
export const App_component_AkbU84a8zes = ()=>{
console.log('render');
return /*#__PURE__*/ _jsx("p", {
onClick$: qrl(()=>import("./app_component_p_onclick_01pegc10cpw"), "App_component_p_onClick_01pEgC10cpw"),
children: "Hello Qwik"
});
};
// app_component_p_onclick_01pegc10cpw.js
export const App_component_p_onClick_01pEgC10cpw = ()=>console.log('hello');
You can see in Listing 2 that instead of including the actual component functionality, Qwik includes a reference, using the componentQrl()
function from the library. This function takes a qrl()
function that uses an anonymous function to import the generated component file. This association between components is all managed under the covers by the Optimizer. The developer doesn’t need to think about it directly.
QRL stands for Qwik URL, which is the way Qwik references something that will be lazy loaded. Basically, any time the framework needs to defer loading something, it will insert a QRL, wrapped by a QRL-specific consumer (like a component, state, or template function).
For example, the componentQRL
can load at the right moment in the code found in the child component while the parent can quickly display its layout. Similarly with the onClick
handler: It is able to be evaluated when the click occurs.
Qwik CLI
The command line tool is available from npm and has the basic features you would expect including creation, dev mode, and production build. The Qwik CLI uses Vite as the build tool. You can start a new app with npm create qwik@latest
, which will launch an interactive prompt.
If you create a simple app and run the production build, you’ll get a dist
directory where you can see all of the separate lazy-loadable chunks of the app we described earlier.
A Qwik adjustment
An interesting place to get a sense of Qwik syntax is the Qwik Cheat Sheet, which offers side-by-side comparisons of Qwik and React code. You’ll see that overall it’s not so hard a transition. Some areas are quite similar, and some require mostly a shift in thinking. The bigger takeaway is that reactive system in Qwik is radically different from React-like frameworks, despite the similarity of syntax achieved with the Optimizer.
Qwik’s innovative approach to code splitting and lazy loading offers a new way forward for front-end JavaScript. It will be interesting to see where things go from here.