在 htmx 2 中,许多属性默认会被“继承”。这允许你将属性放在父元素上,其行为会应用到子元素。这一行为源自 intercooler.js 时代,其灵感来自 CSS,而且毫不意外地,其效果也与 CSS 类似:强大但有时难以理解。
在 htmx 4 中,属性不再被继承,除非你在属性名后添加 :inherited 来明确指定:
<!-- htmx 2 -->
<div hx-confirm="Are you sure?">
<button hx-delete="/item/1">Delete</button>
</div>
<!-- htmx 4 -->
<div hx-confirm:inherited="Are you sure?">
<button hx-delete="/item/1">Delete</button>
</div>
这将是迁移到 htmx 4 时最大的升级负担。为了简化这一过程,我们提供了一个命令行工具来帮助你找到需要标记为继承的位置。
请注意,hx-disinherit 之类的属性不再需要,应将其移除。
htmx 2 触发的事件在库的发展过程中自然增长,组织得不是特别好,导致很难确切知道何时触发了哪个事件。
在 htmx 4 中,所有事件现在都遵循 htmx:phase:action[:sub-action] 格式:
此外,还做了以下更改:
完整表格请参阅《htmx 4 新特性》。
命令行升级检查器会标记 hx-on 属性以及 JavaScript 中能找得到的旧事件名称。
历史记录支持一直是 htmx 内置的功能,允许你通过简单的属性实现支持后退按钮的操作。在 htmx 2 中,使用 localStorage 中的缓存来快照页面以便恢复。遗憾的是,这个快照可能包含第三方 JavaScript 库对 DOM 的修改,这成为大量问题的根源。当页面被恢复时,这些修改依然存在,但底层 JavaScript 逻辑却已经失效。
htmx 4 不再在 localStorage 中缓存页面。在后退导航时,htmx 会重新获取页面并将其交换到 <body>,或者如果有 [hx-history-elt] 元素,则交换到该元素中。这使第三方 JavaScript 库在大多数情况下能够“正常工作”,并且在良好的请求缓存支持下,速度非常快。
如果你希望使用本地缓存,我们现在提供了一个非常完整的 hx-history-cache 扩展,它可以从 sessionStorage 恢复历史记录,并设计为能与 Alpine.js 等脚本解决方案良好集成。
htmx 4 有两个我们非常兴奋的重大新特性:
我们现在在 htmx 中原生支持 morphing swaps。我创建了 idiomorph,差点把它包含在 htmx 2.x 中,但最终决定不这样做。在 htmx 4 中,Michael 对算法进行了出色的改进,并将其无缝集成到 htmx 中。
另一个主要新特性是 <hx-partial> 标签。该标签类似于 out-of-band swaps,但当你想要做的事情不仅仅是简单地将单个元素替换为新版本时,它要清晰得多:
<hx-partial hx-target="#messages" hx-swap="beforeend">
<div>New message</div>
</hx-partial>
<hx-partial hx-target="#count">
<span>5</span>
</hx-partial>
扩展
htmx 4 的许多亮点在扩展方面。切换到内部使用 fetch() 让我们得以重新思考扩展如何工作以及应该如何工作,并催生了许多新扩展的创建(和重新创建),例如:
此外,还有三个新的或……
最后,我们决定是时候尝试我们自己的、与 htmx 紧密集成的小型前端脚本解决方案了。hx-live 的灵感来自 Alpine.js、jQuery 和 hyperscript,让前端脚本编写变得愉快而有趣。它甚至支持我们所说的基于 DOM、HATEOAS 友好的响应式。
发行版中新增了一个 htmax.js 捆绑包,如果你不想考虑选择哪些扩展,它将 htmx 与其中最流行的扩展打包在单个文件中。
完整的升级指南请参阅《htmx 4 新特性》。
如前所述,我们提供了一个升级工具来帮助你:
$ npx htmx.org@4.0.0 upgrade-check -- ./templates
File extensions: .html, .php, .js, .ts, .jinja, .jinja2, .j2, .erb, .hbs
Use --ext to add more (e.g. --ext .vue --ext .svelte)
Scanning 1 file(s)...
Found 8 issue(s) in 1 of 1 file(s).
templates/index.html:1: [inheritance] hx-headers needs :inherited suffix (descendant on line 3 has hx-delete) (this looks like a CSRF token; without :inherited the header does not reach child elements and the server rejects the request)
templates/index.html:2: [inheritance] hx-target needs :inherited suffix (descendant on line 3 has hx-delete)
templates/index.html:2: [inheritance] hx-confirm needs :inherited suffix (descendant on line 3 has hx-delete)
templates/index.html:3: [renamed-attr] hx-disable -> rename to hx-ignore (hx-disable now means 'disable during request')
templates/index.html:4: [removed-attr] hx-vars is removed -> use hx-vals with js: prefix
templates/index.html:4: [removed-attr] hx-prompt is removed -> load the hx-prompt extension to keep the same syntax
templates/index.html:9: [old-event] old event name "htmx:afterRequest" -> "htmx:after:request"
templates/index.html:9: [old-api] htmx.addClass() is removed -> use element.classList.add()
我们还在发布一个 agent skill 来协助升级。