1 分钟

编译器方法提升网页性能:Rich Harris 的观点

从实用角度看编译器方法如何提升网页性能,对比以运行时为主的框架与编译时输出,并给出一个简单可用的决策框架。

编译器方法提升网页性能:Rich Harris 的观点

为什么有些网页应用即便在好设备上也感觉很慢

用户很少用技术术语来描述性能。他们会说应用感觉很沉重。页面需要很久才显示内容,按钮响应迟滞,像打开菜单、在搜索框输入或切换选项卡这类简单操作会卡顿。

这些症状很常见:首屏加载慢(空白或半渲染的 UI)、交互滞后(点击延迟、生硬滚动)、以及一些本应立即完成的操作后出现长时间的加载指示器,例如保存表单或筛选列表。

很多问题来自运行时成本。通俗地说,就是页面加载后浏览器为了让应用可用必须做的工作:下载更多 JavaScript、解析、执行、构建 UI、附加事件处理器,然后在每次更新时继续做额外工作。即便在快速设备上,浏览器在解析和执行大量 JavaScript 时也有极限,一旦超过就会拖慢体验。

性能问题也常在后期显现。起初应用很小:几屏、数据轻、UI 简单。随着产品成长,市场加入跟踪器、设计加入更复杂的组件、团队增加状态、功能、依赖和个性化。单个变更看起来无害,但总量会累积。

这就是为什么团队开始关注以编译器优先的性能思路。目标通常不是追求完美分数,而是继续交付的同时避免应用每个月都变慢。

运行时为主 vs 编译时:基本思路

大多数前端框架帮你做两件事:构建应用,并让 UI 与数据保持同步。关键差别在于“同步”这件事什么时候发生。

在以运行时为主的框架中,更多工作发生在页面加载后的浏览器里。你发布一个通用的运行时,它能处理许多情况:跟踪变化、决定哪些地方要更新并应用这些更新。这种灵活性对开发很友好,但通常意味着更多需要下载、解析和执行的 JavaScript,影响 UI 可用的时间。

在编译时优化中,更多工作会转移到构建步骤。构建工具会分析你的组件并生成更直接、针对应用的代码,而不是把一大套规则发给浏览器。

一个有用的比喻:

  • 以运行时为主像是发一份说明书和工具箱,再在客户家组装。
  • 编译时像是把零件都裁好并贴好标签,组装就快得多。

大多数实际产品处在两者之间。编译器优先的方法仍然会发布一些运行时代码(路由、数据获取、动画、错误处理)。以运行时为主的框架也会依赖构建时技术(压缩、代码拆分、服务端渲染)来减轻客户端的工作。实务上的问题不是哪边“对”,而是哪种组合更适合你的产品。

Rich Harris 和“框架就是编译器”为什么重要

Rich Harris 是推动编译器优先前端思路的代表性声音之一。他的论点很直接:尽可能把工作提前做完,这样用户下载更少代码,浏览器做更少工作。

动机很务实。许多以运行时为主的框架会发布一个通用引擎:组件逻辑、响应性、差异检查、调度和各种辅助工具,这些都必须适用于任何可能的应用。这种灵活性会消耗字节和 CPU。即使你的 UI 很小,也可能需要为一个很大的运行时付出代价。

编译器方法把模型翻转过来。在构建时,编译器查看你的实际组件并生成它们所需的具体 DOM 更新代码。如果某个标签从不变化,它就变成普通的 HTML。如果只有一个值会变化,构建输出中只包含该值的更新路径。你不再发布一个通用的 UI 机器,而是发布为你的产品量身定制的输出。

这通常带来直接结果:发送给用户的框架代码更少,且每次交互需要做的工作更少。尤其在低端设备上,这种好处更容易显现,因为额外的运行时开销更快就会变成明显的卡顿。

权衡仍然存在:

  • 当编译器无法在构建时理解的动态模式会更难实现。
  • 工具链质量更重要(构建速度、调试体验、源映射)。
  • 生态惯例可能不同,影响招聘与复用。
  • 你可能仍然需要运行时辅助,只是更小、更有针对性。

实用经验法则:如果你的 UI 在构建时大体可知,编译器可以生成紧凑的输出。如果 UI 极其动态或依赖插件,较重的运行时会更容易胜任。

