htmx 4.0:把争议移出核心——小核心、大扩展,与超文本的下一步

htmx 4.0(2026-08-28)全解析:fetch 替代 XHR、错误响应也 swap、显式继承 :inherited、事件名统一为 htmx:phase:action;新增 hx-partial、hx-status、hx-live、HCON 与 htmax 全家桶;以及为什么 AI 写代码越便宜,这种「少写 JS」的库反而越重要。

  • 前端
  • htmx
  • 超文本
  • HATEOAS
  • Web
  • 开源项目

本站之前一直在讲 AI:Agent 的 Harness、推理引擎、检索层……但今天聊个看似离题、其实贴着 AI 时代主线的东西—— 前端本身。2026 年 8 月 28 日,htmx 4.0 正式发布(仓库 github.com/bigskysoftware/htmx, 4.9 万 star,JavaScript)。上一个大版本 2.0 还是 2024 年 6 月的事,中间隔了两年多、六个 beta、八个 alpha。

这篇把 4.0 到底改了什么、新加了什么、升级要踩哪些坑,以及一个更大的问题一次讲清楚:在 AI 写代码越来越便宜的今天,为什么一个「让你少写 JavaScript」的库反而更重要了。

一、htmx 是什么:一句话复习

没用过 htmx 的读者,先花 30 秒建立直觉。它的口号是 high power tools for HTML, 核心思想叫 HATEOAS(Hypermedia As The Engine Of Application State)—— 服务器返回的不是 JSON,而是 HTML 片段,浏览器把这段 HTML 直接「换」进页面指定的位置。 整个交互长这样:

<button hx-post="/clicked" hx-swap="outerHTML">
  点我
</button>

点一下,htmx 发 POST 到 /clicked,服务器回一段 HTML,按钮自己被换掉。没有构建步骤、没有虚拟 DOM、没有 hydration——服务端用你最熟的语言(Python、Go、Ruby……)拼 HTML, 前端就是几十个 hx-* 属性。 如果你用过 jQuery 时代的「服务端渲染 + 一点 JS 点缀」,htmx 就是把这套打法系统化、现代化之后的形态。

二、4.0 最大的变化:从「一个库」变成「一层 + 一堆扩展」

理解 4.0 的钥匙,是官网那句迁移文档标题底下的潜台词:core 变小了,extensions 变多了。 2.0 的时候 SSE 和 WebSocket 的旧属性刚被砍掉、移到外部仓库;4.0 反手把重写过的hx-sse、hx-ws 又「请」回来,变成随官方发行版一起走的核心扩展(core extensions)。

htmx 4 · 小核心,大扩展层2026-08-28 v4.0.0 · 49k starhtmx corefetch 发请求 · hx-swap 换 DOM · 历史管理HCON 统一属性语法 · htmx:phase:action 事件命名扩展层 · 有争议的东西全部出 core流式 HTMLhx-ws · hx-sse · hx-multipart重写后收编,三套 API 对齐续传 cursor · PTag 去重UX 与响应式hx-live · hx-pending · hx-prompthx-preload · hx-browser-indicatorq() + :text 绑定,官方首肯响应式交换策略hx-targets · hx-upserthx-download · hx-head<hx-partial> 多目标一次更新性能hx-history-cache · hx-ptagsessionStorage 回退缓存兼容htmx-2-compat · hx-alpine-compat恢复 2.x 默认与事件名安全hx-csp · nonce 门控 evalConstructable Stylesheets 免 noncehtmax · 官方全家桶,一个文件core + 11 个常用扩展 · <script src="/js/htmax.min.js"> 即用history-cache 随包但默认关闭,meta 里 opt-in扩展直接 <script> 引入即可,不再需要 hx-ext 属性;可用 meta 名单限制加载范围方向:core 只留「没人吵架的三件事」,其余全部扩展化——连「要不要响应式」都是可选的
图 1 · htmx 4 的分层:精简的核心只管发请求、换 DOM、管历史;流式、响应式、兼容、安全全部做成扩展

这张图里最值得注意的是方向:所有「有争议的东西」都出了 core。 响应式要不要?做成 hx-live 扩展,你爱装不装;CSP 严格怎么办?hx-csp 扩展; 还想留在 2.x 的行为?htmx-2-compat 扩展帮你把默认和事件名都恢复回去。 core 只保留发请求、换 DOM、管历史这三件「没人会吵架的事」。

