JavaScript 框架的 AI 成本

同一个宝可梦应用,12 个框架各让 agent 写 5 遍,结果 Hono+htmx 最便宜,Angular 最贵,代码量差了七成,Lighthouse 分数却几乎一样。

中文
复制
一张横向条形图,标题为 Cost per run, in US dollars,列出 12 个 JavaScript 框架的单次构建成本,从 Hono+htmx 的 $2.67 到 Angular 的 $4.97,每根条形都带误差线

我对 Web 开发社区的一大不满是,我们做出的许多决定都凭感觉。框架“快得飞起”,工具能给开发“超级充电”,选对抽象层对开发者和用户都有好处(DX 和 UX 一举两得!)。这些吹嘘通常毫无证据。Alex Russell 把这种现象称为“前端失去的十年”,Baldur Bjarnason 把编程称为一种流行文化,而我一直在博客里用事实和基准测试来对抗这股风气(至少在我状态好的时候)。

很长一段时间里,社区对证据的过敏似乎源于对更高 DX 的渴望。换句话说,开发者就是喜欢在 React 里敲 JSX 的那种感觉,而支持 React 或相关工具的说法(“虚拟 DOM 很快!”)多半是事后合理化,用来为最初的直觉找理由。(Jonathan Haidt大概会同意这个判断。)

但到了 agent 时代,开发者不再手敲代码,受 DX 的约束也随之减少,我却看到感觉依然主导一切(而且不只是“vibe-coding”意义上的)。前端被重写,是因为某个框架“对 agent 更友好”。(有人想过问问 agent 自己吗?)重写后的代码“更快”,agent 犯的“错误更少”,诸如此类。这些说法同样往往没有证据。每次读到这类叙述,我都会想:“基准测试在哪?”

我很好奇,于是写了一个基准测试。它的结构很简单。

  • 用 PokéAPI 设计一个小巧但不简单的宝可梦应用(关注过我的作品的人不会感到意外)
  • 指示 agent 用某个特定的 JavaScript 框架/元框架(比如 Next.js、Nuxt、SvelteKit 等)构建这个应用
  • 用一套规范符合性测试来检验产出(防止 agent 作弊)
  • 从开发时间、消耗的 token 数、代码行数,以及 Lighthouse 的性能和无障碍评分这几个维度评估结果

这个应用相当简单。你有一份宝可梦列表,包含它们的属性、招式以及来自各款游戏的数据。你可以搜索、筛选等等。

宝可梦网页应用截图,列出了各种怪物及其属性等。

先说明几点前提。这不是一个完美的实验。这个应用只代表一个holotype,它偏内容型,交互很少,更像 Wikipedia,而不是 Figma、GMail 之类。话虽如此,我总得挑某个应用来构建,至少这个应用在高层面上容易理解,写规格和测试也相对直接。

为了尽量拉平起点,我让每个框架/元框架都使用该工具的官方 MCP 或 skills。我不想被人说“这个框架在训练数据里代表性不足,所以我们做了自己的 MCP,希望大家用”之类。我也没有过度指定具体架构(比如 SSR 还是 CSR),也没有要求应用性能尽可能高。我只是把规格交给 Opus 5.5,用 high effort,让它使用指定框架的最新版本。每个 agent 都在沙箱里运行,事先不知道会在 Lighthouse 上测性能和可访问性,不过拿到了符合性测试套件。

另外,我没有规定任何 CSS 方法论,只要求不用 Tailwind、Bootstrap 或其他组件框架,并基于一组规格 token 来设计,以保证视觉上的一致性。我不想让这个测试变成 CSS 的比拼,重点在 JS 框架、CSR 与 SSR、SPA 与 MPA 这些方面。最终的应用跑一套符合性测试,测试基于可访问性树而非原始 DOM,这样就不必规定某种 HTML 结构。有一个框架用了依赖 shadow DOM 的 web components,所以必须这么做。

