Qwik-前端性能的终极方案?

2022-12-08 0 351

责任编辑为源自 黄瓜音频后端控制技术项目组的该文,已许可 ELab 正式发布。 译者:张永清

Qwik 是甚么

Qwik 是两个后端架构,句法类似于 React 采用 JSX 和 Hooks,但是 Qwik 是全栈SSR架构,所以 Qwik 选用了一连串思路强化网页的井字操控性,做的不论应用领域表面积多大,井字操控性 PageSpeed 试验基本上都能达至最高分 Framework Benchmarks。

Misko Hevery

Misko Hevery,Qwik 的译者,更加著名的Title是Angular.js&Angular的译者。

他在圣迭戈高中毕业后相继去了矽谷许多子公司,Intel、Xerox(伯杰)、Sun和Adobe子公司他都有组织工作过,在那些子公司他主要就专门从事资料库/后端各方面的组织工作。

2005年重新加入了Google,2009年和Adam Abrons一同合作开发了Angular.js,而后正式成为Google的工程项目。

2021 年从Google离任重新加入builder.io正式成为CTO并已经开始了 Qwik 工程项目。

两个难题

为何后端架构不断涌现,Svelte、SolidJS、Astro、Fresh、Marko、Qwik?

Big Runtime

react 和 Vue 都是如前所述 Runtime 的架构,即架构这类有许多的标识符,且单厢装箱到最后的乙醛中被发送至采用者的应用领域程序里继续执行,当采用者须要在网页上继续执行操作方式发生改变模块的状况时,架构的 Runtime 会依照捷伊模块状况(State)排序(diff)出须要预览的DOM结点进而预览View。

Qwik-前端性能的终极方案?

从上图能窥见,像 Angular、React、Vue 等 Big Runtime 的架构表面积都较为大,在井字读取的这时候就须要读取架构的JS文件并且继续执行相关的标识符,那么就会产生一定的操控性开销,尤其是弱网和低操控性手机,操控性影响就会越大。

Virtual DOM is Pure overhead

Virtual DOM is pure overhead

Virtual DOM 并不高效。说Virtual DOM高效是因为它不会直接操作方式原生DOM,操作方式DOM较为消耗操控性。应用领域状况变化时架构会生成捷伊 Virtual DOM,并通过diff算法去排序出本次数据预览真实的视图变化,然后只发生改变“须要发生改变”的DOM结点。

React 顶层模块state预览,如果不进行任何强化,则所有子模块单厢重渲染(re-render)。所谓的re-render是你 Class Component 的render方法被重新继续执行,或者函数模块被重新继续执行。模块被重渲染是因为Vitual DOM的高效是建立在 Diff 算法上的,而要有 Diff 一定要将模块重渲染才能知道模块的新状况和旧状况有没有发生发生改变,进而才能排序出哪些DOM须要被预览。

正是因为架构这类很难避免无用的渲染,React才允许采用一些诸如shouldComponentUpdate、PureComponent、memo和useMemo的API去告诉架构哪些模块不须要被重渲染。

在 React 16 版本之前,Virtual DOM 的对比是通过递归实现,如果模块树嵌套很深,那操控性势必降低;React 16 之后,推出 Fiber 架构,虽然省不掉必要的 render,但把递归 Diff 改为可打断的循环,并且花费精力解决任务优先级调度难题,强化了采用者体验。

Virtual DOM 还有个最大的难题——额外的内存占用,以 Vue 的 Virtual DOM 对象为例,100W 个空的 Virtual DOM(Vue) 会占用 110M 内存。

我认为最重要的难题是模块状况到网页元素是有映射关系的,而是用 Virtual DOM 则丢失了这个映射关系,须要 DOM Diff 来重新构建这个关系,纯粹是多余的消耗(Pure Overhead)。

SSR Hydration is Pure Overhead

Hydration is Pure Overhead

Hydration 是为了对服务端进行渲染的HTML提供交互能力的计划。Hydration 的过程一般是运行架构和模块标识符,还原应用领域状况和构建Virtual DOM,并将事件监听添加到HTML元素上。

Qwik-前端性能的终极方案?

Misko Hevery认为Hydration是很低效的解决计划。

Qwik-前端性能的终极方案?

从上图能窥见,Hydration 须要较长的时间来进行应用领域的状况恢复,主要就因为以下两点:

1、架构必须下载与当前网页相关的所有模块标识符并且由应用领域程序引擎进行解析和继续执行。

2、架构必须继续执行与网页上的模块关联的标识符重新构建整个应用领域程序,以重建事件监听和内部模块树,即使实际上没有创建任何捷伊DOM。

