htmx 1.x 升级到 2.x 实战:默认值、hx-on 语法与扩展拆分的完整改造清单
【免费下载链接】htmxhtmx - high power tools for HTML项目地址: https://gitcode.com/GitHub_Trending/ht/htmx
htmx 是一套主打"高功率 HTML"的无构建前端工具,让你直接在标签上写hx-*属性就能发起请求并替换页面片段。从 htmx 1.x 升到 2.x 时,真正要动手的只有少数几处:几个默认配置的调整、hx-on事件语法的换血、扩展从核心包里被拆出去,以及两个内部 API 的替换。下面按"升级前判断 → 逐项改造 → 验证回退"的工作流来走,每一项都给出恢复 1.x 行为的具体写法,方便你逐步推进、随时回退。
升级前先判断:模块文件和加载方式要不要动
2.x 把发行产物按 JavaScript 模块体系做了拆分,不同构建场景要挑对应的文件。构建脚本 scripts/dist.sh 里能直接看到它产出htmx.amd.js、htmx.cjs.js、htmx.esm.js这几份模块专属产物,而htmx.js则继续留给浏览器直接加载。
| 你的项目形态 | 选这个文件 |
|---|---|
浏览器<script>直接引 | /dist/htmx.js(无需改动) |
打包用 ESM(import/export) | /dist/htmx.esm.js |
| RequireJS 等 AMD 加载器 | /dist/htmx.amd.js |
| Node / CommonJS | /dist/htmx.cjs.js |
所以如果线上一直是用<script src="/dist/htmx.js">挂着的,升级后这条引用原封不动就能跑。只有在模块化打包场景里,才需要按上表核对一下导入路径。
把扩展从核心包里"搬出去"
1.x 时代跟着核心一起发布的扩展,在 2.x 里已经全部独立分发,核心包不再捆绑它们。大部分 1.x 扩展在 2.x 下能继续工作,但有两点要留意:
- SSE 扩展是强制项,必须升到 2.x 版本,否则跑不起来;
- 官方建议把用到的扩展整体升到 2.x,好与新版核心的事件模型对齐。
另外,如果你页面上还在用旧的hx-ws、hx-sse写法,要换成对应扩展提供的属性形式。扩展的引入与构建细节,参考 www/content/extensions/_index.md 和 www/content/extensions/building.md。
三个默认值:2.x 悄悄改了什么
2.x 调整了三个全局配置的默认值,全部能通过htmx.config覆盖。当前取值在 src/htmx.js 的默认配置对象里能直接对上。
| 配置项 | 2.x 默认 | 1.x 行为 | 想恢复 1.x 的写法 |
|---|---|---|---|
scrollBehavior | 'instant' | 平滑滚动 | htmx.config.scrollBehavior = 'smooth' |
methodsThatUseUrlParams | ['get', 'delete'] | ['get'] | htmx.config.methodsThatUseUrlParams = ['get'] |
selfRequestsOnly | true | false | htmx.config.selfRequestsOnly = false |
3.1 滚动默认从"平滑"改成"瞬时"
scrollBehavior在 src/htmx.js 里默认写成'instant',可选'auto' | 'instant' | 'smooth'。它直接决定了每次内容交换后的滚动手感:swap 完成后 htmx 会调用target.scrollIntoView({ block, behavior: htmx.config.scrollBehavior })(见 src/htmx.js),也就是说不再默认带平滑过渡动画。
如果产品上依赖那种缓缓滚过去的体验,在初始化里加一行即可:
htmx.config.scrollBehavior = 'smooth';3.2 DELETE 的参数默认走 URL 查询串
methodsThatUseUrlParams决定哪些 HTTP 方法的参数被编码进 URL 查询串、而不是塞进请求体。2.x 把它默认设成['get', 'delete'](src/htmx.js)。请求发送前会走到htmx.config.methodsThatUseUrlParams.indexOf(verb) >= 0这个判断(src/htmx.js)来分流。
这里看似"反常",其实是更贴 HTTP 规范的修正:规范认为DELETE和GET一样应该用请求参数而非请求体。如果你的后端一直靠 DELETE 请求体拿参数,那就显式回退:
htmx.config.methodsThatUseUrlParams = ['get'];3.3 跨域请求默认被关闸
selfRequestsOnly默认true(src/htmx.js),意味着只放行同源请求。它在请求真正发出前就被检查(src/htmx.js),属于一次安全加固。确有跨域需求时再放开:
htmx.config.selfRequestsOnly = false;提示:关掉
selfRequestsOnly只是解除了 htmx 这一侧的拦截,跨域请求最终能否成功,仍然取决于服务端有没有正确配置 CORS。
事件写法换血:从 hx-on 到 hx-on:
1.x 用一个hx-on属性把"事件名 + 冒号 + 处理代码"叠在一起;2.x 改成一个事件一个独立属性的hx-on:形式。官方给出的标准对照是这样的——
1.x:
<button hx-get="/info" hx-on="htmx:beforeRequest: alert('Making a request!') htmx:afterRequest: alert('Done making a request!')"> Get Info! </button>2.x 等价:
<button hx-get="/info" hx-on:htmx:before-request="alert('Making a request!')" hx-on:htmx:after-request="alert('Done making a request!')"> Get Info! </button>这里有个容易踩的坑:事件名必须写成 kebab-case。比如htmx:beforeRequest要写成htmx:before-request。根因是 HTML 属性名不区分大小写,浏览器会把属性名整体小写化,一旦写了驼峰,里面的大写就废掉了;只有短横线写法能稳定匹配到。htmx 本身同时认驼峰和短横线事件名,但在属性位置只能靠 kebab-case,这一点在 www/content/attributes/hx-on.md 里有说明。
源码侧,2.x 的属性发现逻辑在 src/htmx.js 里同时兼容hx-on:/data-hx-on:,以及旧式hx-on-/data-hx-on-前缀。
还有一条简写能省事:hx-on::before-request等价于hx-on:htmx:before-request,省掉了重复写htmx:命名空间,适合挂请求周期事件。
提示:同一元素上
hx-on:*和旧的hx-on不能并存,只要存在hx-on:*,旧hx-on的值就会被忽略,迁移时要清理掉旧写法。
两个 API 层面的"断舍离"
makeFragment 永远返回 DocumentFragment
1.x 里htmx.makeFragment()视响应内容不同,可能返回Element或DocumentFragment;2.x 起统一只返回DocumentFragment。
看 src/htmx.js 的实现就清楚了:响应以<html>开头时解析整份文档、取出<body>子节点组装成片段并把<title>存进fragment.title;以<body>开头则类似处理 body;其他部分 HTML 会用内部<template class="internal-htmx-wrapper">包一层再解析,以最大化解析灵活性,同时兼容根级<title>的旧行为。函数还会把<hx-*>自定义标签临时转成<template hx type="*">以便在 HTML 解析中存活,并对<script>做规范化。
如果你之前在代码里对makeFragment的返回值做了Element/DocumentFragment的分支判断,升级后统一按DocumentFragment处理即可。
selectAndSwap 没了,交给 swap
这是给扩展作者的重点:1.x 内部 API 里的selectAndSwap被移除,改由swap顶替,而且它同时开放在内部 API 和公开 API 上,普通开发者也能直接用htmx.swap()驱动交换。官方迁移序列(www/content/migration-guide-htmx-1.md):
let content = "<div>Hello world</div>"; // 要交换进目标的 HTML let target = api.getTarget(child); // 解析交换目标元素 let swapSpec = api.getSwapSpecification(child); // 读取 hx-swap 规格 api.swap(target, content, swapSpec);底层swap(target, content, swapSpec, swapOptions)在 src/htmx.js 里依次做:解析出真正目标、在文档根节点上下文里处理 OOB 交换与hx-select-oob、处理hx-select与hx-preserve、执行主交换并触发htmx:beforeSwap/htmx:afterSwap、维护焦点与选区(document.activeElement及其selectionStart/End),最后走 settle 流程。而把hx-swap解析成结构化的getSwapSpecification(src/htmx.js)会拆出swapStyle、swapDelay/settleDelay、transition、ignoreTitle、scroll/show及各自目标选择器、focus-scroll等。
公开 API 形态见 www/content/api.md,核心参数:target(元素或选择器)、content(要交换的 HTML 字符串)、swapSpec(swapStyle必填,如innerHTML,可选swapDelay、settleDelay、transition、ignoreTitle、head、scroll系列),以及可选的swapOptions(select、selectOOB、eventInfo、anchor、contextElement、afterSwapCallback/afterSettleCallback等)。极简用法:
htmx.swap("#output", "<div>Swapped!</div>", {swapStyle: 'innerHTML'});提示:扩展内若需要为指定元素查找可用扩展,可把相关元素放进
swapOptions.contextElement,源码注释表明它目前就承担这个用途。
浏览器支持:IE 正式告别
htmx 2.0 不再支持 IE。仍在维护的 1.x 系列会继续照顾 IE,并会维持可预见的未来。
- 有 IE 硬兼容需求的项目,请继续锁在 1.x 版本线;
- 已面向现代浏览器(Chrome / Firefox / Safari / Edge)的,可以放心升 2.x,享受新语法、统一 API 与更稳的安全默认值。
升级后的验证与回退
升完之后建议按这条线过一遍,全部能过再合进主线:
- 加载无报错:确认
<script>或模块导入指向 2.x 产物,控制台干净。 - 事件回归:逐一核对
hx-on是否都改成了hx-on:且事件名是 kebab-case,重点回归htmx:before-request/htmx:after-request这类请求周期事件。 - DELETE 接口:回归测试参数走 URL 查询串后后端能否正确解析,不行就回退
methodsThatUseUrlParams = ['get']。 - 跨域场景:升级后被拦截的跨域请求,检查是否需要
selfRequestsOnly = false。 - 扩展:确认 SSE 等扩展升到 2.x,并清掉
hx-ws/hx-sse旧属性。 - 自定义扩展:全局搜
selectAndSwap残留,全部换成swap并按 www/content/api.md 的签名调整参数。
得益于"默认值可用htmx.config覆盖 + 旧hx-on兼容层 + 可继续引用旧发行文件",整个过程能做得很平滑:改动集中在独立分支上验证,发现不对劲随时能回退。逐项对照 CHANGELOG.md 里的版本记录,再结合 src/htmx.js 的实现与 www/content/api.md 的公开 API 文档,就能把这次大版本升级做得既干净又稳。
【免费下载链接】htmxhtmx - high power tools for HTML项目地址: https://gitcode.com/GitHub_Trending/ht/htmx
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考