这个实验让我学到的一点是,我测的每样东西都高度随机。LLM 是随机词生成器,有时掷出的骰子点数低,agent 就跑偏到某个奇怪的方向,浪费一堆 token,这既不是框架的错,也不是它工具的错。Lighthouse 本身也会给出不一致的结果。Largest Contentful Paint(LCP)可能这次认定某张图是“最大”元素,下次又换成另一张。要做到完全公平的实验很难,但多跑几轮确实有帮助,我也尽量如实说明这些波动。

废话少说,直接看结果。

结果

我从构建成本、质量、可维护性这几个维度来拆解。每个框架/元框架我都取了 5 次运行的中位数。也就是说,同一个应用我在同一个框架里构建了 5 遍。(没错,如今用编码 agent 就能干这种离谱的事。)

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)

单次运行成本,单位美元。取 5 次运行的中位数。

注意,这里引用的是 Claude Code 直接给出的 API 价格,并不代表我实际花了多少钱。我用的是 Max 套餐,这些只是我每月 $100 总额的一部分。

还要注意方差。React Router 的中位数大约比 Hono+htmx 差 1,但ReactRouter最便宜的一次(2.82)仍然比 Hono+htmx 最贵的一次($3.19)便宜。不过,一些可靠的规律还是浮现了出来。

最明显的是,SvelteKit、Gracile+Lit 和 Angular 的构建成本高于其他框架。SvelteKit 可能是因为 agent 每次运行都会去读 SvelteKit v3 迁移指南,大概是因为它太新了,不在 Opus 5.5 的训练数据里。Gracile 类似,agent 对这个框架不太熟,要么克隆了 Gracile 仓库,要么从 node_modules 读了它的编译后源码。Angular 则似乎是代码总量导致的(见下文)。

还要提一句,并非每个 agent 都用上了 MCP/skill/Claude Code 插件,也不是每个框架都有对应的插件。

AI 工具使用情况

框架已安装Agent 是否使用?
AngularAngular MCP 和 Angular agent skills5 次运行中 3 次用了 MCP,5 次运行中 4 次用了 skill 工具,5 次运行中 2 次读取了 skill 文件
AstroAstro MCP server5 次运行中 2 次用了 MCP
Gracile+Lit不适用不适用
Hono+htmxHono skills5 次运行全部用了 skill 工具
Next.jsnext-devtools-mcp 和 next-dev-loop skill5 次运行中 1 次读取了 skill 文件,5 次运行中 2 次用了 AGENTS.md
NuxtNuxt MCP不适用
Qwik不适用不适用
React RouterReact Router skill5 次运行全部用了 skill 工具,5 次运行全部读取了 skill 文件
SolidStart不适用不适用
SvelteKitSvelte Claude Code plugin5 次运行全部用了 MCP
TanStack Start不适用5 次运行中 2 次读取了 skill 文件
Vanilla不适用不适用

以 TanStack Start 为例,agent 只在 5 次运行中的 2 次发现了它的 skill 文件(这些文件随项目的 node_modules 一起发布)。Nuxt 的 MCP 则从未被 agent 使用过。Next.js 这边,agent 只在 5 次运行中的 1 次读取了 skill 文件,5 次运行中 2 次用了 AGENTS.md。结果分布如此之广,又回到了 LLM 的随机性上。不过我仍然认为这是个有效的实验,因为我不觉得有多少 vibe-coder 会全大写地冲 agent 吼,逼它去用 MCP(而且很多人可能压根就没装)。

另外,如果你好奇 Hono+htmx 是怎么选出来的,我主要是想让 htmx 有个代表,而 Hono 看起来是个不错的搭配(也就是作为 Rails/Django 等的 JavaScript 替代方案)。至于 Gracile+Lit,我想通过 web components 的标杆框架(Lit)来代表 web components,而我知道的元框架只有 Gracile。如果有更合适的候选,很适合拿来做后续实验。

关于“Vanilla”,需要说明 agent 被要求使用“vanilla JavaScript 或 TypeScript 配合 Vite,不使用 UI 框架”。出乎意料的是,它的表现不如好几个框架,这似乎说明有某个框架总比没有强(或者说,至少比裸用 Vite 强)。选 Vite 是因为它是许多框架的底层,也代表了相当标准和通用的 JavaScript 基础设施。

总耗时和 token 用量与成本基本吻合。