所以 Hydration 是纯开销,因为整个应用领域的构建过程在 Node 上都已经运行过了,但是这部分信息没有同步到应用领域程序,而是丢弃掉了,所以客户端须要重新继续执行一遍标识符进行 Hydration 来重新恢复整个应用领域。而如果服务端将所有的应用领域所须要的信息序列化传输到应用领域程序,那么 Hydration 的过程完全能省略。

所以 Hydration 是与应用领域的复杂度成正比的。所以即采用了 SSR,网页的 TTI 也可能不是很好。

社区的探索Precompile

如今许多捷伊架构都没有 VDOM,反而是通过预编译然后直接进行细粒度的 DOM操作方式来达至比 VDOM 更好的操控性。

Qwik-前端性能的终极方案?

Svelte 是 Precompile 的先行者,其通过静态编译减少架构运行时的标识符量,两个 Svelte2 模块编译了以后,所有须要的运行时标识符都包含在里面了,除了引入这个模块这类,你不须要再额外引入两个所谓的架构运行时!

{{ msg }} 会被编译成如下标识符:function renderMainFragment(root, component, target) { var a = document . createElement(a); var text = document . createTextNode(root . msg); a . appendChild(text); target . appendChild(a) return { update: function (changed, root) { text . data = root . msg; }, teardown: function (detach) { if (detach) a . parentNode . removeChild(a); } }; } 能看到,跟如前所述 Virtual DOM 的架构相比,这样的输出不须要 Virtual DOM 的 diff/patch 操作方式,自然能省去大量标识符,同时,操控性上也和 vanilla JS 相差无几(仅就这个简单示例而言),内存占用更是极佳。这个思路其实并不是它首创,之前有两个操控性爆表的模板引擎 Monkberry.js 也是这样实现的,ng2 的模板编译其实也跟这个很类似于(但是中间加了一层渲染抽象层)。 如何看待 svelte 这个后端架构?- 尤雨溪的回答 – 知乎 https://www.zhihu.com/question/53150351/answer/133912199

SolidJS 也是 Precompile,和 Svelte2 相比有少量的运行时,目前 Svelte 也改为有少量运行时的计划来减少标识符表面积,所以支持 Tree Shaking。

SolidJS

Qwik-前端性能的终极方案?

Demo在线示例

Svelte 和 SolidJS 等预编译架构解决了 Runtime 和 VDOM 的难题,没有解决了 Hydration 的难题。

Islands Architecture

Islands 架构模型早在 2019 年就被提出来了,并在 2021 年被 Preact 译者JSON Miller在 Islands Architecture 一文中得到推广。这个模型主要就用于 SSR (也包括 SSG) 应用领域,我们知道,在传统的 SSR 应用领域中,服务端会给应用领域程序响应完整的 HTML 内容,并在 HTML 中注入一段完整的 JS 脚本用于完成事件的绑定,也就是完成 hydration (注水) 的过程。当注水的过程完成之后,网页也才能真正地能够进行交互。当两个网页中只有部分的模块交互,那么对于那些可交互的模块,我们能继续执行 hydration 过程,因为模块之间是互相独立的。 而对于静态模块,即不可交互的模块,我们能让其不参与 hydration 过程,直接复用服务端下发的 HTML 内容。可交互的模块就犹如整个网页中的孤岛(Island),因此这种模式叫做 Islands 架构。 摘录自 Islands 架构原理和实践

Islands Architecture 没有解决 Runtime 的难题,部分解决了 Hydration 和 VDOM 的难题。

React Server Component(RSC)

React 在 2020年12月正式发布了 RSC 的 Demo,能在Node.js上运行 RSC,然后生成 DSL 下发到应用领域程序,最后由架构层解析 DSL 并预览到 DOM 上。

Qwik-前端性能的终极方案?

RSC 能将一些模块的渲染放到服务端,后端做纯展示,所以仅RSC的依赖不会被装箱到客户端,这样如果某个模块有两个较大的第三方依赖,就能把第三方依赖放到RSC里,在服务端运行模块并将产生的结果传输到应用领域程序端进行展示。

用官方给出的demo来举例子,为了渲染两个用markdown写的笔记,我们须要用到240kb的js标识符(gzip之后是74kb)充当运行时:// NoteWithMarkdown.js // NOTE: *before* Server Components import marked from marked; // 35.9K (11.2K gzipped) import sanitizeHtml from sanitize-html; // 206K (63.3K gzipped) function NoteWithMarkdown({text}) { const html = sanitizeHtml(marked(text)); return (/* render */); } 笔记只是用于查看,此时此刻它是纯静态的(不须要采用者与之交互)。那么如果我们能够在服务器上把它渲染成静态内容,我们是不是省掉把大量js标识符传输到客户端,解析和继续执行的成本了呢?有了React Server Component,我们能够做到这一点。