编译时优化在实践中能改进什么

验证真实用户旅程
使用自定义域名在各路由和页面上进行更真实的性能检测。

编译时优化改变工作发生的地点。更多决策在构建阶段完成,留给浏览器的工作更少。

一个显著结果是更少的 JavaScript 被发送。更小的包体减少网络时间、解析时间以及页面能响应交互之前的延迟。在中端手机上,这比许多团队预期的更重要。

编译器还可以生成更直接的 DOM 更新。当构建步骤能看到组件结构时,它可以产生只触及实际变化节点的更新代码,避免在每次交互时经过多层抽象。这会让频繁更新的场景感觉更流畅,尤其是列表、表格和表单。

构建时分析还能加强 tree-shaking 和死代码删除。收益不仅是文件更小,也是浏览器需要加载和执行的代码路径更少。

Hydration(同构页面激活)也是构建时决策可以帮助的领域。Hydration 是把服务端渲染的页面变为可交互的步骤,附加事件处理器并在浏览器重建足够的状态。如果构建能标记哪些部分需要交互、哪些不需要,就可以减少首屏工作量。

作为副作用,编译通常也能改善 CSS 作用域。构建可以重写类名、移除未使用样式并减少跨组件样式污染。这在 UI 增长时能降低意外成本。

想象一个带筛选和大型数据表的仪表盘。编译器优先的方法可以保持初始加载更轻量,在筛选点击后仅更新改变的单元格,并避免对永远不会交互的页面部分做 hydration。

在哪些情况下以运行时为主的框架仍然有意义

更大的运行时并非自动就是坏事。它常常换来灵活性:运行时才能决定的模式、丰富的第三方组件、经过多年打磨的工作流。

当 UI 规则经常变化时,运行时为主的框架很有优势。如果你需要复杂路由、嵌套布局、富表单和深层状态模型,成熟的运行时会像一个安全网。

运行时是在为便利付费

当你希望框架在应用运行时而非仅在构建时处理大量工作时,运行时就很有用。这会让团队日常更快,即便它增加了开销。

常见收益包括成熟的生态、大量熟悉的状态与数据获取模式、强大的开发者工具、便捷的插件扩展方式,以及从常见人才库招聘时更顺畅的入职体验。

团队熟悉度既是成本也是收益。一个稍慢但团队能自信交付的框架,往往胜过一个更快但需要再培训、更严格纪律或自定义工具链才能避免坑的方案。

当运行时开销不是瓶颈时

许多“慢应用”的抱怨并非由框架运行时造成。如果页面在等待慢 API、超大图片、太多字体或第三方脚本,换框架并不会解决核心问题。

在登录保护的内部管理仪表盘里,通常即便使用较大的运行时也能获得良好体验,因为用户使用的是高配设备,工作负载由表格和后端查询主导。

“足够快”在早期是正确的目标。如果你还在验证产品价值,就保持高迭代速度,设定基本预算,只有在有证据表明需要时才引入编译器优先的复杂度。

性能与迭代速度:团队真正的权衡

在生产环境中测量
部署并托管你的应用,以在真实设备上验证首屏加载和交互体验。

迭代速度是反馈时间:开发者改动一个界面、运行它、看到问题并修复所需的时间。保持这个循环短能让团队更频繁地交付和学习。这就是为什么以运行时为主的框架在早期看起来更高产:熟悉的模式、快速的结果、很多内建行为。

当性能工作做得过早或过宽时,会放慢这个循环。如果每个合并请求都变成微优化的争论,团队就不敢冒险。如果在你还不知道产品是什么样子时就搭建复杂的流水线,人们会把时间花在与工具斗争上,而不是与用户沟通。

技巧在于就“足够好”达成共识,并在这个范围内迭代。性能预算给你这个范围。目标不是追求完美分数,而是设定限制以在保持开发速度的同时保护用户体验。

一个实用的预算例子:

  • 在中端手机上首屏在几秒内可用。
  • 每个路由的 JavaScript 保持在团队同意的上限内。
  • 关键交互(搜索、加入购物车、保存)响应迅速。
  • 单个功能不能添加超过小幅度的 JS 或 CSS。

