JavaScript 框架的 AI 成本:Opus 5.5 多框架基准评测
The AI cost of JavaScript frameworks
作者让 Opus 5.5 使用不同 JavaScript 框架与元框架构建同一款 Pokémon 应用,并比较成本、耗时、代码量与 Lighthouse 表现。
作者用统一应用规格比较多款 JavaScript 框架方案的构建成本、耗时、代码量和 Lighthouse 表现,为智能体编程选型提供横向数据。
我在 Web 开发社区中最反感的一点是,我们做出的许多决定都以感觉为准。框架“快如闪电”,工具会“让你的开发效率飙升”,正确的抽象能同时惠及开发者和用户(DX 与 UX 二合一!)。所有这些激动人心的夸耀通常都没有任何证据。Alex Russell 曾称之为“前端失去的十年”,Baldur Bjarnason 曾称编程是一种流行文化,而我则一直试图在博客中用事实和基准测试来抵制这种趋势(至少我已经尽力了)。
很长一段时间以来,我们社区对证据的排斥似乎源于追求更高 DX。换句话说,开发者只是喜欢在 React 中输入 JSX 时的手感,而推广 React 或相关工具的论点(“虚拟 DOM 很快!”)在很大程度上都是事后为最初直觉找的合理化理由。(Jonathan Haidt很可能会认同这一评价。)
但是在智能体时代,开发者不再亲手编写代码,因此不再那么受 DX 制约,我仍然看到大家继续凭感觉行事(这不只是“凭感觉编程”的意思)。前端被重写,只因为某个框架“更适合智能体”。(有人想到问一问智能体吗?)最终得到的代码“更快”,智能体犯的“错误更少”,等等。这些说法 Again 往往毫无证据。每当我读到其中一种说法时,都会想:“基准测试在哪里?”
不过,我好奇了起来,于是写了一个基准测试。这个基准测试的结构相当简单明了:
- 使用PokéAPI设计一个小型但并不简单的宝可梦应用(那些一直关注我的 工作的人不会感到意外)
- 指示智能体使用特定的 JavaScript 框架或元框架(例如 Next.js、Nuxt、SvelteKit 等)构建该应用
- 根据规范符合性测试套件测试输出结果(以防止智能体作弊)
- 从开发时间、消耗的 token 数量、代码行数,以及Lighthouse中的性能和无障碍评分等方面评估结果
这个应用相当简单:你会看到一个宝可梦列表,其中包含它们的属性、招式以及来自各种游戏的数据。你可以搜索、筛选等等:

