写点什么

全新 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:488834

评论

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

Unload data from Databend | 新手篇(4)

Databend

databend

在 Spring 生态中玩转 RocketMQ

Apache RocketMQ

RocketMQ spring could

前端食堂技术周刊第 60 期:TypeScript 4.9、Ant Design 5.0、用 vanilla-extract 编写高性能的 CSS、Node.js 安全最佳实践

童欧巴

中国银河证券:缺少DevOps,企业数字化转型就是带着脚镣跳舞

嘉为蓝鲸

DevOps 数字化转型 金融 证券

深度解读隐语密态计算设备SPU

隐语SecretFlow

机器学习 开源 隐私计算 开源框架 隐语

第二章STP应用配置

初学者

网络 11月月更

MACH架构的质量工程指南

俞凡

架构 微服务 云原生

Alluxio 2.9新版发布 | 重塑架构,支持大规模多租户环境

Alluxio

分布式 新闻 Alluxio 大数据 开源 数据编排

低代码会使初级码农失业吗

秃头也爱科技

零基础转行前端培训学习好吗?

小谷哥

边旅游边赚钱!数字游民离不开远程控制软件

RayLink远程工具

远程控制软件 远程办公软件 远控软件 RayLink 远程控制连接

“PKS+生态(智方舟)加速营”圆满结束,九科信息与中电智方舟达成战略合作

九科Ninetech

第一章三层交换应用

初学者

网络 11月月更

国内外排名最靠前的20+项目管理软件

易成管理学

Java集合与数据类型

kingcall

Java JAVA集合 Java集合框架 java数据类型

需求收集方法工具,以及进行需求分析的6大要素

易成管理学

华为云Astro的前世今生:用7年时间革新低代码开发观念

科技怪授

浅谈中小企业进军新媒体行业为何屡次遭受挫败

石头IT视角

打造用户喜爱的产品,离不开需求助推器|影响地图Impact Mapping

老彦

敏捷开发 软件工程 设计思维 用户故事地图 影响地图

华为云弹性负载均衡ELB,如何保障服务器不瘫痪?

清欢科技

netty系列之:在netty中使用proxy protocol

程序那些事

Java 架构 Netty 程序那些事

【Ajax】全面详细了解git的基础操作【万字教学+面试常客】

坚毅的小解同志

git 11月月更

真正的按需计费-函数工作流 FunctionGraph实战,5分钟搭建图片压缩应用

秃头也爱科技

2022-11-21:第N高的薪水。表结构和数据的sql语句如下。请问sql语句如何写? DROP TABLE IF EXISTS employee; CREATE TABLE employee (

福大大架构师每日一题

数据库 sql 福大大

高标准企业级安全性,华为云会议为线上沟通保驾护航

秃头也爱科技

Meetup预告:SeaTunnel在天翼云数据集成平台的探索实践

Apache SeaTunnel

大数据 技术分享 数据同步 数据集成 Apache SeaTunnel

唯一获奖容器厂商!灵雀云斩获2022信创“大比武”通信赛道大奖

York

容器 云原生 数字化转型 国产化 信创云

数据仓库实战教程

kingcall

数据仓库 数据湖 数据安全 数仓 数仓建模

jvm(三)类加载机制、javac编译

想要飞的猪

JVM类加载

redis 集群实现方案与原理

想要飞的猪

redis哨兵集群 redid集群cluster

华为云企业交换机ESW,让数据业务无缝迁移上云

爱科技的水月

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