框架时间(分钟)
Astro8.2 分钟(6.2–9.7)
Hono+htmx8.6 分钟(5.9–9.6)
Nuxt9.0 分钟(8.0–17.6)
Vanilla10.1 分钟(6.9–18.6)
TanStack Start10.3 分钟(8.3–13.9)
Qwik10.7 分钟(7.1–12.9)
SvelteKit10.8 分钟(8.6–13.8)
Next.js10.9 分钟(8.3–11.9)
SolidStart11.2 分钟(8.2–15.9)
Gracile+Lit12.1 分钟(10.8–12.9)
React Router12.5 分钟(8.4–13.6)
Angular17.2 分钟(9.8–23.7)

每次运行的耗时。从 agent 视角看的实际耗时(wall-clock),取 5 次运行的中位数。

框架Token(百万)
Nuxt3.85 M(3.16–8.18)
Astro4.13 M(3.03–7.57)
Hono+htmx4.26 M(3.40–5.61)
Next.js4.99 M(3.54–8.82)
Vanilla5.00 M(3.44–6.32)
Qwik5.64 M(3.41–7.03)
TanStack Start7.07 M(5.85–7.99)
SolidStart7.13 M(5.02–10.07)
React Router7.58 M(4.55–8.18)
SvelteKit8.53 M(6.79–9.29)
Gracile+Lit9.04 M(7.36–11.10)
Angular9.76 M(4.92–14.94)

每次运行的 token 数,单位为百万。包含缓存读取、未缓存输入和输出。取 5 次运行的中位数。

顺带一提,token 数据由 Claude Code 报告,时长则只是总的实际耗时。

质量

当然,我认为结果才是关键,所以我也用 Lighthouse 测了这些 agent。作为软件开发者,我觉得重要的不只是把东西做得便宜,还要做得性能好、可访问性好。

先说我的基准测试的两个前提。

  • 这个宝可梦应用很小,代表不了一个大型、复杂的真实应用。
  • Lighthouse 以及整个 Core Web Vitals 体系对 SPA 天然不利,所以 MPA 框架在 Total Blocking Time(TBT)、JavaScript 字节数这类指标上几乎稳赢。我考虑过加一些应用内性能指标,但最终放弃了,因为那会给基准测试增加复杂度和波动,而且那些指标是我自己发明的,不是行业标准。

为简单起见,我只报告首页的分数。其他页面也有数据,但得分都差不多,或者像 LCP 这类指标波动很大,放进来只会让事情变复杂。

简而言之,Performance、Accessibility 和 CLS(Cumulative Layout Shift)的 Lighthouse 分数几乎已经拉满,各框架之间差别不大。

框架CLSPerformanceAccessibility
Angular0.00098100
Astro0.000100100
Gracile+Lit0.000100100
Hono+htmx0.000100100
Next.js0.00099100
Nuxt0.000100100
Qwik0.000100100
React Router0.000100100
SolidStart0.000100100
SvelteKit0.000100100
TanStack Start0.00099100
Vanilla0.11197100

所以要想看到更有意思的数字,得看别的指标。最可靠的是传输的 JavaScript 字节数,它基本反映了客户端框架的体积。

框架JS (KiB)
Astro0 KiB (0–1)
Vanilla7 KiB (6–7)
Gracile+Lit8 KiB (1–13)
Hono+htmx18 KiB (18–18)
SvelteKit36 KiB (35–36)
SolidStart40 KiB (38–40)
Qwik40 KiB (40–41)
Nuxt84 KiB (83–85)
Angular93 KiB (81–102)
React Router115 KiB (115–119)
TanStack Start118 KiB (112–119)
Next.js142 KiB (136–143)

传输的 JavaScript 字节数,由 Lighthouse 报告。取 5 个应用的中位数(Angular 为 3 个)。

Total Blocking Time(TBT)有意思的地方在于,它并不单纯取决于 JavaScript 字节数;有些应用在主线程上花的时间比其他应用更多。不过这些数字都很小。

