所以呢?
上次,我承诺要写一篇关于“如何获得 SPA 带来的好处,同时避免它们极度不堪的后果”的文章。Nolan Lawson 基本上就是这么写的,然后那个疯子又做了一遍。他几乎涵盖了我想要的所有内容:
- 如今,MPA 页面加载速度出奇地快
- 绘制保持、流式 HTML、跨页面代码缓存、后退/前进缓存等。
- Service Worker 渲染
- 另请参阅Jeremy Wagner 的文章,了解为什么离线优先 MPA 很酷
- 理论上,MPA 页面转换很快就会实现
- 实际上,Kroger.com 没有这个功能,我们的原生应用也几乎没有,所以我不在乎
- 他的主要观点是:
-
如果您使用 SPA 的唯一原因是“它可以加快导航速度”,那么也许是时候重新评估这一点了。
(我不认为他谈到边缘渲染和 MPA 是如何成为好伙伴的,但我提到了这一点,所以这里勾选了该框。)
既然诺兰说出了我本想说的话(用更少的语言!),我就开门见山了:我在这个系列中的观点是否让网站运行速度显著提升?以下是我说到做到的部分:
仅仅证明速度很重要还不够:我们还得在情感上说服大家。天哪,要让所有人看到,如果我们的网站速度快了,会有多好。
让人们感受到某种东西的最好方法就是让他们亲身体验。我们的网站对我们销售的手机来说是否很痛苦?是时候让他们痛苦一下了。
演示
我计划在月度产品会议上展示速度的重要性。大致如下:
-
为与会者购买足够的Poblano 手机。
-
在这些手机和受限连接上,尝试使用 Kroger.com:
- 登录
- 搜索“鸡蛋”
- 添加一些到购物车
- 尝试查看
-
在演示中重复这些步骤。
-
请注意性能是基石功能:没有它,其他功能就不存在。
在那个双关语糟糕的笔记本电脑旁边,摆放着最初 15 部演示手机中的 10 部。(KaiOS 翻盖手机让我避免了过度专注于 Chrome 或 Poblano VLE5 的规格。)
瞄准低端手机的一个好处是,演示硬件的成本相对较低。每台 Poblano 大约 35 美元,当时的促销活动把一些价格降到了 25 美元。
它有多快?
遗憾的是,我无法给你演示,所以这个视频就足够了:
由于每个视频的录制时间不同,浏览器 UI 也有所不同。(另外,猜猜 walmart.com 的框架。)
视频的文字描述
我争分夺秒地让 demo、kroger.com、Kroger 原生应用、amazon.com 和 walmart.com 尽快上线结账功能。以下是每个应用耗时顺序:
- 20秒演示
- amazon.com,59秒
- Kroger 原生应用,耗时 1 分 21 秒
- walmart.com,2分14秒
- kroger.com,3分44秒
如果您想提高 dev.to 的视频可访问性,请考虑对我的元素功能请求<track>
进行投票。
我们的 CDN 联系人一度将其半公开到现实互联网上。当我在@AmeliaBR的 Firefox 开发者工具中看到这个消息时,我兴奋不已:
那是俄亥俄州辛辛那提 → 加拿大埃德蒙顿。293毫秒对于网络响应来说并不算太差,但我很高兴,因为我知道我们可以更快……
- 大约50-100 毫秒来自地理距离,可以通过边缘渲染/缓存等进行改进。
- PCF 的 gorouter 延迟为 50ms。幸运的是,我们放弃了 PCF。
- Nagle 算法耗时 40ms,Node.js 和反向代理耗时甚至可能达到 80ms。这就是
TCP_NODELAY
目的所在。 - 调整 gzip/brotli 压缩,例如它们的缓冲区大小和刷新行为
- 较低延迟的 HTTPS 配置,例如较小的 TLS 记录大小
假设在现实世界中平均延迟为 200 毫秒。根据第一篇文章中的数据,基于 kroger.com 目前 1.2 个 TTFB 计算,这意味着每年 4000 万美元。或者,相当于当时公司利润的 5% 左右。(实际数字可能更高。如果延迟和收入之间存在如此大的差异,那么延迟与收入之间的变化就不再是线性关系了。)
那么...进展如何?
最令人关注的问题是它的表现如何?组织对它的看法如何?它的采用率如何?等等。
该组织对此有何看法?
现场的反响甚至超出了我最宽容的预期。只有房间里最严厉的“爸爸”式嗓音才能让听众安静下来,完成演讲。重要人物站起来表示,他们希望看到更多像这样自下而上的倡议。未能出席的贵宾也要求演示。甚至一些在 React 和 Web 性能方面与我意见相左的开发者也承认,他们对此很感兴趣。
这挺好的,但 kroger.com 的速度还是慢得吓人。至于如何从演示中学习,我认为有以下几种选择:
- 使新原则适应现有代码
- 重写(增量或非增量)
- 单独的MVP
将新原则应用于 kroger.com 的现有代码?
自然,大家会问,如何让我们现有的 React SSR 架构也能像演示中那样快速运行。没问题!为什么不直接用React?为什么不妥协并改进现有网站?
我们尝试了。开发人员在 Webpack 的“矿场”里苦苦挣扎,只为获得更小的打包文件。我们放弃了 IE11,以减少 polyfill 的使用。我们将页脚改成了静态 HTML。经过数月的努力,我们将 JS 打包文件缩小了约 10%。
一个月后,我们又回到了原点。
这是否意味着在 React 中实现快速网站太难了?拜托,这是一个无法回答的标题党问题。但这证明我们公司无法在 React SPA 架构下持续开发,否则网站速度会持续下降。也许是出于管理原因,也许是出于教育原因,但经过几次这样的循环,一个合理的结论是我们根本无法应对。当每个新功能都添加客户端 JS 时,感觉我们还没开始就已经注定要失败了。(试想一下,告诉一家企业,每个新功能都必须替换一个现有功能。看看你能坚持多久。)
有一次,有人让我用 React 写一篇关于 MPA 架构的成本/收益分析,该架构让 demo 运行速度更快。篇幅太长,我没法在这里重复,所以我就用经典的互联网手法™:把一个细微的话题解释成有争议的点。
不使用 React 开发多页应用的原因
- React 服务器渲染 HTML 的速度比许多其他框架/语言慢
-
如果你更频繁地进行服务器渲染,即使是很小的差异也会累积起来。而且这些差异并没有那么小。
- React 的页面加载性能不太好
-
react
+比许多框架react-dom
都大,其增长趋势令人沮丧。 -
理论上,React 页面可以很快。但实际上,它们很少能做到。
-
它的补液过程会惹恼用户,在最糟糕的时机执行大量工作,而且非常脆弱,难以推理。你希望每个页面都面临这些风险吗?
ℹ️ 好吧,我觉得我至少必须支持这一点。
从使用 SSR Rehydration 的真实网站收集的性能指标表明,强烈建议不要使用 SSR Rehydration。归根结底,原因在于用户体验:它很容易让用户陷入“恐怖谷”效应。
虚拟 DOM 方法在页面加载时会造成很大的开销:
- 渲染整个组件树
- 读回现有的 DOM
- 区分两者
- 渲染协调后的组件树
如果您要展示与初始响应几乎相同的内容,那么这会有很多不必要的工作
text/html
!先别管性能了。即使在 React 中正确地进行 rehydration也很棘手,因此将其用于 MPA 可能会导致每个页面崩溃:
- 为什么 React 中的服务器端渲染如此困难
- 补液的危险
- 大型电商应用中 React SSR 的案例研究
- 修复 Gatsby 的补液问题
- gatsbyjs#17914:[讨论] Gatsby、React 和 Hydration
- React 中“服务器渲染”的 bug
不,真的,浏览一下那些链接。问题的本质比具体细节更重要。
- React 对抗多页面思维模型
-
它更倾向于使用 JS 属性,而不是 HTML 属性(你懂的,就是那种
class
vs. 的className
东西)。这倒不是什么大问题,但确实有点儿征兆。 -
服务器端 React 及其生态系统努力假装自己在浏览器中运行。服务器和浏览器渲染之间的差异被视为同构故障,应该予以修复。
React 承诺即将推出解决这些问题的方法,但测试、基准测试和推测这些方法将是另一篇文章。(两年前它们也根本不存在。)我对 React 即将推出的流式传输和部分数据融合 (partial hydration) 的实现方式并不感到兴奋——我应该进行尽职测试,但对于非 JSON 流,单独的 HTTP 连接似乎在页面加载过程中效果不佳。
回到我的目标,Facebook 真的会为那些农村/低配置/网络不好的用户使用 React 吗?几乎没有 JS 的网站mbasic.facebook.com有一个数据点。
重写 kroger.com,是逐步重写还是逐步重写?
软件重写永远是个笑话。开发人员说这将是最后一次重写,因为我们终于知道该如何正确完成了。与此同时,企业会根据开发人员过去的错误程度,有意识地估计每个代码库的寿命。
因此,自然而然的问题是:我们下一个不可避免的重写应该是 Marko 吗?
我成功地在内部研发方面展示了我的方法和另一种方法。我不能公布具体细节,但我为此制作了这张难以捉摸的海报:
因为我是一个无可救药的 Web 开发人员,所以我使用 HTML 和 CSS 来制作它。
那次评测的官方结论是:“性能是应用程序的问题,而非平台的错。” 评测决定将长期目标锁定在开发者体验™上,而不是网站速度。
我暗自松了一口气:如果新的架构与上一个架构采用相同的人员、流程和文化,那么它实际上会更快吗?
既然成功避免了大规模重写,我们就可以尝试小规模的渐进式改进——加速 A/B 测试。如果成功,就足以让我们尝试进一步的改进,如果这些改进也成功了……
最简单的可行的办法似乎是先将静态资源<script>
和<link>
元素流式传输到其余 HTML 部分之前。我们会用 Marko 重写外部脚手架 HTML,然后将 React 嵌入到页面的动态部分中。以下是一个简化的示例,可以解释我的意思:
import {
renderReactRoot,
fetchDataDependencies
} from './react-app'
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<for|{ url }| of=input.webpackStaticAssets>
<if(url.endsWith('.js')>
<script defer src=url></script>
</if>
<if(url.endsWith('.css')>
<link rel="stylesheet" href=url>
</if>
</for>
<PageMetadata ...input.request />
</head>
<body>
<await(fetchDataDependencies(input.request, input.response)>
<@then|data|>
$!{renderReactRoot(data)}
</@then>
</await>
</body>
</html>
这有许多改进:
-
当服务器等待动态数据和 React SSR 时,浏览器可以下载并解析我们的静态资产。
-
由于 Marko 仅使用 序列化组件
state
,因此外部 HTML 不会添加到我们的 JS 包中。(这比上面的例子更严重;由于是 Real Codebase,我们的 HTML 脚手架更加复杂。) -
如果成功的话,我们可以从外到内重写组件,并在每一步中缩小捆绑包。
-
Marko 还通过更高效的 SSR 和更小的 HTML 输出(引用剥离、标签省略等)收回成本,因此除非我们愿意,否则我们不会降低服务器指标。
这差点就成功了!但我们的 Redux 代码却阻碍了我们。我们的 Reducers 'n' Friends 包含足够多的重定向/页面元数据/分析/业务逻辑,这些逻辑假设整个页面会一次性发送,任何代码都可以随意返回 DOM 并更改先前生成的 HTML……就像<head>
……
我们尝试抽出开发时间来解决这个问题,因为无论如何,在 React 18 的世界中,我们都必须让 Redux 能够兼容流处理。可惜的是,Redux 及其生态系统在设计时并未考虑到流处理,因此,分配足够的开发时间来克服这些障碍被认为“不够以产品为导向”。
推出一个单独的、更快的 kroger.com 版本?
虽然“让 React 实现这个”的尝试和 Streaming A/B 测试都挺好,但它们不是我的首选。我倾向于单独启动一个低配置、带有 Respectful 重定向的网站——姑且称之为 Respectful 重定向吧https://kroger.but.fast/
。我喜欢这种方法,因为……
- 真实用户从显著加速中受益所需的最短时间
- 有助于解决文化悖论:你现有的文化造就了现在的网站。在这种文化中推行新方法将会改变你现有的文化或最终结果,而改变的可能性取决于需要多少人参与。一个拥有自己目标的小团队可以培育自己的文化,以实现这些目标。
- 如果它取得足够大的成功,它就可以依靠自己的成果运行,同时积累功能,直到“我们应该交换吗?”这个问题变成明显的“是”或“否”。
被收养了多少?
嗯……说来话长。
性能团队被并入了 Web 平台团队。这本意是好的,但回想起来,平台团队的高紧急性部署、监控和事件响应不可避免地挤占了重要但紧急性较低的速度改进工作。
许多人也对一个独立的、更快的网站的想法很感兴趣。他们自愿贡献技能和时间,估算预算、设置持续集成/持续交付 (CI/CD) 以及其他帮助。他们的努力、善意和乐观让我惊叹不已。事情似乎不可避免地会发生——至少,我们会收到一个明确的拒绝,这可以指导我们接下来的尝试。
好消息是:确实发生了一些事情。
坏消息是:美国 2020 年春季实施封锁。
最初的震惊过后,我意识到自己处于一个独特的位置:
-
由于新冠疫情,进入超市变得极其危险。
-
这场疫情对蓝领工人、高危人群和无家可归者造成了尤为严重的伤害。
-
我有一个概念验证,即使是便宜和/或连接不良的设备也可以快速在线浏览、购买和订购杂货。
即使居家令期间,人们也不会停止购买食品或药品。如果我们有一个网站,让即使是最贫困的人也能足不出户购物,那将挽救生命。即使他们只能浏览,也能减少在店内停留的时间。
怀着前所未有的明确目标,我全身心投入到kroger.but.fast
MVP 的开发中。我知道这会让我精疲力竭,但我也知道,任何半心半意的行为都会让我后悔终生——不去尝试,在道德上是错误的。
我们把演示版本放在一个几乎完全公开的prod bucket里运行,只有一个秘密登录才能访问。我们尝试让内部的任何人都用它来买杂货。
我不确定是否有人对此感到困扰。
我不知道到底发生了什么。我的经历和Zack Argyle 在 Pinterest Lite 的经历很相似,只是没有一个圆满的结局。(他花了 5 年时间,所以也许我只是不耐烦。)我是个合同工,而不是“正式员工”,所以我无权参与内部决策——这也意味着我无法了解任何提交给上级的提案为何会丢失或被拒绝。
一旦有小道消息称 Bridge 可能正在与这样的项目争夺资源……那时我决定留下来,除了快速通关高血压之外什么也不做。
当快速代码遇到不好的事情时
一方面,它明显缺乏真正的改变。为了达到所需的速度,演示版本故意否定了我们许多设计、开发甚至管理方面的决策。为了避免组织压力,某种形式的“臭鼬工厂”往往是实现这种重大改进的唯一途径,而这很难获得批准。
另一个原因是:要对现有产品进行彻底改进,必然会面临一个内在悖论:很多人的工作都依赖于该产品,你不可能让人们相信一个他们拿钱不相信的东西。尤其是在现有架构的销售速度比更早的架构更快的情况下。(情况不总是如此吗?)
我花了一段时间才明白,为什么人们在个人方面热情高涨,但在职业方面却无能为力。有一件事帮了我大忙,那就是《道德迷宫》里的名言。或者,如果你想找一个不那么容易让你沮丧的链接,我当时正试图在一个可以委婉地描述为0.5级的组织里开展一个4级项目。
关于我的话题已经说得够多了。你呢?
也许你正在开发一个需要快速运行的网站。你首先要做的就是获得真正能代表你用户的硬件。 为你服务的用户设定正确的基准。你的技术选择必须基于此,否则你只是在装腔作势。
不过,如果您的目标是廉价手机,我可以告诉您我今天会看什么。
为了获得与我的演示最接近的性能,请尝试Marko。
是的,我现在因为 Marko 的工作而得到报酬,[编辑:不再是了] 但什么技术比相同技术更能匹配我的演示速度?(具体来说,我使用了@marko/rollup
。)
但是,只推荐我雇主的东西就太不厚道了。还有什么,还有什么……如果你的网站不需要 JS 就能运行,那么绝对应该选择静态网站。但对于像电商这样需要一点互动性的东西——嗯,我的 demo 没有运行 JAMstack 是有原因的。
我的要求清单是……
- 流式 HTML。(请参阅第 2 部分了解原因。)
- 最小框架 JS — 至少一半
react
+react-dom
。 - 仅对某些组件进行水合的能力,因此您的用户只需下载实际提供动态功能的JavaScript 。
- 可以在 CDN 边缘服务器中渲染。遗憾的是,对于 JavaScript 以外的语言来说,这很难实现,除非你使用类似Fly.io 的 One Weird Trick之类的方法。
Solid是仅次于 Marko 的亚军;它唯一缺少的要求是部分水合。
Svelte 不支持流式处理,也没有部分 hydration,但它通过其文化来抑制 JS 的使用,解决了过多应用 JS 的问题。如果 Svelte 实现了流式 HTML,我会推荐它。也许有一天会实现。
如果 Preact 有部分 Hydration 和流式处理功能,我也会推荐它;尽管 Preact 的目标并不总是与我的一致,但我无法反驳 Jason Miller 的一致成果。Preact 应该会有与 React 的流式处理和服务器组件等价的功能,对吧?
Remix几乎值得推荐;它的理念是🧑🍳💋。它的渐进式增强方法正是我想要的,从 React 18 开始,它可以流式传输 HTML,而且他们正在做着非常宝贵的工作,成功地说服了React 开发者这些功能的重要性。这类东西让我挥舞着拳头表示赞同:
如果我们能把所有代码从浏览器移到服务器,那不是很棒吗?每次需要访问数据库或调用需要私钥的 API 时,都要编写无服务器函数,这难道不烦人吗?(是的,确实如此)。这些都是 React 服务器组件承诺为我们做的事情,我们当然可以期待它在数据加载方面能做到,但它们对数据变更没有任何作用,如果能把这些代码也从浏览器移出来就太好了。
我们了解到,获取组件是导致最慢用户体验的最快方法(更不用说通常随之而来的所有内容布局转变)。
受到影响的不仅仅是用户体验。开发者的体验变得复杂,因为需要处理各种上下文管道、全局状态管理解决方案(通常只不过是服务器端状态的客户端缓存),并且每个包含数据的组件都需要拥有自己的加载、错误和成功状态。
说真的,Remix 唯一让我不喜欢的地方就是……React。看看这个 perf trace:
当然,主线程总共只阻塞了0.8秒,但我不想每次页面导航都这样。这很好地解释了为什么Remix要逐步增强客户端导航……不过我已经阐述过我的观点了。
理想情况下,Remix 应该允许你使用其他框架,而我会把 Marko 塞进去。他们已经讨论过这种可能性了,所以谁知道呢?
文章来源:https://dev.to/tigt/so-what-c8j