How does the Qwik.js framework pursue extreme performance

1. Prerequisite knowledge: ssr (you can understand Qwik only if you understand here)

From the beginning of learning front-end development, we continue to learn various front-end optimization methods to improve the performance of front-end code, among which “server-side rendering (ssr)” mode helps us greatly improve the first screen of projects developed using front-end frameworks performance, so what is the workflow of ssr? Let’s briefly sort it out.

 The first step: server-side splicing html

When a user requests a page, the server side will stitch together the html structure of a page and return it to the client, such as the following structure:

 <!DOCTYPE html>
<head>
    <title>Document</title>
</head>
<body>
    <div id="App">
        <button>点击弹出: hello</button>
        <ul>
         <li>1</li>
         <li>2</li>
        </ul>
    </div>
   <script src="/_ssr/2046328.js" defer></script>
</body>
</html>
 Step 2: The html loaded by the client is displayed

As can be seen from the above code, html structure can be displayed after loading, but for example, click on leave, such interaction events still do not exist, you need to load /_ssr/2046328.js before the page can interact (Live), so we still have to request a bunch of js files to the local.

 Step 3: js can interact only after the hydration stage is completed

2. What can be optimized in the ssr process

How do you feel after reading the above ssr process? Do you feel that ssr may be a “visual liar”, we simply list a few points that can be optimized:

  1. Although the display speed of the first screen is faster, it is not interactive, so his tti (page interactive time) is not much optimized, but it is undeniable that there is an improvement, but not too much.
  2. The downloaded js is still a relatively full amount of js code.
  3. When the js code is executed, a lot of logic still needs to be processed, and the DOM on the page must be reprocessed.

In 2020, the project I was in charge of was built using ssr technology. The speed of the first screen has indeed improved, but the disadvantage is that it consumes more server resources, and maintenance costs go up, such as occasional memory leaks, and Every time I update the code, I need to manually execute some commands on the server (the team pipeline was not perfect at that time), which gave me a direct feeling at the time that the battle was quite big and the benefits were a little small.

3. What is Qwik

Qwik can be understood as a front-end ssr frame that has a syntax close to react , but is more radical than the traditional ssr

  1. Greatly optimized or even canceled the hydration process
  2. Not only lazy loading components, but also code such as click events can be lazy loaded
  3. Almost can be done, only load the currently used js code and css code
  4. If the dom element does not appear in the visual area of the screen, the internal method of the component is not executed

Qwik The goal is to delay loading all the code, such as a button before you click it, then Qwik will not load the click-related logic, and even he will not load react Relevant code, after all, sometimes the user does not perform any operation after entering the page, so we do not need to load all the resources.

Of course, after reading the above description, you will feel that using Qwik will make the operation lag, take the ticket with questions and let us study it in depth.

Fourth, the initialization project

Briefly explain the installation and basic usage, so that everyone will have a clearer concept in their minds, but I feel that the official website is well written, so please go to the qwik official website for detailed usage.

 Initialize the project

The following command is the command to create the project:

 npm init qwik@latest

The first time I used this command, I was stunned, because I only used npm init to initialize an empty project, at best I used npm init -y this way, but I checked the official website and found out that the original You can also use it like this:

 // 原命令
npm init qwik@latest

// 相当于
npx create-qwik

// 要注意, 不是
npx qwik@latest/create

So it can be seen that we can directly npm install create-qwik -g and then create-qwik to initialize the project in the same way:

The specific capabilities of the options are not the focus of this article. This time, we will first experience the overall experience Qwik we will have the opportunity to deduct the details later.

 start up

Startup during development

 npm install

npm start

Startup after packaging

 npm run build

npm run serve
 click event

Since the whole is almost the same as react , let’s get straight to the point and first look at how to define a component and define its click event:

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

export const Home = component$(() => {
  const state = useStore({
    count: 0,
  });

  return (
    <button onClick$={() => (state.count += 1)}>home组件: {state.count}</button>
  );
});

We can find that the component is generated by a component$ function. With this function, the component can be an asynchronous component, that is, when the component is not used on the user’s screen, the relevant code of this component will not be loaded.

onClick$ This name also has a $ , the meaning is similar, that is, when we do not trigger the click event, we will not download the code of the click event, which is very detailed.

Five, hooks

 useStore defines variables

The way to define variables is different from react :

 const state = useStore({
    count: 0,
    name: '金毛cc'
  });

Modify the value directly on the body, state

 state.name = '被修改啦'

Suddenly there is a feeling of writing vue .

 useServerMount$

Register a server mount hook that only runs in the server when the component is first mounted.

This hook only runs on the server side and is written as follows:

 useServerMount$(async () => {
    console.log("什么时候执行: useServerMount$");
    const n: number = await new Promise((resolve) => {
      setTimeout(() => {
        resolve(9);
      }, 3000);
    });
    state.count = n;
  });

The printed text cannot be seen in the browser, and can be viewed in the vscode console during development:

 useClientEffect$ to monitor the visibility of elements

Only when rendering on the client side, of course there are corresponding lifecycle methods:

 useClientEffect$(() => {
    console.log("初始化: useClientEffect$");
  });