框架TBT (ms)
Astro0 ms (0–0)
Gracile+Lit0 ms (0–0)
Hono+htmx0 ms (0–0)
SvelteKit0 ms (0–1)
Vanilla0 ms (0–0)
TanStack Start1 ms (0–36)
Qwik3 ms (0–30)
SolidStart11 ms (0–12)
React Router17 ms (0–52)
Nuxt40 ms (14–54)
Next.js71 ms (66–121)
Angular103 ms (93–123)

总阻塞时间(TBT)。数据来自 Lighthouse,模拟移动端降速。每个应用取 5 次 Lighthouse 运行的中位数,再取 5 个应用的中位数(Angular 为 3 个)。

最大内容绘制(LCP)受前述指标自身问题的影响,波动很大。不过正如预期,它总体上不利于 SPA、有利于 MPA。

框架LCP(ms)
Astro810 ms(792–1662)
Gracile+Lit956 ms(796–980)
Hono+htmx978 ms(977–1662)
Qwik979 ms(954–1278)
SvelteKit1130 ms(1129–1131)
SolidStart1204 ms(1203–1278)
Vanilla1667 ms(764–1676)
Nuxt1727 ms(1427–1844)
React Router1729 ms(1729–1770)
Angular1954 ms(1351–1955)
TanStack Start2028 ms(1805–2104)
Next.js2090 ms(1028–2187)

最大内容绘制(LCP)。数据来自 Lighthouse,模拟移动端降速。每个应用取 5 次 Lighthouse 运行的中位数,再取 5 个应用的中位数(Angular 为 3 个)。

注意,Next.js 那组数字波动之所以这么大,是因为其中一个应用没有对图片做懒加载,而另外四个应用做了懒加载。Astro 和 Vanilla 也有类似的起伏,看起来是优先级处理或 LCP 本身的怪癖造成的。

不过总的来说,这个指标倾向于奖励用 HTML 快速完成绘制的 MPA,而不利于等客户端 JavaScript 执行完才绘制的 SPA(后者可能还要经历多次受降速限制的往返来获取宝可梦数据),这并不意外。抛开波动不谈,这里的 LCP 基本取决于 JavaScript 包的体积。(Qwik 是个明显的例外。严格来说它是使用客户端路由的 SPA,但得分却处在 MPA 框架之列。)

不过要注意,各框架在偏向客户端还是服务端上并不一致,有的甚至不确定是否使用客户端框架。例如 Gracile 的 agent 在两次运行中根本没有用到 Lit。至于“Vanilla”,agent 拥有完全的自由(只被要求使用 Vite),但 5 次全都选择了构建 SPA。

SSR 与 CSR 之争,某种程度上也是 Angular 麻烦的来源。有 2 次运行中,agent 构建的 SSR 应用会拒绝非 localhost 的请求(尽管指令中已明确说明应用会部署在反向代理之后)。我认为这属于未遵循规范的合理失败,因此决定不重跑这 2 次失败的迭代。(这只影响代码量和性能测量;在“成本”一节中,我报告了 Angular 全部 5 次迭代的结果。)

可维护性

衡量可维护性的方法很多(包括圈复杂度之类花哨的指标),但我决定从简,直接报告久经检验的代码总行数。

框架代码行数
Hono+htmx1370 (1264–1489)
Nuxt1415 (1382–1458)
Astro1461 (1379–1503)
Vanilla1492 (1437–1742)
Next.js1539 (1452–1627)
React Router1564 (1421–1621)
SvelteKit1611 (1503–1816)
SolidStart1630 (1528–1894)
TanStack Start1697 (1506–1771)
Gracile+Lit1717 (1412–1748)
Qwik1744 (1646–1827)
Angular2009 (2003–2051)

代码行数。指源文件中非空行的数量。取 5 个应用的中位数(Angular 为 3 个)。

在我看来,这是比较有意思的一张图。某种意义上,框架的全部意义就在于减少你需要维护的代码量。用 Rich Harris 那句令人印象深刻的话说:

“框架不是组织代码的工具,而是组织思维的工具。” Rich Harris,“Rethinking reactivity”

在 agent 时代,这一点更加切中要害。代码量最终会影响 token 数量,未来的修改也不例外,也就是说,它直接影响长期维护一个代码库的实际成本。这并不是说简洁是唯一重要的价值,但看到 Hono+htmx 用大约 70% 的代码就能做到和 Angular 差不多的事(而且还更快),确实很有意思。

