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」的库反而越重要。
本站之前一直在讲 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)。
这张图里最值得注意的是方向:所有「有争议的东西」都出了 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 行)把破坏性变更列得很实在。 我按「不改会出事」的程度排了个序,前五条是必看:
| # | 变化 | 一句话 | 回退开关 |
|---|---|---|---|
| 1 | fetch() 替代 XHR | 所有请求走原生 fetch,不可回退;htmx:xhr:* 那批事件直接没了 | 无 |
| 2 | 错误响应也会被 swap | 2.x 里 4xx/5xx 不换内容;4.0 里除了 204/304 全换——服务器的 422/500 页面会直接进 target | htmx.config.noSwap = [204, 304, '4xx', '5xx'] |
| 3 | 继承必须显式声明 | hx-confirm="…" 不再自动传给子元素,要写 hx-confirm:inherited="…" | htmx.config.implicitInheritance = true |
| 4 | hx-disable 改名连环套 | 旧 hx-disable(跳过处理)→ hx-ignore;hx-disable 这个名字现在是旧 hx-disabled-elt 的意思。官方明确说:先改前者再升级 | 无(必须改代码) |
| 5 | 事件名全部重排 | htmx:afterSwap → htmx:after:swap,新范式是 htmx:phase:action[:sub-action];一堆错误事件合并成 htmx:error | htmx-2-compat 扩展 |
| 6 | 历史不再缓存页面 | localStorage 缓存没了,回退时重新请求再换进 <body> | hx-history-cache 扩展(opt-in,用 sessionStorage) |
| 7 | hx-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 = 60000 | htmx.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 的全改掉、 工具函数删了让你用原生——没有一条是「为了改而改」,全是把暗处的东西拽到明处。
四、新东西:六个值得记住的名字
破坏之外,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。
我的收获
- 「把争议移出 core」是最便宜的架构决策。 响应式、CSP、Alpine 共存、2.x 兼容——每一个单独看都值得吵三个月, 全做成扩展之后 core 反而更好维护了。这和软件工程里「内核 + 插件」是同一个智慧, 只是 htmx 把它做到了连「要不要响应式」这种路线问题都插件化。
- 显式化是跨时代的红利。
:inherited、HCON、htmx:phase:action, 表面上是给人类读的,实际上是给下一个读者(很可能是 Agent)读的。 写代码时多问一句「三年后(或一个没见过这代码的模型)看这里要猜几次」,很多设计就有答案了。 - 错误也 swap,是 4.0 里最有胆的一刀。 2.x 把 4xx/5xx 当「没发生」是最常见的静默失败来源;4.0 强迫你把错误页当成一等 UI 来设计, 再给你
hx-status做精细控制。「默认做对的事,精细控制留后门」——好的默认就是这样。 - 流式 HTML 可能是被低估的一整条赛道。 SSE + Last-Event-ID 续传、multipart resume cursor、
HX-PTag轮询去重、WS 非 JSON 当 HTML—— 这一套东西拼起来,AI 应用里最常见的「打字机流 + 状态推送」根本不需要 WebSocket JSON 协议。 下次做流式 UI,先问一句:这段流,能不能就是 HTML 本身? - 小库的版本号也值得写一篇。 49k star 的项目,两年磨一个 major,迁移文档 459 行、每个 breaking change 都配回退开关和改名顺序警告。 这种「把升级成本当成自己的 bug 来修」的态度,是开源项目信任感最实在的来源。
想深入了解,推荐v4.0.0 release notes、官方迁移文档《What's New in htmx 4》、hx-live 编程指南 和HCON 语法指南。 手上有 htmx 2.x 项目的,先别急着升——把那张十条表过一遍,标出自己项目中了几条,再决定。