辛宝Otto

辛宝Otto 的玄酒清谈

北漂前端程序员儿 / 探索新事物 / Web Worker 主播之一/内向话痨
xiaoyuzhou
email

Speedpass - Qwik 2.0 Preview

Express - Qwik 2.0 Preview

Express - Qwik 2.0 Preview#

Original Information#

Original Article Link:
Heading towards Qwik 2.0: Lighter, Faster, Better
Published on 2024-02-10

Included Information#

Qwik is firmly on the HTML-first route, and the interaction of the page will be thoroughly lazy-loaded: officially referred to as resumability.

Looking back at the resumability of version 1.0 from the perspective of the 2.0 version update, there are areas that need improvement. Qwik needs to add information to the HTML content to achieve the mapping relationship between HTML and JavaScript logic.

Specifically, three things have been done:

  • Listener location. Add specific attributes to the HTML, not much actual content, and little room for improvement.
  • Component boundaries. The core of this update.
  • Application state. Also considering adjustments, to be discussed later.

All changes are backward compatible and will not affect users.

Web applications are independent of specific frameworks and establish relationships between abstract logic and the DOM tree.

Examples#

Source Code#

For example, a Qwik component using JSX syntax, wrapped with the specific function component$, implements a counter.

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

export const Counter = component$(() => {
  const count = useSignal(123);
  return (
    <>
      Count: {count.value}!
      <button onClick$={() => count.value++}>+1</button>
    </>
  );
});

export const Layout = component$(() => {
  return (
    <main>
      <Slot />
    </main>
  );
});

// Call in the page like this: <main> <Counter/> </main>

HTML Output#

You can open the official Qwik Playground at https://qwik.dev/examples/introduction/hello-world/ and replace the above content in app.tsx.

The output HTML will have some comment nodes, containing a lot of information.

image

<div q:container="paused" q:render="static-ssr" q:version="dev"
     q:base="/build/" q:locale q:manifest-hash="dev">
  <main>
    <!--qv q:s q:sref=5 q:key=-->
      <!--qv q:id=7 q:key=xYL1:zl_0-->
        <!--qv q:key=H1_0-->
          Count:
          <!--t=8-->123<!---->!
          <button on:click="..." q:id="9">
            +1
          </button>
        <!--/qv-->
      <!--/qv-->
    <!--/qv-->
  </main>
  <script type="qwik/json">{...}</script>
</div>

Comment Node 1#

The first comment node <!--qv q:s q:sref=5 q:key=--> corresponds to the Layout component in the source code, implementing the slot functionality for routing content.

Comment Node 2#

The second comment node <!--qv q:id=7 q:key=xYL1:zl_0--> represents the above Counter component. If there are props, it will also be associated with the qwik/json content.

The original text is a bit confusing and not easy to understand:

  • The additional attributes are used to cross-reference the props with this virtual node.
  • A key point of resumability is that one should be able to re-render a component without the parent component needing to be resumed as well.
  • But components get props from the parent, so the props need to be serialized and recoverable so that this component can render independently of others.
  • Qwik needs a virtual node for that.

This means that this Qwik component needs to decouple from the parent component to achieve independent re-rendering, but it also depends on the props provided by the parent component.

Comment Node 3#

The third node <!--qv q:key=H1_0--> marks the <></> node in JSX.

Comment Node 4#

The comment node <!--t=8-->123<!----> marks the text node to be updated.

Other Special Attributes#

q:id/ q:sref / t are used to associate with the state.

Although there are many nodes, they are all useful.

What are the issues?#

There is some unnecessary content. In actual runtime, the Counter component will never be downloaded to the browser because there are no additions or deletions of DOM during subsequent interactions. Qwik can update the DOM on its own, so the virtual node of the Counter component is not necessary.

If it is a streaming transmission, other components may affect the Counter component during the download and rendering process, so the comment node can only be left there. The entire app needs to be processed before it is known that this component is static and the virtual node is unnecessary.

After serializing the data, an ID is needed for mapping. However, when streaming HTML, qwik xxx

How will 2.0 improve?#

  • All human-imperceptible data is placed at the end of the HTML, all for faster rendering of the UI.
  • More efficient virtual node encoding scheme.
  • Make the resumability algorithm even more lazy-loaded, focusing only on the virtual nodes of user input.

The article provides the output of the new version, which is very clean and does not contain any unnecessary comment nodes, but is equivalent to the above output.

<div q:container="paused" q:render="static-ssr" q:version="dev"
     q:base="/build/" q:locale q:manifest-hash="dev">
  <main>
   Count: 123!
   <button on:click="...">+1</button>
  </main>
  <script type="qwik/state">[...]</script>
  <script type="qwik/vnode">!{{HDB1}}</script>
</div>

The virtual node information is no longer mixed with the HTML, and now it is all placed in qwik.vnode, all for faster rendering of the UI.

But doesn't that mean losing auxiliary information? How do we know that 123 will be updated? Pay special attention, 123 is surrounded by pure strings!

The conclusion is that the auxiliary node information still exists. Look at the content of qwik/vnode. All the node information is compressed into 9 characters:

!{{{HDB1}}}.

How are these 9 characters generated?#

How are these 9 characters generated? What situations need to be considered? This is a technical issue.

The document says:

We use the document.createTreeWalker API to retrieve all DOM nodes in depth in the first order.

The key points are:

  • document.createTreeWalker is an API used to traverse all DOM nodes. Let's ignore it for now and come back to it later.
  • depth-first is used to identify any node with a serial number, so there is no need to allocate an ID.

So what does this mysterious 9 characters mean? !{{HDB1}} Let's break it down. Please take another look at the rendering result.

// source
<main>
<>
Count: {count.value}!
<button onClick$={() => count.value++}>+1</button>
</>
</main>


// output
<main>
Count: 123!
<button on:click="...">+1</button>
</main>

  • ! The encoding process needs to skip how many elements to reach <main>.
  • { The first curly brace indicates that the <main> element has a virtual element for props.
  • { The second curly brace indicates that the component includes a <></> virtual node.
  • H Represents the letter h at index 7 in the rendered Count: 123 (after the space and before 123). The range covers the content Count: (including the space after the colon) with a total of 7 characters. This position is the first node.
  • D Index 3 represents the cursor position after 123, which is a new text node.
  • B Index 1 corresponds to !.
  • 1 Represents the number of elements to consume, which is 1, which is the <button>.

It's amazing, I kind of understand but not really. It means that uppercase letters are used as markers for positions within a range of up to 26 characters, and they can be infinitely expanded with lowercase letters.

Wow, I don't need to worry about the details for now. Anyway, it means that any node can be marked with a starting position. It really eliminates the need for virtual nodes. This pursuit of perfection is incredible!

Advantages#

Because the encoding of virtual nodes is at the end, when decoding and executing, the details of the component can be safely known.

This will further reduce the amount of hypertext markup language sent. Since the new encoding uses depth-first indexing, no additional unnecessary IDs are left behind.

Additional Optimization#

The article mainly introduces optimization in terms of encoding. There are also optimizations in runtime, mainly for explanation purposes. Using arrays to store data instead of objects, arrays can freely add data, and using a single array to store data reduces memory pressure. Another advantage is that accessing is O(1) and fast.

I didn't understand the other advantages, so I'll leave them for now.

Impression#

Qwik has always been known for its extreme rendering performance. This 2.0 version promises backward compatibility and has little impact on users. Therefore, I am looking forward to the internal details. This article is the first in a series of articles, mainly introducing the details of extreme compression encoding. Many areas require more time to understand and digest, but I am confident in the next version. It is really an interesting framework.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.