结论

这个基准测试还有很多可以改进的地方。比如,你可以要求 agent 用 Service Worker 做离线缓存,或者优化宝可梦图片文件,或者使用懒加载、fetchpriority、Speculation Rules API 等等。这些性能技巧我大多知道,但我刻意没有引导 agent 去用,因为典型的 vibe-coder 不会这么做。

另一方面,这个基准测试完全可以靠预先生成所有 HTML 文件作为静态资源来作弊(Pokémon 也就那么多)。我在说明里明确禁止了这种做法,并在测试框架里加了显式检查,因为我觉得这不符合基准测试的本意。(我的目标是模拟大量可能动态变化的数据,这比纯静态站点更有挑战性。)

你也可以改进我跑基准测试的方式,比如跑更多轮次、换不同的编码 agent、试不同的 effort 等级、用不同的框架或框架的不同版本、分别在有和没有 MCP/skill 的情况下测试、在 Chromium 之外的更多浏览器里测、构建不同类型的应用,等等。我没做这些主要是为了省时间。不过,如果有人想自己跑这个基准测试或者 fork 它,代码在 GitHub 上,欢迎随便折腾。我很乐意在这里汇报你的结果(也包括你可能发现的任何错误或缺陷)。

我在这里的目标不是写出一个终极基准测试来回答“我是 vibe-coder,该选哪个框架?”这篇文章的重点是说明,在 AI 编码时代,仍然可以基于证据而不是感觉来做决定。基准测试本身很大程度上是 vibe-coded 的,不过代码我审过,所以如果有错误,请算在我头上。我也非常乐意看到竞品框架在我这套东西上做出改进。

我也不想抹黑任何特定框架。跑基准测试之前,我并不知道哪个会“赢”,尽管我确实怀疑 MPA 会胜过 SPA(这一点我在全文里都坦然承认了)。你当然可以写一个更偏向 SPA 的基准测试,比如把应用做成复杂的仪表盘、电子游戏、生产力工具等等。你也可以选不同的指标。Lighthouse 和 Core Web Vitals 对 SPA 相当苛刻,所以你可以改为衡量应用内交互性。

我也相信,这些框架的作者本人也能改进自己的框架或 AI 工具,在这个基准测试上取得更好的成绩。比如我强烈怀疑,眼下这批 MCP 和 skills 可能弊大于利。所以,如果这篇文章成了这些框架性能表现的最终定论,我会非常失望。我也相信,还有一些新兴框架正跃跃欲试,想在这类基准测试中崭露头角。

退一步说,我也希望这篇文章能让你在这个 vibe coding 的怪异新时代里,对 JavaScript 框架的选择多一分审视。你真的是依据证据在选技术栈吗?还是只是沿用自己已经熟悉的东西?这完全可以理解,尤其是如果你还在亲自阅读、审查和调试代码的话。但如果你基本已经放手不管了,那你对当年像原始人一样一行行敲代码时做出的选择,究竟还有什么执念?如果你正在重新审视自己构建软件的整体方式,那也许值得重新审视一下你在 npm install 后面敲下的东西?随口一说。

就我个人而言,我对上面评估过的这些框架没有任何执念,唯一的信念是我们应当努力为用户打造尽可能好的体验。上面这些框架的作者,我认识并尊重其中许多人,我也知道他们正在为我所拥护的同样价值而不懈努力。我只是希望推动整个社区朝更好的方向走,做出更好、更快、更易用的框架,去支撑下一代 Web 应用。

附录

测试所用的框架版本(注意 Qwik 在测试中途发布过一个补丁版本)。

框架版本
Angular@angular/core 22.2.3
Astroastro 7.4.2
Gracile+Lit@gracile/gracile 0.11.0, lit 3.3.3
Hono+htmxhono 4.13.14, htmx.org 2.0.11
Next.jsnext 16.4.3, react 19.3.1
Nuxtnuxt 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 Routerreact-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
Vanillavite 8.3.5

来源: Read the Tea Leaves← 返回首页