【AICon】AI 基础设施、LLM运维、大模型训练与推理,一场会议,全方位涵盖! >>> 了解详情
写点什么

全新 JavaScript 框架 Qwik:以独特的可恢复性方式提速网页应用

  • 2022-11-22
    北京
  • 本文字数:3414 字

    阅读完需:约 11 分钟

全新JavaScript框架Qwik:以独特的可恢复性方式提速网页应用

AngularJS 的创造者Misko Hevery近期宣布了新网络框架Qwik测试版本的推出,声称无论应用程序有多大,Qwik 都能够快速地构建。在多数情况下,Qwik 会先下载 1KB 的 JavaScript,在需要的时候才会懒加载或预处理程序和应用程序代码。


在一次名为《如何从主线程中移除99%的JavaScript》的演讲中,Hevery 介绍了 Qwik 背后的原理。


Qwik 的目标很简单,确保复杂的网站也能在谷歌页面速度评分项上拿到 100/100……归根究底,就是要让互动时间尽可能地缩短。

 

如你所见,行业中的大多数框架都能在优化图片和 CSS 上做到尽善尽美,但 JavaScript 方面却又乏善可陈。因为这对于互联网上的每个人来说都是系统性的问题,我的意思是说,问题根源在于工具而不是开发者。

 

用于优化 JavaScript 交付速度的工具是 Qwik 关注的问题。


Misko 将 JavaScript 在互动时间指标上负面的表现归因于水合(Hydration)作用。水合在连接服务器的渲染时出现。服务器接收到客户端对页面的请求后,做出对应查询以填充界面,并将结果返回客户端。虽然对用户来说,服务器端的页面渲染速度通常要比客户端渲染的页面要快(如更快的首次内容绘制),但页面却并不是立即就可交互的,客户端还需要下载并执行页面上的 JavaScript 脚本。

 

在多数框架中,这种首次交付的 HTML 与应用程序的 JavaScript 协调的过程称作水合。在水过程中,Web 应用程序框架将事件处理程序和 DOM 元素相连接,并初始化应用程序状态。水合之后用户操作会被事件处理程序捕捉,从而使页面可交互。

 

Qwik 保留了服务器端的渲染,通过在服务器上运行应用程序以避免水合。它将所有相关状态信息序列化,将页面内容和序列化的状态一起以 HTML 的形式发送给客户端。这些相关的状态信息包括时间监听器、内部数据结构,以及应用状态。借助序列化的状态,客户端可以接力完成服务器端没有执行完的任务

 

处理交互性的 JavaScript 加载默认是延迟进行的,一般是直到用户实际使用交互时才启动,也就是说一个按钮的事件处理程序最晚可以在用户点击按钮时加载。这种即时的 JavaScript 获取加上预取策略,利用浏览器的本地能力,在不影响页面交互性的前提下完成了文件的加载。

 

在 Qwik 文档中有详细的介绍:

 

Qwik 只会预取当前页面需要的代码,避免下载与静态组件相关的代码。最坏的情况是 Qwik 预取的代码量与现有框架的最佳情况相同,而在大多数情况下,Qwik 所预取的代码只会比现有框架要少。

 

除主线程之外的其他线程都可以做到代码预取,大多数浏览器甚至支持主线程之外的代码 AST 语法预分析。


如果用户在预取完成之前开始交互,浏览器会自动优先交互模块于其他预取模块。

 

Qwik 可以将应用程序分解成部分,这些分块可以按照用户交互的概率高低顺序进行下载。

 

Qwik 网站为开发者提供了教程、实例,以及学习和尝试 Qwik 的在线运行平台。以简单的计数器为例,由一个按钮和显示按钮点击次数的文本框组成,实现方法如下:


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++}>Click</button> </p> </div> );});
复制代码


开发者可以通过 Qwik 的component$ API 创建可恢复组件,有状态的组件可以通过useStore API 显示其对某段状态的依赖。在处理程序的名字后附加$ 字符创建可恢复的事件处理程序(如前文例子中的onclick$ )。通过这些手动添加的提示,Qwik 可以将应用程序文件打包,以实现并优化 JavaScript 的懒加载。前文的计数器程序在服务器端渲染的 HTML 如下:


<!DOCTYPE html><html  q:container="paused"  q:version="0.11.1"  q:render="ssr"  q:base="/repl/21kry8ac4hl/build/">  <html>    <head q:head>      <title q:head>Tutorial</title>    </head>    <body>      <!--qv q:id=0 q:key=AkbU84a8zes:-->      <div>        <p>          Count:          <!--t=1-->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>      <!--/qv-->    </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>
复制代码