首先,我得说明几点注意事项:这不是一项完美的实验。这个应用只代表一个正模标本——它是一个相对以内容为主、交互性较少的网站,更接近 Wikipedia,而不是 Figma、GMail 等。尽管如此,我总得选择某个应用来构建,而这个应用至少从高层角度来看容易理解,规格制定和测试也相对直接。
为了尽量让各个框架或元框架站在同一起跑线上,我让它们各自使用该工具的官方 MCP 或 skills。我不想招来这样的指责:“这个框架在训练数据中的体现不够充分,所以这是我们构建 MCP 的原因,我们希望人们使用它”等等。我也没有过度规定具体应采用的架构(例如 SSR 还是 CSR),也没有要求该应用必须尽可能高性能。我只是以高努力档把规格说明交给了 Opus 5.5,并告诉它使用指定框架的最新版本。每个智能体都在沙箱中运行,而且不知道它之后会在 Lighthouse 的性能和无障碍方面接受测试,尽管它已经获得了符合性测试套件。
另外,我也没有强制规定任何 CSS 方法论,只要求避免使用 Tailwind、Bootstrap 或其他组件框架,并基于一组规范令牌进行设计,以确保视觉一致性。我不想让这次测试全都围绕 CSS,而是聚焦于 JS 框架、CSR 与 SSR、SPA 与 MPA 等。最终生成的应用是基于无障碍树而非原始 DOM 的符合性测试套件进行测试,以避免强制规定任何特定的 HTML 结构——有一个框架使用了依赖 Shadow DOM 的 Web Components,因此必须这样做。
我从这次实验中学到的一件事是,我所衡量的一切都具有很强的随机性。LLM 是随机词生成器,所以有时你会得到一个较低的骰子点数,代理就会偏离到某个奇怪的方向,浪费大量 token,这并不是框架或其工具的过错。Lighthouse 本身也可能给出不一致的结果:最大内容绘制(LCP)可能认定某张图片是“最大”元素,下次运行时却又选择另一张。要进行完全公平的实验很难,但运行多次迭代确实有所帮助,我也努力如实反映这些差异。
闲话扯完,现在来看结果。
结果
我将从多个维度进行拆分:构建成本、质量和可维护性。对于每个框架/元框架,我取了 5 次运行的中位数:也就是说,我用同一个框架构建了 5 次相同的应用。(是的,如今利用编程代理可以做这种有点疯狂的事情。)
例外是 Angular,它有两次未能创建可正常运行的应用,因此我改用 3 次迭代。(下文会对此作出解释。)
成本
| 框架 | 成本 ($) |
|---|---|
| Hono+htmx | $2.67 (2.23–3.19) |
| Astro | $2.76 (2.16–3.63) |
| Nuxt | $2.82 (2.39–4.25) |
| Vanilla | $2.98 (2.24–3.37) |
| Next.js | $3.14 (2.58–4.13) |
| Qwik | $3.27 (2.35–3.80) |
| TanStack Start | $3.54 (3.11–3.96) |
| SolidStart | $3.79 (2.84–4.81) |
| React Router | $3.81 (2.82–3.97) |
| SvelteKit | $4.28 (3.59–4.65) |
| Gracile+Lit | $4.65 (3.92–5.07) |
| Angular | $4.97 (3.19–6.82) |
3-5 次运行的中位数。
注意,这些价格直接引自 Claude Code 的 API 定价,并不实际代表我花了多少钱。我使用的是 Max 套餐,因此这只是我每月 $100/month 总费用的一部分。
还要注意波动幅度:React Router 的中位数大约比 Hono+htmx 高 $1,但 React Router 最便宜的版本($2.82)仍比 Hono+htmx 最贵的版本($3.19)便宜。不过,一些可靠的规律确实显现出来了。
最明显的是,SvelteKit、Gracile+Lit 和 Angular 的构建成本高于其他框架。对于 SvelteKit,这可能是因为每次运行时,代理都会阅读 SvelteKit v3 迁移指南,很可能是因为它太新了,尚未进入 Opus 5.5 的训练数据。Gracile 也是如此,代理们不太了解这个框架,因此要么克隆 Gracile 仓库,要么从 node_modules 阅读其编译后的源代码。对于 Angular,这似乎取决于代码总大小(见下文)。
我还应该说明,并非每个代理都使用了 MCP/技能/Claude Code 插件,也不是每个框架都配备了此类工具:
点击查看 AI 工具使用情况
| 框架 | 已安装 | 智能体是否使用? |
|---|---|---|
| Angular | Angular MCP 和 Angular 智能体技能 | MCP 在 3/5 次运行中使用,技能工具在 4/5 次运行中使用,技能文件在 2/5 次运行中使用 |
| Astro | Astro MCP 服务器 | MCP 在 2/5 次运行中使用 |
| Gracile+Lit | N/A | N/A |
| Hono+htmx | Hono 技能 | 技能工具在 5/5 次运行中使用 |
| Next.js | next-devtools-mcp 和 next-dev-loop 技能 | 技能文件在 1/5 次运行中使用,AGENTS.md 在 2/5 次运行中使用 |
| Nuxt | Nuxt MCP | N/A |
| Qwik | N/A | N/A |
| React Router | React Router 技能 | 技能工具在 5/5 次运行中使用,技能文件在 5/5 次运行中使用 |
| SolidStart | N/A | N/A |
| SvelteKit | Svelte Claude Code 插件 | 5/5 次运行中均使用 MCP |
| TanStack Start | N/A | 2/5 次运行中发现技能文件 |
| Vanilla | N/A | N/A |
例如,TanStack Start 的智能体只有 2/5 次运行发现了它的技能文件(这些文件随项目的 node_modules 一同提供)。对于 Nuxt,智能体从未使用 MCP。对于 Next.js,智能体只有 1/5 次运行读取了技能文件,2/5 次运行读取了 AGENTS.md。结果分布范围很广,这再次指向 LLM 的随机性本质。不过,我仍认为这是一项有效的实验,因为我怀疑很少有 vibe 编程者会用全大写字母对智能体大喊大叫,让它们使用 MCP(而且很可能有不少人干脆完全不用它)。
另外,如果你想知道我是怎么想到 Hono+htmx 的:本质上,我想以某种方式体现 htmx,而 Hono 看起来是个不错的搭配(即充当 Rails/Django 等框架的 JavaScript 替代方案)。至于 Gracile+Lit,我想通过其标杆框架 Lit 来体现 Web 组件,而当时我对 Gracile 的了解仅限于它是一个元框架。如果有更合适的候选方案,那就很适合用于后续实验。
对于“Vanilla”,请注意,智能体被告知要使用“原生 JavaScript 或 TypeScript 配合 Vite,不使用 UI 框架”。令人意外的是,这种方式的表现不及多个框架,这似乎说明,拥有某种框架可能比没有框架更好(或者至少比直接使用 Vite 更好)。选择 Vite,是因为它是许多框架的基础,并且代表了相当标准、与具体框架无关的 JavaScript 基础设施。
总耗时和 token 数量与成本相当吻合:
| 框架 | 耗时(分钟) |
|---|---|
| Astro | 8.2 min (6.2–9.7) |
| Hono+htmx | 8.6 min (5.9–9.6) |
| Nuxt | 9.0 min (8.0–17.6) |
| Vanilla | 10.1 min (6.9–18.6) |
| TanStack Start | 10.3 min (8.3–13.9) |
| Qwik | 10.7 min (7.1–12.9) |
| SvelteKit | 10.8 min (8.6–13.8) |
| Next.js | 10.9 min (8.3–11.9) |
| SolidStart | 11.2 min (8.2–15.9) |
| Gracile+Lit | 12.1 min (10.8–12.9) |
| React Router | 12.5 min (8.4–13.6) |
| Angular | 17.2 min (9.8–23.7) |
从智能体视角测得的实际耗时。3-5 次运行的中位数。
| 框架 | Token 数(M) |
|---|---|
| Nuxt | 3.85 M (3.16–8.18) |
| Astro | 4.13 M (3.03–7.57) |
| Hono+htmx | 4.26 M (3.40–5.61) |
| Next.js | 4.99 M (3.54–8.82) |
| Vanilla | 5.00 M (3.44–6.32) |
| Qwik | 5.64 M (3.41–7.03) |
| TanStack Start | 7.07 M (5.85–7.99) |
| SolidStart | 7.13 M (5.02–10.07) |
| React Router | 7.58 M (4.55–8.18) |
| SvelteKit | 8.53 M (6.79–9.29) |
| Gracile+Lit | 9.04 M (7.36–11.10) |
| Angular | 9.76 M (4.92–14.94) |
缓存读取、未缓存的输入和输出。3-5 次运行的中位数。
顺便说一下,token 数量是由 Claude Code 报告的。持续时间只是总挂钟时间。
质量
当然,因为我认为最终效果很重要,我还使用 Lighthouse 对这些智能体进行了测试。作为软件开发者,我认为我们不仅要以低成本构建东西,还要确保它们性能出色且具备良好的可访问性。
首先,我应该说明关于我的基准测试的两点注意事项:
- 这款 Pokémon 应用规模很小,并不能代表大型、复杂的真实世界应用。
- Lighthouse 和 Core Web Vitals 总体上对 SPA 不利,因此 MPA 框架几乎必然会在总阻塞时间 (TBT)、JavaScript 字节数等指标上胜出。我考虑过添加一些应用内性能指标,但后来放弃了,因为这会增加基准测试的复杂性和波动性,而且这些指标将由我自行编造,而不是采用行业标准。
为简单起见,我只报告首页的分数。还有其他页面,但它们的得分相当接近,或者在 LCP 等指标上出现剧烈波动,因此将它们纳入只会让事情更加复杂。
简而言之:Lighthouse 的性能、可访问性和 CLS (累积布局偏移) 分数几乎已经饱和,而且不同框架之间的差异不大:
| 框架 | CLS | 性能 | 可访问性 |
|---|---|---|---|
| Angular | 0.000 | 98 | 100 |
| Astro | 0.000 | 100 | 100 |
| Gracile+Lit | 0.000 | 100 | 100 |
| Hono+htmx | 0.000 | 100 | 100 |
| Next.js | 0.000 | 99 | 100 |
| Nuxt | 0.000 | 100 | 100 |
| Qwik | 0.000 | 100 | 100 |
| React Router | 0.000 | 100 | 100 |
| SolidStart | 0.000 | 100 | 100 |
| SvelteKit | 0.000 | 100 | 100 |
| TanStack Start | 0.000 | 99 | 100 |
| Vanilla | 0.111 | 97 | 100 |
因此,要获得一些更有意思的数据,我们需要查看其他指标。最可靠的指标是传输的 JavaScript 字节数,它在很大程度上反映了客户端框架的大小:
| 框架 | JS (KiB) |
|---|---|
| Astro | 0 KiB (0–1) |
| Vanilla | 7 KiB (6–7) |
| Gracile+Lit | 8 KiB (1–13) |
| Hono+htmx | 18 KiB (18–18) |
| SvelteKit | 36 KiB (35–36) |
| SolidStart | 40 KiB (38–40) |
| Qwik | 40 KiB (40–41) |
| Nuxt | 84 KiB (83–85) |
| Angular | 93 KiB (81–102) |
| React Router | 115 KiB (115–119) |
| TanStack Start | 118 KiB (112–119) |
| Next.js | 142 KiB (136–143) |
由 Lighthouse 报告。3-5 个应用的中位数。
总阻塞时间 (TBT) 很有意思,因为它并非完全是 JavaScript 字节数的函数;其中一些应用在主线程上耗费的时间比其他应用更长。尽管如此,这些数字都相当小:
| 框架 | TBT (ms) |
|---|---|
| Astro | 0 ms (0–0) |
| Gracile+Lit | 0 ms (0–0) |
| Hono+htmx | 0 ms (0–0) |
| SvelteKit | 0 ms (0–1) |
| Vanilla | 0 ms (0–0) |
| TanStack Start | 1 ms (0–36) |
| Qwik | 3 ms (0–30) |
| SolidStart | 11 ms (0–12) |
| React Router | 17 ms (0–52) |
| Nuxt | 40 ms (14–54) |
| Next.js | 71 ms (66–121) |
| Angular | 103 ms (93–123) |
按照 Lighthouse 的报告,采用模拟移动端限速。每个应用进行 5 次 Lighthouse 运行并取中位数,然后再对 3-5 个应用取中位数。
由于前述指标本身存在的问题,最大内容绘制(LCP)的波动性很高。不过,正如预期,它总体上确实会惩罚 SPA,并奖励 MPA:
| 框架 | LCP (ms) |
|---|---|
| Astro | 810 ms (792–1662) |
| Gracile+Lit | 956 ms (796–980) |
| Hono+htmx | 978 ms (977–1662) |
| Qwik | 979 ms (954–1278) |
| SvelteKit | 1130 ms (1129–1131) |
| SolidStart | 1204 ms (1203–1278) |
| Vanilla | 1667 ms (764–1676) |
| Nuxt | 1727 ms (1427–1844) |
| React Router | 1729 ms (1729–1770) |
| Angular | 1954 ms (1351–1955) |
| TanStack Start | 2028 ms (1805–2104) |
| Next.js | 2090 ms (1028–2187) |
按照 Lighthouse 的报告,采用模拟移动端限速。每个应用进行 5 次 Lighthouse 运行并取中位数,然后再对 3-5 个应用取中位数。
请注意,Next.js 数字的巨大波动可以解释为:一个应用避免了延迟加载图片,而另外四个没有这么做。Astro 和 Vanilla 也出现了类似的波动,似乎是由优先级处理或 LCP 的特殊问题造成的。
不过总体而言,该指标往往会奖励能够快速借助 HTML 完成绘制的 MPA,而不是那些要等客户端 JavaScript 运行后才绘制的 SPA(期间还可能经历多次受到限速影响的往返请求,以获取 Pokémon 数据),这并不令人意外。忽略方差,这里的 LCP 主要取决于 JavaScript 包体积。(Qwik 是一个值得注意的例外:从技术上说,它是一个使用客户端路由的 SPA,但其得分与 MPA 框架相当。)
不过请注意,一些框架在更侧重客户端还是服务器方面存在差异,甚至是否使用客户端框架也不尽相同。例如,Gracile 智能体甚至在两次运行中都没有使用 Lit。对于“Vanilla”,智能体拥有完全的自由裁量权(唯一限制是被要求使用 Vite),但仍然连续 5 次都选择构建 SPA。
SSR 与 CSR 的取舍也是 Angular 出现问题的部分原因:在 2 次运行中,智能体构建了一个会拒绝非 localhost 请求的 SSR 应用(尽管说明中明确指出该应用将位于反向代理之后)。我认为这确实属于未遵守规范,因此决定不重新运行这 2 次失败的迭代。
可维护性
衡量可维护性的方法很多(包括循环复杂度之类的复杂指标),但我决定保持简单,报告一个久经考验的指标:代码总行数。
| 框架 | 代码行数 |
|---|---|
| Hono+htmx | 1370 (1264–1489) |
| Nuxt | 1415 (1382–1458) |
| Astro | 1461 (1379–1503) |
| Vanilla | 1492 (1437–1742) |
| Next.js | 1539 (1452–1627) |
| React Router | 1564 (1421–1621) |
| SvelteKit | 1611 (1503–1816) |
| SolidStart | 1630 (1528–1894) |
| TanStack Start | 1697 (1506–1771) |
| Gracile+Lit | 1717 (1412–1748) |
| Qwik | 1744 (1646–1827) |
| Angular | 2009 (2003–2051) |
源文件中非空行的数量。3-5 个应用的中位数。
对我来说,这是其中较有意思的图表之一。从某种意义上说,框架的核心目的就在于减少你需要维护的代码量。用 Rich Harris 那句令人难忘的话说:
“框架不是用来组织代码的工具;它们是用来组织思维的工具。”
– Rich Harris,“重新思考响应式”
在智能体时代,这番话更显切中要害,因为代码规模最终会影响所需的 token 数量,包括未来编辑时所需的数量,这意味着它会直接影响长期维护代码库的实际成本。这并不意味着简练是唯一重要的价值,但看到 Hono+htmx 仅用 ~70% 的代码就能大致完成 Angular 所能做的事情(而且速度也更快),确实很有意思。
结论
你可以通过许多方式改进这个基准测试。例如,可以要求智能体使用 Service Workers 进行离线缓存,或优化宝可梦图片文件,或使用懒加载、fetchpriority、Speculation Rules API 等。尽管我了解许多这类性能优化技巧,但我故意没有引导智能体采用其中任何一种,因为典型的凭感觉编程者不会这么做。
另一方面,只要预先将所有 HTML 文件生成静态资源,这个基准测试就完全可能被作弊绕过(宝可梦的数量终究有限)。我在说明中明确禁止了这种做法,并在测试工具中将其设为一项明确的检查,因为我认为这违背了基准测试的初衷。(我的目标是模拟大量可能动态化的数据,这比纯静态网站更具挑战性。)
你也可以通过增加运行次数、使用不同的编程智能体、尝试不同的投入程度、使用不同的框架或框架的不同版本、分别测试有无 MCP/skills、在 Chromium 之外的更多浏览器中测试、构建不同类型的应用等,来改进我运行这个基准测试的方式。我没有做这些事,主要是为了节省时间。不过,如果有人想自行运行这个基准测试或对它进行衍生开发,它就在 GitHub 上,所以我鼓励你放手去试。我很乐意在这里公布你的结果,以及你可能发现的任何错误或缺陷。
我写这篇文章的目标,并不是要为“我是个凭感觉编程者,该选哪个框架?”这个问题打造一个包打天下的基准测试。本文的重点是证明,在 AI 编程时代,我们仍然可以基于证据而非感觉来做决定。这个基准测试本身大部分也是凭感觉编程完成的,尽管我确实审查过代码,所以如果存在错误,还请将其视为我的问题。我也完全乐见竞争框架尝试在我这里所做的基础上取得更好的成绩。
我也不是想抹黑任何特定框架。在运行基准测试之前,我不知道哪个框架会“获胜”,尽管我确实怀疑 MPA 的表现会优于 SPA(这一点我在本文中一直毫不避讳地承认)。你当然可以把应用做成复杂的仪表板、电子游戏、效率工具等,从而编写一个更偏向 SPA 的基准测试。你也可以选择不同的衡量指标:Lighthouse 和 Core Web Vitals 对 SPA 相当苛刻,因此可以改为衡量应用内的交互性。
我还确信,框架作者 themselves 可以改进各自的框架或 AI 工具,并在这个基准测试中取得更好的表现。例如,我强烈怀疑当前这批 MCP 和技能可能弊大于利。因此,如果这篇文章成了这些框架性能表现的最终定论,我会非常失望。我敢肯定,也有一些新兴框架正迫不及待地想在这样的基准测试中取得好成绩。
至少但愿这篇文章能促使你在这个古怪的新“氛围编程”时代,进一步审视自己对 JavaScript 框架的选择。你选择技术栈时真的以证据为依据吗?还是只是沿用自己已经熟悉的东西?这当然可以理解,尤其是当你仍在亲自阅读、审查和调试代码的情况下。但如果你已经基本全程放手不管,那么对于当年像动物一样逐行编写代码时所作的选择,你如今究竟还有多少依恋?如果你正在重新评估构建软件的整体方式,也许也值得重新审视自己在 npm install 之后输入的东西?随口说说。
就个人而言,除了坚信我们应当努力为用户打造尽可能好的体验之外,我对上文评估过的这些框架没有任何偏爱。我认识并尊重上述许多框架的作者,也知道他们正在为我所倡导的同一价值观不懈努力。我只是希望推动整个社区朝更好的方向发展,打造更优质、更快、更易访问的框架,为下一代 Web 应用提供动力。
补充说明
测试中使用的框架版本(注意,Qwik 在测试过程中发布了一个补丁版本):
| 框架 | 版本 |
|---|---|
| Angular | @angular/core 22.2.3 |
| Astro | astro 7.4.2 |
| Gracile+Lit | @gracile/gracile 0.11.0, lit 3.3.3 |
| Hono+htmx | hono 4.13.14, htmx.org 2.0.11 |
| Next.js | next 16.4.3, react 19.3.1 |
| Nuxt | nuxt 4.6.2, vue 3.5.44 |
| Qwik | @builder.io/qwik-city 1.20.2(第 1 次迭代),1.20.3(第 2、3、4、5 次迭代),@builder.io/qwik 1.20.2(第 1 次迭代),1.20.3(第 2、3、4、5 次迭代) |
| React Router | react-router 8.5.0, react 19.3.1 |
| SolidStart | @solidjs/start 2.1.0, solid-js 1.9.17 |
| SvelteKit | @sveltejs/kit 3.1.0, svelte 5.57.2 |
| TanStack Start | @tanstack/react-start 1.169.4, react 19.3.1 |
| Vanilla | vite 8.3.5 |
你可以在联邦宇宙上发表评论。
来源:Hacker News · nolanlawson.com