当前位置: 首页 > Web前端 > HTML

如何去除项目中99%的JS代码

时间:2023-03-28 10:31:26 HTML

大家好,我是Kason。在最近的WWC22上,builder.io的CTOmi?kohevery(也是Angular/AngularJS的发明者)发表了一段富有想象力的演讲。他在演讲中介绍了一个全栈SSR框架——Qwik,号称可以帮你去除项目中99%的JS代码。他是怎么做到的,本文我们将介绍Qwik。欢迎加入人类优质前端框架群,飞行性能差?如果你不想做coder,那我们先说说Qwik诞生的背景。对于很多2C的Web应用(比如电商)来说,首屏性能指标与用户留存相关,而用户留存与赚多少钱有关。因此,应用程序打开的速度会影响您赚多少钱。然而,对于前端开发者来说,首屏性能指标并不容易优化。原因并不是开发人员不够努力。让我们看一下两个性能指标。如何优化FCPFCP(FirstContentfulPaint)测量从页面开始加载到页面内容的任何部分完成在屏幕上呈现的时间。目前Web应用普遍采用前端框架开发,这意味着从HTML解析到最终页面渲染都会引入大量的JS代码(框架本身的代码,第三方依赖包的代码……)。框架JS代码执行框架JS代码,框架完成页面渲染,导致FCP指标下降。为了优化FCP,框架的作者提出了SSR(ServerSideRender,服务器端渲染),在服务器端生成首屏需要的HTML,为FCP节省了上述三个步骤所需要的时间.但是,TTI指标仍需优化。如何优化TTITTI(交互时间)测量页面完全交互所需的时间。主要测量从以下1到3所需要的时间:首先测量页面中元素绑定事件与元素交互后的FCP时间,事件响应时间在50ms以内。使用SSR后,虽然FCP降低了,但是framehydrate(Waterinjection,即框架让页面响应交互所花费的时间)对TTI还是有影响的。可见性能瓶颈的根源在JS代码。React18的SelectiveHydration通过优先考虑用户交互部分的水化来优化TTI指标。然而,Qwik更为极端。他的目标是杀死所有不必要的JS耗时。这里的耗时包括两部分:作为静态资源加载的JS耗时,以及JS运行时超超细粒度hydrate的耗时。说传统SSR的粒度是整个页面。那么React18的SelectiveHydration的粒度就是产生交互的组件。那么Qwik的粒度就是组件中的某个方法。例如下面是HelloWorld组件(可以发现Qwik使用了类似React的语法):对应的页面渲染效果:打开浏览器网络面板,这个页面会有多少JS请求?由于这是一个静态组件,没有逻辑,所以答案是:没有JS请求。让我们来看看经典的Counter组件。与HelloWorld相比,增加了点击按钮状态变化的逻辑。代码如下:对应的页面渲染效果:打开浏览器网络面板,这个页面会有多少个JS请求?答案又是:没有JS请求。注意,在这两个组件的代码中,component$是用来定义组件的,并且有一个$符号。在Counter中,onClick$回调也有一个$符号。在Qwik中,以$为后缀的函数是延迟加载的。hydrate的粒度有多细取决于$的定义有多细。比如Counter中,如果onClick$有$后缀,那么点击回调就是懒加载的,所以首屏渲染不会包含点击后逻辑对应的JS代码。点击按钮后,会发起2个JS请求。第一个请求返回点击后的逻辑:第二个JS请求返回组件重新渲染的逻辑:执行完这两段代码后,Counter变为1。如果查看元素,会发现在点击前,逻辑的地址存放在button的on:click属性中:点击后,会从对应的地址下载JS代码,执行对应的逻辑。从优秀到极致,你觉得优化到极致了吗?还没有。对于一些长期存在于页面中,需要JS驱动的模块(比如轮播图),在模块展示之前,并不一定要先对应JS。比如下面这个时钟的例子,页面上有一个长长的列表,比一屏高,列表的底部有一个时钟。下面是列表滚动到底部时的样子:在Clock组件的useClientEffect$中定义了时钟指针摆动的逻辑:Qwik中也有类似React的useEffect,但是在Qwik中可以执行这个Hook在服务器/客户端上。为了区分,useClientEffect是只在客户端执行的useEffect。加上$后缀表示是懒加载的。具体效果是:当页面滚动到铃铛响起时,不会请求useClientEffect$对应的JS代码。时钟暴露时,会发起两次JS资源请求:useClientEffect时钟组件的逻辑重新渲染逻辑如果检查元素,在时钟暴露之前,指针对应的元素不会移动:当时钟暴露,加载并执行JS代码,动效启动:对于传统SSR中的datahydrate,数据实际上初始化了两次:第一次渲染页面,此时服务端导出的html已经承载了首屏渲染的数据框。hydrate之后,再次转换数据,用于frame中状态的后续渲染在Qwik中,页面初始化的时候,会有一个qwik/json类型的script标签,用于存放当前页面激活状态对应的数据:激活了什么?比如下面是一篇文章的评论区。这是首屏渲染后的样子:这些评论数据会出现在qwik/json保存的数据中吗?不,因为没有交互来激活它们。我们发现一个评论被折叠了,点击后评论会展开:点击这个行为会请求:点击逻辑对应的JS代码。该注释对应于组件的重新渲染逻辑。这时候评论数据会出现在qwik/json中,因为点击交互激活了这个数据。所以在Qwik中,如果没有必要,数据不会被初始化两次。HTML中有非激活数据,激活数据保存在qwik/json的script标签中。这个功能会带来一个很有趣的效果:在调试工具中复制Elements面板下的DOM结构,然后粘贴到新页面中,可以重现页面当前的交互状态(比如输入框仍然保留之前输入的内容):在其他框架中,只能重现页面的初始状态。交互的时候请求JS会不会卡?可能有同学会问,如果网络不好,在交互的时候请求JS代码不会导致交互卡顿吗?Qwik允许您指定哪些组件很可能被用户高概率操作(例如,在电子商务应用程序中,购物车按钮被点击的概率很高)。这些组件逻辑对应的JS代码会被预取,在不影响首屏渲染的情况下被预请求:并且这些组件预取的顺序是可以调整的。这意味着可以跟踪用户行为,并且可以将用户交互的频率作为指标,作为组件预取优先级的依据,以启发式地提高应用程序性能。这是真正的面向用户的性能优化,而且是全自动的。总结一下,今天是一个前端框架百花齐放的时代,不同的框架都在寻找自己独特的卖点。Qwik的卖点是将JS代码的拆分从普通的编译时(如webpackchunking)、运行时(如动态导入)改为交互时。JS代码的最终拆分只是为了达到一个目的——在首屏渲染时去掉你项目中99%的JS代码。对于这波操作,你怎么看?