This hook can monitor whether the component is displayed on the screen, that is to say, it is only executed when the component can be seen by the user, so let’s experiment. We push the home component out of the screen and observe whether useClientEffect$ is executed:

 <Host>
      <h1 style={{ marginBottom:'1200px'}}>Welcome to QwikCity</h1>
      <Home></Home>
    </Host>

But when we scroll we show the home component:

In fact, he uses IntersectionObserver this method monitors the state of dom , so if some of our components need to display the requested data, then we can display the requested data when this component appears on the screen Please request again later.

The reason why he can provide such a method is because of the characteristics of the Qwik framework, and we will understand it when we talk about the Host component later.

 Changes in useWatch$ subscription value (with big pits)

Let’s write that whenever count changes, it will trigger this watch :

 useWatch$((track) => {
    const count = track(store, 'count');
    store.doubleCount = 2 * count;
  });

There is a big hole here, that is, when your component code has useServerMount method and it is below useWatch , useWatch can only be executed once, that is, only once It is executed once at the server end, and will not be executed subsequently.

Here is the wrong usage:

 // 错误示范
// 书写在上方
  useWatch$((track) => {
    const count = track(state, "count");
    state.doubleCount = count + 2;
  });
// 书写在下方
  useServerMount$(async () => {
    const n: number = await new Promise((resolve) => {
      setTimeout(() => {
        resolve(9);
      }, 500);
    });
    state.count = n;
  });

So when we need to continuously monitor the change of a certain value, we need to put useWatch useServerMount$ :

 // 正确写法
// 书写在上方
   useServerMount$(async () => {
    const n: number = await new Promise((resolve) => {
      setTimeout(() => {
        resolve(9);
      }, 500);
    });
    state.count = n;
  });
// 书写在下方
 useWatch$((track) => {
    const count = track(state, "count");
    state.doubleCount = count + 2;
  });

6. There is a big pit in the click event

After reading the official website of qwik from the beginning, I found that all the examples he gave were inline functions, as in the picture:

 <button onClick$={()=>state.count += 1}>
      home组件: {state.count}
  </button>

But in fact, we usually use the following form:

 const handleClick = ()=>{
    state.count += 1
 }
 return <button onClick$={handleClick}>
      home组件: {state.count}
  </button>

Good guy, I just called good guy, is this not letting me reuse the method? There is no way I didn’t try it successfully, and finally I had to change to the following form:

The meaning here is that this function is not serializable, so it cannot be used, and I thought that as long as the serializable method can be placed here? There is the following third way of writing:

It won’t work if you put it in the scope of the component, then I put it outside the component, but the following error is still reported:

But unless we export the method, there will be no error:

So at least the methods imported from the outside world can still be used. The methods in the current component scope can only be written in the inline method of dom , and the key points are these bug in his There is no detailed description in the official website documentation, and it is all up to the developers to explore by themselves, which makes my experience very poor.

Seven, code template to help

Qwik component code itself is a bit special, so he also provides several code templates to help users generate code, in the qwik.code-snippets file of vscode:

use:

8. Is the reform of usage really good?

From the various usages of the Qwik framework, it can be seen that their team’s ambitions, and the official website also mentioned why they did not use the grammar of react , and the reason they gave react The current architecture cannot achieve the desired effect Qwik , so it can only be achieved by overturning and refactoring Qwik .

But all the difficulties they mentioned are problems encountered in the realization of the ‘process’, and the final usage should belong to the ‘result’, in the case of not being 10 times better, why should developers learn the writing method of heart? And These spellings are still full of bugs.

Of course, all innovations are worthy of encouragement. Even a little change may change this monotonous world, but if you are uncomfortable with it, you can say it generously.

9. What is $cached?

We briefly introduced the usage above, then we will mainly talk about the principle, taking the dimension of click event as an example, when we define a click event on button , then the compiled The structure is like this:

It can be seen that on:click event corresponds to a string of 字符串 , why is the click event not a function?

In fact, this is because of the click event mechanism of Qwik 8c4cb4b996367d9696b6cedb91ccf8f0—, first Qwik will monitor the click event globally, and then when a dom is clicked Qwik will detect the body of the dom Whether there is an onclick event and read the corresponding string, then load the corresponding file according to the address of the string, and execute the corresponding method after loading the file.

So how did we write click事件 converted into a string? Here is a concept called ”:

 // 转换前
 <button onClick$={() => {
     state.count += 1;
   }}

// 转换后
 <button onClick$={qrl('./chunk-c.js', 'Home_onClick', [store, props])}

So it can be understood as qrl method is specially responsible for converting some logic to the corresponding js文件 method, so here we understand why the component onClick are so many restrictions on how events are written, because these logics, such as compliance, can be abstracted into 独立的js文件 . If they cannot be abstracted, asynchronous loading cannot be achieved.

10. The cache is partitioned: the Host tag

Host The label is the outermost layer of almost every component, that is, as shown in the figure below, any component should be wrapped Host :

 return (
    <Host>
      ....//
    </Host>
  );

The reason for an extra layer of wrapping Host Let’s take a look at the explanation given by the official website:

