The new Qwik JavaScript framework seeks faster web applications with a unique approach: resumability

Misko Hevery, the creator of AngularJS, recently announced the beta availability of Qwik, his new web framework. Qwik claims to create fast apps, regardless of app size. In most cases, Qwik only downloads 1 KB of JavaScript first. Event handlers and application code are lazily loaded and prefetched as needed.

In a conference addressing How to remove 99% of JavaScript from the main threadHevery explained the purpose of Qwik as follows:

Qwik has a very very simple goal, which is that it wants to make sure that no matter how complex your website is, you should be able to score 100/100 on a Google page speed score. […] It all basically boils down to making the interaction time as fast as possible. […]
What you see is that for the most part, as an industry, we do a fabulous job of optimizing our images, a fabulous job of optimizing our CSS, but not so much on JavaScript. Because this is a systemic problem for everyone on the web, I’m going to argue that the problem is the tooling, not the developers.
Tools [that optimizes the delivery of JavaScript for speed] is the type of problem Qwik focuses on.

Misko attributed JavaScript’s negative impact on interactive time measurement to hydration. Hydration occurs in relation to server-side rendering. The client requests a page; the server makes the relevant requests, populates the page, and sends it back to the client. While server-side rendered pages are generally displayed faster to users than client-side rendered pages (e.g. faster First Contentful Paint), the pages are not immediately interactive. The server sent a static version of the page. The client must then download and execute the JavaScript responsible for ensuring the interactivity of the page.

In many frameworks, this process of reconciling the originally shipped HTML with the application’s JavaScript is called hydration. During hydration, the web application framework attaches event handlers to DOM elements and initializes application state. After hydration, the user’s actions will be taken over by the event handlers: the application is now interactive.

Qwik avoids hydration by running the application on the server (server-side rendering as usual); serialize all relevant state elements; and send the client both the page content and the serialized state in HTML format. Relevant state includes event listeners, internal data structures, and application state. With serialized state, the client can simply resume execution where the server left off.

The loading of the JavaScript that handles an interaction is by default deferred, possibly until the interaction is actually initiated by the user. This means that a button’s event handler can be loaded (at the latest) when the user clicks that button. This just-in-time JavaScript fetching is complemented by prefetching strategies that leverage the browser’s native ability to schedule document loading without sacrificing page interactivity.

Qwik documentation details:

  • Qwik only fetches the code needed for the current page. Qwik avoids downloading code associated with static components. In the worst case, Qwik preloads the same amount of code as the best case of existing frameworks. In most cases, Qwik preloads a small fraction of code compared to existing frameworks.
  • Code prefetching can occur on threads other than the main thread. Many browsers can even pre-parse the AST of code outside of the main thread.
  • If the user interaction occurs before the prefetch is complete, the browser will automatically prioritize the interaction block over the remaining prefetch blocks.
  • Qwik may divide the application into several smaller chunks, and these chunks may be downloaded in order of the likelihood that the user will interact with them.

The Qwik website offers tutorials, examples, and a playground for developers to learn and experiment with Qwik. A simple counter application, consisting of a button and a message displaying the number of times the button was clicked, is implemented as follows:

import { component$, useStore } from '@builder.io/qwik';

export const App = component$(() => {
  const store = useStore({ count: 0 });

  return (
    <div>
      <p>Count: {store.count}p>
      <p>
        <button onClick$={() => store.count++}>Clickbutton>
      p>
    div>
  );
});

Developers Create Resume Components with Qwik’s component$ APIs. Stateful components reveal their dependencies to state elements with the useStore APIs. Developers create resumable event handlers by adding the $ character in the name of the handler (for example, onclick$ in the example above). Using these tips provided by the developers, Qwik bundles application files in a way that enables and optimizes JavaScript lazy loading. The server-rendered HTML for the above counter application is as follows:


<html
  q:container="paused"
  q:version="0.11.1"
  q:render="ssr"
  q:base="/repl/21kry8ac4hl/build/"