注意,HTML 文件是通过以下方式强化的。


  • q: 属性,如q:baseq:idq:key

  • 包含特定框架信息的 HTML 注释,如<!--qv q:id=0 q:key=AkbU84a8zes:-->

  • 序列化状态,如<script type="qwik/json"> {"ctx": ..., "objs":[{"count":"1"},0], "subs":[["2 #0 0 #1 data count"]]} </script>

  • 用于在客户端恢复应用程序执行的 Qwik 脚本,如<script id="qwikloader"> ... </script>window.qwikevents.push("click")


Qwik 的在线代码运行平台可以让开发者了解到程序代码是如何被切割打包的,还是用前面的计数器为例,客户端的打包方式如下:



如截图所示,计数器的应用程序被分成了三个脚本。当用户点击按钮时,动态下载并执行其中两个脚本(Qwik 运行时间和 click 事件处理程序的代码)。



参考Qwik文档了解具体执行情况以及代码拆分的原理。Qwik 的网站给出了大量包括教程示例,以及演示在内的信息,还有一个可互动的在线代码运行平台。Qwik 社区中同样也有一个非常简单的电子商务示例,一般对电子商务的厂商来说,页面加载速度提高收入也会增加。


Qwik 团队目前由 AngularJS 的创造者 Miško Hevery、基于 Go 语言 Web 架构 Gin 的创造者 Manu Almeida、Web组件编译器Stencil的创造者 Adam Bradley 组成。

 

目前,Qwik 已推出测试版,且采用 MIT 许可开源,欢迎各位在遵循 Qwik行为准则的前提下贡献代码。


原文链接:

New Qwik JavaScript Framework Seeks Faster Web Apps with Unique Approach: Resumability

2022-11-22 11:488772

评论

发布
暂无评论
发现更多内容

Prometheus 监测 RocketMQ 最佳实践

阿里巴巴云原生

阿里云 RocketMQ 云原生 Prometheus

易观分析:2022年Q3中国跨境进口零售电商市场规模为1124.8亿元

易观分析

报告 跨境电商

大数据培训应该怎么学习

小谷哥

Java程序员的BAT面试之路:数据库事物特性及隔离级别,记得看看

钟奕礼

Java 程序员 java面试 java编程

论文领读|基于 VQVAE 的长文本生成

澜舟孟子开源社区

人工智能

开发者问第五期

HMS Core

HMS Core

服务全球开发者!灵雀云与Ubuntu推出一体化云原生解决方案

York

容器 云原生 操作系统 开源生态

人人都可以给想象插上翅膀(内含AI绘画教程)

鼎道智联

openai AI绘画 鼎道智联

Pycharm 搭建 Django 项目

千锋IT教育

前端培训与自学有什么区别吗

小谷哥

【web 开发基础】PHP 中的递归函数 (38)

迷彩

递归 11月月更 PHP递归 递归函数

APISIX Ingress 是如何支持上千个 Pod 副本的应用

API7.ai 技术团队

Kubernetes 容器 api 网关 APISIX

2022年10月中国网约车领域月度观察

易观分析

报告 网约车

Kotlin函数和扩展(extension)

子不语Any

kotlin Andrdoid 11月月更

在实践中学习类型定义、类型覆盖、CSS Modules

小鑫同学

CSS typescript 前端 11月月更

Kotlin函数声明与默认参数(Default argument)

子不语Any

android kotlin 11月月更

链表剖析及自己手撸"单链表"实现基本操作(初始化、增、删、改等)

C++后台开发

数据结构 链表 linux开发 Linux服务器开发 C++开发

完全解析大数据中MapReduce的运行流程

好程序员IT教育

大数据 MapReduce Service

Kotlin类声明和构造器(constructor)

子不语Any

android kotlin 11月月更

web前端培训学习后找工作难吗?

小谷哥

优先级反转那些事儿

字节跳动终端技术

ios QoS 移动开发 优先级反转 turnstile

超级自动化行业前景广阔——首个数字化转型国家标准发布:价值体系优化、创新和重构是数字化转型根本任务

九科Ninetech

Spring 依赖注入有哪几种方式

千锋IT教育

Kotlin变量和属性

子不语Any

kotlin andiod 11月月更

易观千帆 | 2022年10月银行APP月活跃用户规模盘点

易观分析

报告 手机银行

记一次HBASE的故障分析和排查过程

鲸品堂

大数据 11月月更

Baklib知识分享|企业产品需求文档的特点

Baklib

PRD 产品需求文档

牛啊!长这么大还是头一次见24W字的SpringBoot从入门到实战文档

程序知音

Java spring springboot java架构 后端技术

电容的“通交流、阻直流”,一次讲清楚

元器件秋姐

元器件采购 元器件电商 电容 电容特性 电容知识

龙蜥技术委员会主席杨勇:下一代操作系统展望

云布道师

开源 操作系统

项目管理软件有哪些?

优秀

项目管理软件

全新JavaScript框架Qwik:以独特的可恢复性方式提速网页应用_语言 & 开发_Bruno Couriol_InfoQ精选文章