RSC 没有解决 Runtime 难题,部分解决了 Hydration 和 VDOM 的难题。

Resumable

Resumable(可恢复性)是 Qwik 提出的两个概念,是指SSR时应用领域在服务端继续执行后能在客户端恢复继续执行,而不用重新构建和下载所有的应用领域标识符,也不须要对整个应用领域进行 Hydration。

Qwik-前端性能的终极方案?Qwik 是如何实现 Resumable 的?Precompile

Qwik 提供了 optimizer 对标识符进行预编译,底层由 SWC 进行驱动。

看两个HelloWorld Demo

Qwik-前端性能的终极方案?

上面的标识符经过optimizer.transformModules编译后会生成两个JSON,然后对其处理成 js 文件

Qwik-前端性能的终极方案?

Qwik-前端性能的终极方案?

能看到,Qwik 把两个模块便后成了两个文件,两个处理 DOM 逻辑,两个处理事件逻辑。

最后通过 renderToString 生成的 SSR HTML 如下:

Hello Qwik

< script type= “qwik/json” > { “ctx” :{ “#1” :{ “r” : “0” }}, “objs” :[ “Qwik” ], “subs” :}

< script > window . qwikevents ||= ; window . qwikevents . push ( “click” )

Interactive

当在 SSR 阶段生成 HTML,第二部就是要在应用领域程序处理事件了,Big Runtime架构是通过 Hydration 来进行事件监听,让网页进行事件响应的,而Qwik则采取了完全不同的计划。

Qwik 的事件监听全部由网页上的qwikevents来处理 :

< script > window . qwikevents ||= ; window . qwikevents . push ( “click” )

Qwikevents 会监听 document/window 的全局事件,而不是针对每个DOM单独监听其事件,这能在不存在应用领域标识符的情况下实现事件监听。

对触发事件的

ResumableComponent tree

通常架构须要在应用领域程序中构建模块树实现对网页的预览(Hydration),Qwik 则通过在 SSRrenderToString的过程中收集模块信息并将其序列化到 HTML 上,所以其不须要在运行时构建模块树,所以能实现以下能力:

Qwik-前端性能的终极方案?

在模块标识符不存在的情况下重建模块层次结构信息,模块标识符能保持惰性。

Qwik 能实现懒读取,只为须要重新渲染的模块重建模块层次结构信息,而不是整个应用领域。

Qwik 收集store和模块之间的关系信息,并创建两个订阅模型,通知 Qwik 哪些模块由于状况更改而须要重新渲染。订阅信息也被序列化到 HTML。

Application state

所有架构都须要保持状况。大多数架构以引用和闭包的形式将此状况保存在标识符中,这样就导致初始化这时候须要下载所有标识符,做好关联,也就是Hydration,但是这样通常会有个难题,就是如果须要恢复子模块,那父模块也须要恢复。Qwik的独特之处在于状况以属性的形式保存在 DOM 中,这使得Qwik模块能独立进行恢复。

在 DOM 中保持状况的后果有许多独特的好处,包括:

1、通过以字符串属性的形式在 DOM 中保持状况,应用领域程序能随时序列化为 HTML。

HTML 能通过网络发送并反序列化为不同客户端上的 DOM。然后能恢复反序列化的 DOM。

2、每个模块都能独立于任何其他模块来恢复。这种只允许对整个应用领域程序的两个子集进行Hydrate 且不须要时序,并仅仅下载须要响应采用者操作方式的标识符,这与传统架构有很大不同。

3、Qwik 是两个无状况架构(所有应用领域程序状况都以字符串的形式存在于 DOM 中)。无状况标识符易于序列化、传输和恢复。这也是允许模块彼此独立Hydration的原因。

4、应用领域程序能在任何时间点进行序列化(不仅仅是在初始渲染时),并且能多次序列化。

强化

大家也会有个疑问:如果网络延迟,点击事件会不会卡顿呢?

Prefetching

Qwik 提供了prefetchStrategy方法来进行 JS 的预取:

export default function (opts: RenderToStreamOptions) {

return renderToStream(, {

manifest,

prefetchStrategy: {

// custom prefetching config

},

…opts,

默认情况下,Qwik 会预取网页上所有可见结点的监听器,也能自己配置预取思路:

Qwik-前端性能的终极方案?

One More Thing

Partytown

Partytown,是两个轻量级的减少第三方JS脚本运行导致网页读取难题的开源工具,由 Builder.io 维护,目前处于试验阶段。通过将第三方JS在WebWorker运行,减少了由于第三方JavaScript导致的继续执行延迟。其想解决以下难题:

释放主线程资源以仅用于主应用领域继续执行;

对第三方脚本进行沙盒处理,并允许或拒绝其访问主线程 API;

在 Web Worker 中隔离长时间运行的任务;

通过将DOM setters/getters 批处理到组预览中来减少来自第三方脚本的布局难题;

限制第三方脚本对主线程的访问;

允许第三方脚本完全按照它们的编码方式运行,而无需任何更改;

从 Web Worker 中同步操作方式(读取/写入)主线程 DOM。

Web Worker 的主要就难题是无法直接访问可从主线程访问的 DOM API,例如window, document或localStorage。虽然能在两个线程之间创建消息传递系统来代理 DOM 操作方式,但用于 Web Worker/主线程通信的postMessageAPI 是异步的。这意味着依赖于同步 DOM 操作方式的第三方脚本将无法按照预期运行。

Partytown 采用 JavaScript Proxy、Service Worker 和同步 XHR 请求,从 Web Worker 内部提供对 DOM API 的同步访问。

先对document采用 Proxy 进行拦截

再采用同步的 XHR 发起请求

然后采用 Service Worker 拦截请求

最后通过postMessage异步发送至主线程。

整个流程虽然较为繁琐,但是其好处就是在Web Worker 运行的 JS 来说,其访问 DOM API 是同步的,完全和主线程一样,就不必重写 JS 来处理 DOM API了。

此外,通过 Proxy 来代理 DOM API,能记录所有的 JS 访问 DOM 记录,并进行拦截限制。

结语

责任编辑对现今(2022/11)大部分架构进行两个简单的分析,也探究了一些社区解决计划,Qwik 算是那些计划的集大成者,Qwik 提出的 Resumable 思想是对现今架构的两个颠覆,我认为其对后端的意义不亚于 VDOM 和 JSX,未来几年应该会更多的看到社区对 Resumable 探索和应用领域,甚至最后取代 React 也未可知。

虽然理念已经较为成熟,但 Qwik 架构这类目前还处于非常初期的版本,架构这类还有较多的难题,建议大家能进行学习研究,持续关注其发展,但短期不要在正式工程项目中采用。

参考

Virtual DOM is pure overhead-https://svelte.dev/blog/virtual-dom-is-pure-overhead

Hydration is Pure Overhead-https://www.builder.io/blog/hydration-is-pure-overhead

Resumable vs. Hydration-https://qwik.builder.io/docs/concepts/resumable/

Resumable JavaScript with Qwik-https://dev.to/this-is-learning/resumable-javascript-with-qwik-2i29

MPAs vs. SPAs-https://docs.astro.build/en/concepts/mpa-vs-spa/

Islands Architecture-https://jasonformat.com/islands-architecture/

The new wave of Javascript web frameworks-https://frontendmastery.com/posts/the-new-wave-of-javascript-web-frameworks/

How we cut 99% of our JavaScript with Qwik + Partytown-https://www.builder.io/blog/how-we-cut-99-percent-js-with-qwik-and-partytown?utm_source=pocket_mylist

都快2020年,你还没听说过SvelteJS?-https://zhuanlan.zhihu.com/p/97825481

Islands 架构原理和实践-https://mp.weixin.qq.com/s/MfztwYyEH30F9IL0keAM5w

Virtual DOM 认知误区-https://juejin.cn/post/6898526276529684493

如何看待 svelte 这个后端架构?- 尤雨溪-https://www.zhihu.com/question/53150351/answer/133912199

【react】初探server component-https://juejin.cn/post/6918602124804915208

后端架构对比(主要就吐槽 React )-https://juejin.cn/post/7158285916266561572#heading-9

Qwik.js架构是如何追求极致操控性的?!-https://segmentfault.com/a/1190000042250628

Introducing Partytown : Run Third-Party Scripts From a Web Worker-https://dev.to/adamdbradley/introducing-partytown-run-third-party-scripts-from-a-web-worker-2cnp

How Partytown Eliminates Website Bloat From Third-Party Scripts-https://www.smashingmagazine.com/2022/04/partytown-eliminates-website-bloat-third-party-apps/

我们来自字节跳动,是旗下黄瓜音频后端部门,负责黄瓜音频的产品研发组织工作。

我们致力于分享产品内的业务实践,为业界提供经验价值。包括但不限于营销搭建、互动玩法、工程能力、稳定性、Nodejs、中后台等方向。

我们在招的岗位:https://job.toutiao.com/s/rDoHAqH。招聘的城市:北京/上海/厦门。

欢迎大家重新加入我们,一同做有挑战的事情!

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务