征服 JavaScript Hydration

2025-05-28

征服 JavaScript Hydration

对于一篇文章来说,这标题真是雄心勃勃。总的来说,这是一个雄心勃勃的目标。Hydration,即在服务器渲染之后将 JavaScript 交互功能重新扩展到应用程序的过程,在过去几年中一直被认为是 JavaScript 框架面临的最具挑战性的问题。

尽管我们在网络上的服务器渲染方面投入了大量精力,但我们仍然没有找到一个能够平衡开发人员成本和最终用户成本的普遍好的解决方案。

无论我们如何优化服务器渲染,水合问题始终萦绕在我们心头。JavaScript 需要在页面初始化时运行,这使得我们的首次内容绘制 (First Contentful Paints) 具有欺骗性,无论我们如何逐步增强,它都会增加首次输入的延迟,而且随着 Web 应用程序的规模和复杂程度不断增加,这种情况只会越来越糟。

许多人致力于解决这个问题,为各种项目做出贡献,并各自权衡利弊。通过他们,我们看到了难题的各个部分逐渐拼凑起来。最终,我们即将看到水合问题得到解决。


寻找可恢复性

那是2021年3月。几个月来,我们一直在思考如何为Marko的下一个版本解决异步数据获取问题,但最终决定先不做。我们已经实现了大部分跨模板分析,这是一种为每个模块生成元数据的机制,任何父模块都可以使用它来准确了解传递给它的数据将如何使用。我们手工编写的基准测试表明,这种方法性能非常出色。现在是时候构建编译了。

但 Michael Rawlings(@mlrawlings)始终无法摆脱这种挥之不去的疑虑:我们做错了。他不想依赖缓存来避免在 hydration 过程中不必要的数据获取,所以他建议我们干脆不要重新运行任何组件,不要执行任何我们已经在服务器上运行的响应式表达式。但做到这一点并不容易

最初的答案来自Svelte。Svelte组件将所有状态插入提升的作用域,并将所有表达式排序到适当的生命周期中,以避免需要反应式运行时。

那么,如果我们可以跨模板进行分析,何不更进一步呢?正如Solid所证明的那样,当组件不再是变更的单位时,我们可以释放出令人难以置信的性能。而将这项工作分解为 Hydration 的好处可能会更加显著。

只要这个作用域全局可用,我们就可以把组件拆分成多个部分,而无需用闭包将它们捆绑在一起。每个部分都可以独立地进行 tree-shaking 和执行。我们需要做的就是在渲染时从服务器序列化这个作用域,并注册任何仅供浏览器使用的代码,以便在 hydration 时立即运行。

我在

事实证明,我们并非唯一得出类似结论的人。几个月后,Angular 的创始人 Misko Hevery(@mhevery )在他的框架Qwik中向世界揭示了这种方法。而且他做得比我们更好。他为这个想法起了个名字。

可恢复性。


消除水分?

图片描述

时间快进到2022年3月6日。这两个项目都朝着这个方向努力了大约一年。那一周,我负责<effect>给Marko 6添加标签。没错,大家都喜欢这个钩子。

效果很有趣,因为它们存在于用户空间,并且它们的行为很奇特,因为它们只在浏览器中运行,因为它们是你与 DOM 交互的机会。而且你往往希望它们在其他所有操作之后运行,这意味着不可避免地需要运行一些辅助队列。

你可以在服务器上使用 JSDOM,但我不建议这样做。使用模拟 DOM 会严重降低服务器渲染速度,而你完全可以只使用字符串(我们再次回顾如何编写最快的 JavaScript UI 框架)。

因此,周一早上开会时,我们正在苦苦思索是否需要增加运行时间来处理日程安排,这时 Dylan Piercey 提出了一个显而易见的问题。

除了效果之外,在水合时还需要在浏览器中运行其他任何东西吗?

我们有事件注册,但它并没有起到什么作用,因为所有事件都委托给了全局处理程序。难道我们不能跳过在未运行效果的模板上创建水合物导出的步骤吗?如果最终用户根本没有注册任何效果,除了运行一个小脚本来引导这些全局事件之外,我们还需要运行其他什么吗?

当他和迈克尔继续研究这对编译意味着什么时,我开始对我们注意到瓶颈的各种反应式排队机制进行一些性能基准测试。

Misko 给我发了这条消息:
图片描述

时机完美。

他完全正确。有些人可能想争论细节,这确实有道理。但这或多或少是在定义上吹毛求疵。我们一年来一直在关注这些问题,却不知何故完全忽略了标题:


水合作用已解决

图片描述

这里有一些细节需要解决。但目前已经达到了这样的程度:在 hydration 阶段,浏览器端只运行浏览器专属代码。除了简单的引导程序加载全局事件处理程序外,无需运行任何其他代码。无需重新运行组件。否则,无需执行任何特定于组件的代码。只需从服务器中断的地方“恢复”即可。

这涵盖了故事的执行部分。数据序列化的问题仍然存在,因为可恢复性可能会增加序列化难度。Marko 正在开发的解决方案利用了响应式图,并考虑到页面根仅在服务器端渲染,从而自动检测哪些数据需要序列化。

可恢复性也与我们在浏览器中加载代码的时间无关。Qwik 一直在开发一种精细的方法,以便逐步加载每次交互所需的代码。其目的是利用使用情况分析来优化未来的捆绑。

所以不同的解决方案之间肯定会存在差异,也有一些细节需要协调。但归根结底,我们目前已经看到了两种解决方案,未来还会有更多。

这仅仅是个开始。随着 Hydration 可能成为过去,下一代 Web 开发即将开启。


如果你想了解它的最新动态,可以看看Qwik。它使用 JSX 和响应式原语,让高性能应用的开发变得简单。以下是我最近对 ​​Misko 的采访:

如果你想看看我的工作成果,你得再等一段时间。我们期待着今年夏天Marko 6 进入 Beta 测试阶段时发布我们的第一个版本。

文章来源:https://dev.to/this-is-learning/conquering-javascript-Hydration-a9f
PREV
♻️ 立即删除未使用的 node_modules 并享受一些可用空间!
NEXT
不使用 JavaScript 的客户端路由