配套的还有一个打包形态叫 htmax——core 加最常用的 11 个扩展打成一个文件,<script src="/js/htmax.min.js"> 一行引入,扩展属性直接可用 (历史缓存默认关闭,想开再加一行 meta)。这就是官方给出的「全家桶」答案。

三、Breaking changes:升级前必须知道的十件事

官方迁移文档(whats-new-in-htmx-4.md,459 行)把破坏性变更列得很实在。 我按「不改会出事」的程度排了个序,前五条是必看:

#变化一句话回退开关
1fetch() 替代 XHR所有请求走原生 fetch,不可回退;htmx:xhr:* 那批事件直接没了无
2错误响应也会被 swap2.x 里 4xx/5xx 不换内容;4.0 里除了 204/304 全换——服务器的 422/500 页面会直接进 targethtmx.config.noSwap = [204, 304, '4xx', '5xx']
3继承必须显式声明hx-confirm="…" 不再自动传给子元素,要写 hx-confirm:inherited="…"htmx.config.implicitInheritance = true
4hx-disable 改名连环套旧 hx-disable(跳过处理)→ hx-ignore;hx-disable 这个名字现在是旧 hx-disabled-elt 的意思。官方明确说:先改前者再升级无(必须改代码)
5事件名全部重排htmx:afterSwap → htmx:after:swap,新范式是 htmx:phase:action[:sub-action];一堆错误事件合并成 htmx:errorhtmx-2-compat 扩展
6历史不再缓存页面localStorage 缓存没了,回退时重新请求再换进 <body>hx-history-cache 扩展(opt-in,用 sessionStorage)
7hx-trigger 的 queue 修饰符删除队列控制只走 hx-sync,如 hx-sync="this:queue all"无
8选择器引号规则变了from:(closest form) 这种括号写法作废,含空格或逗号的选择器用单引号:from:'closest form'、target:'.a, .b'无
9默认 60 秒超时2.x 默认 0(不限时);4.0 defaultTimeout = 60000htmx.config.defaultTimeout = 0
10一堆小 API 被砍htmx.addClass/removeClass/closest/remove() 等直接删——「用原生 JS」;hx-vars、hx-params、hx-request、hx-ext、hx-history 等属性删除或改名各有去处(见迁移表)

读完这十条应该能感觉到 4.0 的脾气:每个 breaking change 背后都站着一条「显式优于隐式」的原则。 隐式继承改显式、错误也 swap(别假装没收到)、XHR 事件名里还挂着 xhr 的全改掉、 工具函数删了让你用原生——没有一条是「为了改而改」,全是把暗处的东西拽到明处。

继承:从「默认往下漏」变成「声明才往下传」回退:htmx.config.implicitInheritance = truehtmx 2.x · 隐式继承hx-confirm="Are you sure?"写在父 div 上hx-delete="/item/1"✓ 自动也有确认框(猜的)htmx 4.0 · 三态,写什么得什么hx-confirm="…"只自己 ✕hx-confirm:inherited="…"往下传 ✓hx-include:inherited:append=".extra"与上层值拼接,不覆盖升级公式(全局搜,逐个改)hx-boost / hx-target / hx-confirm / hx-include / hx-swap …凡是靠「父元素顺手带上」生效的 → 补 :inherited凡是靠「子元素叠加父元素」生效的 → 用 :inherited:append适用一切属性:hx-boost:inherited、hx-target:inherited……写法统一,没有例外表hx-disinherit / hx-inherit 两个旧属性直接删除——显式继承下它们没有存在意义对 Agent 友好:读代码的人(人或模型)不用再猜「这个值是从哪层漏下来的」
图 2 · 显式继承:属性默认只作用于自己,加 :inherited 才往下传,加 :append 则与上层的值拼接而不是覆盖

四、新东西:六个值得记住的名字