Host elements are used to mark component boundaries. Without a host element, Qwik would not know where the component starts and ends. This information is required so that components can render independently and out of order, which is a key feature of Qwik.

I have written several articles about react-keep-alive react dom and I have some understanding of this aspect. Inserting sub-components is not responsive, and the specifics are not clear in one or two sentences. You can read my previous article: Some knowledge about the keep-alive function of react is here

Since the official says ‘must have a Host’ tag, it means that each component of our rendering will have an extra layer dom , since it cannot be avoided, use it as much as possible, first we can specify Host what is the tag dom attributes:

Note that tagName is the second parameter of the component$ method:

So now why do you Qwik inside, you can use useClientEffect$ method to monitor whether the component is in the visible area, because the outer layer of the component basically has a Host -Element, so even the following writing can detect the explicitness and concealment Host the element:

 <Host>
  <>
    <div>1</div>
    <div>2</div>
    <div>3</div>
  </>
    <div>4</div>
</Host>

11. How does Qwik handle latency? prefetch

I have always had a question when I first looked at this framework, the delayed loading of click events may cause 卡顿 , at least it also increases the processing time of click events, in case the code of this click event is a bit large And the user’s network is not good, isn’t it a cool song?

Qwik The team has of course thought about these issues, at least I was convinced by the reasons they gave, the English version of the official website is a bit difficult to understand, so I will explain it in my language:

ssr框架是需要js文件的, js文件加载完毕注水完毕才是页面可交互, 串行的, Qwik , click 字符串 -Then its rendering speed is of course fast, and at the same time 字符串 Qwik will be turned on webWorker The prefetching of the code happens on other threads than the main thread, and not all at once, but The code of several components currently in use, so that as long as the click event is monitored to request a certain file in the future, then webWorker will pass the corresponding file directly without requesting the network.

And Qwik also shows that: Loading js文件 may take more time than 执行js逻辑 .

And it is not one js文件 there is only one method, but multiple related methods will be used. For example, if a click event is triggered, other methods will also be loaded, so there is no need to load them one by one.

12. Recoverability

可恢复性 is a signature concept launched by Qwik , we will discuss this feature from three aspects:

  1. Reduce 注水 : As I said before, set the click listener event globally, so that you don’t have to load the component every time 注水 to interact.
  2. Component tree: In the traditional ssr mode, after the server rendering is completed, there may be some dom the structure has changed, and you need to re Qwik 注水 Qwik It is possible to rebuild the component hierarchy when the component code does not actually exist, and the component code can remain inert. My understanding here is Host The credit of the component, such as a certain The position of the dom has been adjusted, so just adjust Host .
  3. Qwik allows to restore any component without parent component code, what I understand is the current ssr the framework needs the parent component to create the child component, but Qwik lot of states are built in, so you can do independent delayed rendering. For example, A is a parent component and a is a child component, then when I load the a component, I don’t need to load all the A component js Logic.

Thirteen, delay loading demonstration, buried points and other events

Next, let’s see when it will load react代码 , because the source code of react is still a bit slow to run, the following figure shows a page request without any interaction Record:

It can be seen that the loaded file is very small. At the first sight I thought it did not depend on react , when we clicked the button:

Download after the click event is triggered core.js

It should be noted here that theoretically everything that needs to use the hooks of react will trigger the loading of this file. For example, if there is useClientEffect$ in the code, then it will be downloaded when it is executed core.js .

But executing useServerMount$ this kind of server execution hooks will not trigger loading core.js , so everyone knows how to write more efficiently!!

14. My afterthought

There are still a lot of problems in use. Although the official website has written a lot of content, there are still too few specific examples, and the examples are not in place. They are just perfect examples under ordinary conditions. .

And it may be because the official website has not been updated very much. Some methods I pasted can’t be used, and I still need to study for a long time…

I am not in favor of introducing a set of syntax that is quite different from react to developers, and the benefits of changing the syntax are not great.

15. My thoughts

This framework has also brought me a lot of thinking. It can take so many subtle points to the extreme and optimize each line of logic into your logic. Then I also have some of my suggestions:

  1. Can developers specify which events need to be asynchronous at will, such as launching onClick and onClick$ two methods to distinguish whether the code needs to be loaded asynchronously.
  2. Whether it is possible to make a click event trigger record for each user, for example, a certain user often triggers certain events, then theoretically, the codes of these events can be preloaded first, and statistics can be performed by burying the click events.
  3. Put the frequently loaded js file on the server with better performance or closer CDN , that is, differential deployment.

From the framework itself, inspiration for actual business:

  1. Can we focus on each click event by burying it, such as recording the top ten click events every week, and then start focusing on optimization along these events.
  2. 注水 that is, interactivity, whether it is really important, for example, whether some pages come in and just look at them and leave, there is nothing to use react the place of ability , then do we load a file like core.js without coming in.
  3. Encapsulate a component similar to the Host component, so that the component within it can appear in the visible area before loading the component.
  4. There may be multiple child components inside a parent component, but there may be only one child component most commonly used, so is it possible to lazy load the parent component and the rest of the child components?