编译器方法提升网页性能: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 + 后端流程并导出源码,让你能在不做大改动的前提下早期测量和比较不同方案。