破坏之外,4.0 真正的新能力集中在六个名字上:

  • <hx-partial>:一次响应,更新多处。 以前要么靠 hx-swap-oob(顺序还反直觉:2.x 里 OOB 先换、主内容后换,4.0 改成主内容先换), 要么多发几个请求。现在响应里直接写几个 <hx-partial hx-target="#messages" hx-swap="beforeend"> 块, 每个块自带 target 和 swap 策略。聊天室「加一条消息 + 更新未读数」这种需求终于不用绕路了;
  • hx-status:按状态码定制 swap。hx-status:422="swap:innerHTML target:#errors select:#validation-errors",hx-status:5xx="swap:none push:false"——支持精确码、50x、5xx 三档, 按精确度排序求值。这正好接住了前面第 2 条 breaking change:错误也 swap 了,那就给你精细的缰绳;
  • hx-live:官方自己的响应式方案。 jQuery 风格的 q()(支持 closest .field、next input 这种方向选择器)、toggle/take/trigger/insert 一套 DOM helper, 再加 :text、:hidden、:.class 这类冒号前缀的响应式绑定, 和 debounce/forEvent/nextFrame 异步 helper。 注意它的定位声明:「inspired by jQuery, Alpine.js and hyperscript」——htmx 官方第一次正面回应了「纯服务端 HTML 不够用」的那一小撮场景;
  • HCON:属性里的小配置语言。hx-swap="innerHTML swap:200ms settle:100ms"、hx-trigger="click delay:500ms"、<meta name="htmx-config" content="transitions defaultTimeout:5000 sse.reconnect:true">—— 去掉大括号的 JSON、裸词即 true、点号表嵌套、{ 开头自动按 JSON 解析。 4.0 把散落在各处的临时解析器统一成了这一套,所以才有了第 8 条的引号新规则;
  • swap 新样式:innerMorph/outerMorph(idiomorph 算法做 DOM diff,保状态)、textContent(只设文本、不解析 HTML)、delete(直接删掉目标), 外加 before/after/prepend/append 四组人类可读的别名;
  • hx-query + HX-Request-Type: 发出新的 RFC QUERY 方法请求(参数放 body 里),请求头新增 HX-Request-Type: full|partial、 核心请求统一带 Accept: text/html。服务端终于能名正言顺地分辨「整页还是片段」了。

五、WebSocket 和 SSE:从「外部实验」变成「一等公民」

2.0 把旧的 hx-ws/hx-sse 属性砍掉时,很多人以为官方放弃实时通信了。 4.0 的答案是反过来的:重写之后以扩展形态收编。 新扩展统一了三套流式 API(hx-ws、hx-sse、hx-multipart 的事件和配置对齐), ws 扩展支持非 JSON 消息当原始 HTML 处理(可取消事件)、SSE 扩展修了 iOS Safari 切后台断连、 支持 Last-Event-ID 续传和 pauseOnBackground。

再加上新增的 hx-multipart(用 multipart/mixed 流式推 HTML, 带 resume cursor)、hx-ptag(轮询去重:HX-PTag: "v42", 内容没变服务器直接说「没变」)、hx-download 和hx-head(<head> 合并), 4.0 的「流式 HTML」工具箱其实是整套里长得最齐的一块。一个判断:htmx 团队认为实时 UI 的未来不在 JSON over WebSocket,而在 HTML over 各种流。

六、它和 Alpine、React 这些邻居现在是什么关系

4.0 有两个扩展专门处理「邻居关系」,态度很有意思:

  • hx-alpine-compat:让 htmx 和 Alpine.js 在同一页面不打架。 不是否定 Alpine,而是承认「服务端 HTML + 一点客户端状态」是常见组合,官方出面把冲突点(morph、历史恢复)修掉;
  • hx-csp:严格 Content Security Policy 下也能跑—— nonce 门控覆盖所有内部 eval 调用、indicator CSS 改用 Constructable Stylesheets(所以连 nonce 都省了)。 这是 htmx 第一次把「安全合规」当成一等功能来做;
  • JSX 兼容:属性名里的冒号(:inherited、hx-ws:connect)对 JSX 不友好, 4.0 允许用 metaCharacter 配置换掉分隔符,比如改成连字符。 一个细节,但说明团队在认真对待「在 React/JSX 项目里渐进采用 htmx」这条路。

和 React/Vue/Svelte 的关系则不需要 4.0 来重新定义:htmx 从来不是来替换它们的, 它服务的是另一类项目——以后端为主、前端交互以「局部更新」为主、不想养一条 JS 构建链的项目。 4.0 只是把这个定位守得更彻底:core 更小、边界更清,争议功能全部扩展化。

七、为什么 AI 时代反而更需要 htmx

这是本站视角下 4.0 最值得写的一节。AI 写代码越便宜,「代码量」就越不值钱, 值钱的是可审计、可回滚、出错面小的东西。从这个角度看,htmx 的设计恰好踩中三个点:

  • AI 生成 HTML 片段,比生成整套前端状态管理靠谱得多。 让模型写一个 React 组件,它要同时管住 props、state、effect、依赖数组; 让模型写一个返回 <tr> 的 Flask/Django handler,出错面小一个数量级。 htmx 把「应用状态」押在服务端和超媒体上,正好匹配 LLM 最擅长的输出形态——模板化的、局部的、可直接肉眼验证的 HTML;
  • 4.0 的「显式化」方向,和 Agent 协作是同构的。 隐式继承、静默丢弃错误响应、藏在各处的临时语法——这些对人类是「约定俗成」, 对 Agent 是「猜谜」。:inherited、HCON 的统一语法、htmx:phase:action 的事件命名, 本质上都是在降低「读代码的人(不管是人还是模型)」的推理负担;
  • 没有构建链 = Agent 改完就能跑。 一个 AI 生成的改动如果还要过 npm install、vite build、hydration mismatch 三关, 反馈循环就长;htmx 项目改完模板刷新即见。和本站之前写的Omarchy 那种「开箱即用」哲学是同一个味道。

反过来也要诚实:htmx 管不了重客户端交互(在线白板、复杂编辑器、游戏画布), 那是本站主赛道里游戏侧要用的另一套东西。它的疆域就是「文档型 + 局部动态」的 web 应用—— 而这恰好是内部工具、管理后台、内容站、AI 应用的前端(聊天流、表单、列表、SSE 推送)最常见的样子。

八、升级建议:三档策略

  • 新项目:直接上 4.0 + htmax。<script src="/js/htmax.min.js"> 一行引入,SSE/WS/preload/history-cache 全齐, 再按需关。别从 2.x 的习惯起手,事件名、继承写法、引号规则都按新的学;
  • 老项目想升:先跑两遍改名,再开兼容层。 第一遍把 hx-disable 改成 hx-ignore(官方强调顺序:这个必须在升级前做), 第二遍把旧 hx-disabled-elt 改成新的 hx-disable; 然后装 htmx-2-compat 扩展恢复 2.x 默认和事件名,逐个页面摘掉兼容、逐个验证错误页和 OOB 顺序;
  • 别急着升:这三类先等等。 重度依赖 queue:all 做请求串行的(要重写成 hx-sync)、 把 4xx/5xx 当「静默失败」用的(4.0 会真的把错误页换进去,先修服务端错误模板或配 hx-status)、 以及 OOB 顺序有隐式依赖的(2.x 是 OOB 先换,4.0 是主内容先换)。

还有两个容易漏的:hx-get/hx-delete 现在都不带外层表单数据了, 需要就加 hx-include="closest form";show:#other:top 这种组合写法作废, 拆成 show:top showTarget:#other。

我的收获

  1. 「把争议移出 core」是最便宜的架构决策。 响应式、CSP、Alpine 共存、2.x 兼容——每一个单独看都值得吵三个月, 全做成扩展之后 core 反而更好维护了。这和软件工程里「内核 + 插件」是同一个智慧, 只是 htmx 把它做到了连「要不要响应式」这种路线问题都插件化。
  2. 显式化是跨时代的红利。:inherited、HCON、htmx:phase:action, 表面上是给人类读的,实际上是给下一个读者(很可能是 Agent)读的。 写代码时多问一句「三年后(或一个没见过这代码的模型)看这里要猜几次」,很多设计就有答案了。
  3. 错误也 swap,是 4.0 里最有胆的一刀。 2.x 把 4xx/5xx 当「没发生」是最常见的静默失败来源;4.0 强迫你把错误页当成一等 UI 来设计, 再给你 hx-status 做精细控制。「默认做对的事,精细控制留后门」——好的默认就是这样。
  4. 流式 HTML 可能是被低估的一整条赛道。 SSE + Last-Event-ID 续传、multipart resume cursor、HX-PTag 轮询去重、WS 非 JSON 当 HTML—— 这一套东西拼起来,AI 应用里最常见的「打字机流 + 状态推送」根本不需要 WebSocket JSON 协议。 下次做流式 UI,先问一句:这段流,能不能就是 HTML 本身?
  5. 小库的版本号也值得写一篇。 49k star 的项目,两年磨一个 major,迁移文档 459 行、每个 breaking change 都配回退开关和改名顺序警告。 这种「把升级成本当成自己的 bug 来修」的态度,是开源项目信任感最实在的来源。

想深入了解,推荐v4.0.0 release notes、官方迁移文档《What's New in htmx 4》、hx-live 编程指南 和HCON 语法指南。 手上有 htmx 2.x 项目的,先别急着升——把那张十条表过一遍,标出自己项目中了几条,再决定。