>
  <html>
    <head q:head>
      <title q:head>Tutorialtitle>
    head>
    <body>
      
      <div>
        <p>
          Count:
          0
        p>
        <p>
          <button
            on:click="app_component_div_p_button_onclick_8dwua0cjar4.js#App_component_div_p_button_onClick_8dWUa0cJAr4[0]"
            q:id="2"
          >
            Click
          button>
        p>
      div>
      
    body>
  html>
  <script type="qwik/json">
    {"ctx":{"#2":{"r":"0!"}},"objs":[{"count":"1"},0],"subs":[["2 #0 0 #1 data count"]]}
  script>
  <script id="qwikloader">
    ((e,t)=>{const n="__q_context__",o=window,r=new Set,i=t=>e.querySelectorAll(t),s=(e,t,n=t.type)=>{i("[on"+e+":"+n+"]").forEach((o=>l(o,e,t,n)))},a=(e,t)=>new CustomEvent(e,{detail:t}),c=(t,n)=>(t=t.closest("[q:container]"),new URL(n,new URL(t.getAttribute("q:base"),e.baseURI))),l=async(t,o,r,i=r.type)=>{const s="on"+o+":"+i;t.hasAttribute("preventdefault:"+i)&&r.preventDefault();const a=t._qc_,l=null==a?void 0:a.li.filter((e=>e[0]===s));if(l&&l.length>0){for(const e of l)await e[1].getFn([t,r],(()=>t.isConnected))(r,t);return}const d=t.getAttribute(s);if(d)for(const o of d.split("n")){const i=c(t,o),s=b(i),a=performance.now(),l=u(await import(i.href.split("#")[0]),s),d=e[n];if(t.isConnected)try{e[n]=[t,r,i],f("qsymbol",{symbol:s,element:t,reqTime:a}),await l(r,t)}finally{e[n]=d}}},f=(t,n)=>{e.dispatchEvent(a(t,n))},u=(e,t)=>{if(t in e)return e[t];for(const n of Object.values(e))if("object"==typeof n&&n&&t in n)return n[t]},b=e=>e.hash.replace(/^#?([^?[|]*).*$/,"$1")||"default",d=e=>e.replace(/([A-Z])/g,(e=>"-"+e.toLowerCase())),p=async e=>{let t=d(e.type),n=e.target;for(s("-document",e,t);n&&n.getAttribute;)await l(n,"",e,t),n=e.bubbles&&!0!==e.cancelBubble?n.parentElement:null},v=e=>{s("-window",e,d(e.type))},w=()=>{var n;const s=e.readyState;if(!t&&("interactive"==s||"complete"==s)&&(t=1,f("qinit"),(null!=(n=o.requestIdleCallback)?n:o.setTimeout).bind(o)((()=>f("qidle"))),r.has("qvisible"))){const e=i("[on:qvisible]"),t=new IntersectionObserver((e=>{for(const n of e)n.isIntersecting&&(t.unobserve(n.target),l(n.target,"",a("qvisible",n)))}));e.forEach((e=>t.observe(e)))}},q=(e,t,n,o=!1)=>e.addEventListener(t,n,{capture:o}),y=t=>{for(const n of t)r.has(n)||(q(e,n,p,!0),q(o,n,v),r.add(n))};if(!e.qR){const t=o.qwikevents;Array.isArray(t)&&y(t),o.qwikevents={push:(...e)=>y(e)},q(e,"readystatechange",w),w()}})(document);
  script>
  <script>
    window.qwikevents.push("click")
  script>
html>

Note how the HTML is enhanced with:

  • q: attributes (for example, q:base, q:id, q:key);
  • HTML comments (for example, ) with framework-specific information;
  • serialized state (cf. );
  • and Qwik scripts (for example, , window.qwikevents.push("click")which take over the application on the client side.

Qwik’s playground allows developers to understand how application code is divided and grouped. In the case of the counter application code, the client bundle is as follows:

The previous screenshot shows that the counter application has been split into three scripts. When the user clicks the button, two scripts are dynamically downloaded and executed (Qwik runtime and code for the click event handler):

To understand what exactly is happening and how code splitting works, developers can refer to the Qwik documentation. The Qwik website provides a lot of information (tutorials, examples, presentations) and an interactive code playground). The community has also provided a non-trivial e-commerce example. E-commerce vendors generally believe that increasing page speed leads to increased revenue.

The Qwik team includes Miško Hevery, creator of AngularJS; Manu Almeida, creator of the Go-based Gin web framework; and Adam Bradley, creator of the Stencil web component compiler; and the Ionic UI toolkit.

Qwik is now available in beta. Qwik is an open source project under the MIT license. Contributions are welcome and must follow Qwik’s Code of Conduct.

Comments are closed.