如果你忽视性能,通常会在后期付出代价。一旦产品增长,变慢就和架构决策绑在一起,而非小幅调整能解决。后期重写可能意味着冻结功能、重培训团队并打破原本可用的工作流。

编译器优先的工具链可以改变这种权衡:你可能接受稍长的构建时间,但能减少每次用户访问和每台设备上要做的工作量。

在产品验证后重新评估预算。早期保护基本体验;随着流量和收入增长,收紧预算并把投资放在能影响真实指标的地方,而不是为了面子工程。

常见问题

为什么我的网页应用在设备很快的情况下仍然感觉很慢?

大多数情况下并不是单纯的网络问题,而是运行时成本:浏览器下载、解析并执行 JavaScript,构建 UI,并在每次更新时做额外工作。

因此即便在性能不错的笔记本上,当 JavaScript 工作量变大时,应用也会显得“沉重”。

以运行时为主的框架和编译时优化有什么区别?

目标是相同的(让客户端做更少工作),但机制不同。

  • 以运行时为主: 向浏览器发送一个通用引擎,在客户端跟踪变化并决定如何更新。
  • 以编译时优化: 在构建阶段生成更直接、针对应用的更新代码,让浏览器执行更少的逻辑。
“框架就是编译器”这句话到底是什么意思?

意思是框架能在构建时分析你的组件,并输出针对你应用的代码,而不是发布一个庞大的通用 UI 引擎。

实际好处通常是更小的包体积和在交互(点击、输入、滚动)时更少的 CPU 开销。

我应该跟踪哪些性能指标而不被弄糟?

从小处开始:

  • 首屏可用时间(不只是“页面加载完成”)
  • 交互延迟(点击/触摸感觉是否卡顿)
  • 每个路由的 JavaScript 大小(浏览器需要下载与解析的量)
  • API 延迟(后端响应时间)

固定在代表性设备上测量同一个用户流程,这样每次构建都能比较结果。

更换框架能解决慢应用问题吗?

有可能,但要先确认瓶颈在哪里。如果你的应用等待慢 API、超大图片、太多字体或第三方脚本,换框架并不能解决这些核心问题。

把框架选择当成众多杠杆之一:先确认时间消耗在网络、JavaScript CPU、渲染还是后端,然后再决定是否更换框架。

什么时候以运行时为主的框架仍然是最合理的选择?

当你需要灵活性和快速迭代时,较重的运行时仍然有意义:

  • 大量第三方组件
  • 复杂的路由与布局模式
  • 面向插件的扩展点
  • 团队已熟悉该生态

如果运行时不是瓶颈,它带来的便利往往值得额外的字节数。

我该如何在编译器优先、运行时优先或混合方案之间做决定?

一个简单默认策略:

  • 编译器优先:适用于必须在中端手机上快速加载和响应的路由(着陆页、引导、关键流程)。
  • 运行时优先:适用于需要最大灵活性的场景(复杂的仪表盘、内部工具)。

混合策略通常效果最佳,但要把边界写下来,避免不同假设混在一起导致难以调试的问题。

真实团队的性能预算应该是什么样的?

给团队一个既能保护体验又不阻碍交付的预算。例如:

  • 中端手机上首屏在几秒内可用
  • 每个路由的 JS 大小有上限
  • 关键操作(搜索、过滤、保存)响应迅速
  • 单个功能不能增加超过小幅度的 JS/CSS

预算是护栏,不是追求完美分数的比赛。

什么是 hydration,为什么它会让首屏感觉变慢?

Hydration 是把服务器渲染的页面变成可交互状态的过程,浏览器会附加事件处理器并在客户端重建必要状态。

如果你对页面做了大量 hydration,即便 HTML 很快渲染,首屏也会感觉慢。构建时的工具有时可以标记哪些部分需要交互,从而减少首屏工作量。

在决定框架前,我应该在薄片原型里包含什么?

一个合格的“薄片”应该包含真实的复杂点:

  • 身份验证/登录
  • 真实数据和最慢的 API 调用
  • 你最慢的页面(通常是表格、表单或仪表盘)
  • 一个用户重复执行的关键操作(过滤、保存、搜索)

如果你在做薄片原型时需要快速上手,Koder.ai 可以通过聊天帮助你构建 Web + 后端流程并导出源码,让你能在不做大改动的前提下早期测量和比较不同方案。

Related posts