mcpField
H
htmx-expert

htmx-expert

github.com/lullabot开发152 次安装47 周前更新
代码仓库

关于 htmx-expert

面向 htmx 开发者的指导 Skill,涵盖请求属性、响应交换、事件、扩展和安全实践。适用于构建超媒体应用、排查请求响应问题,以及将 SPA 模式改为 htmx 方案。

主要功能

  1. 01htmx 属性与请求控制参考
  2. 02响应内容交换与状态管理
  3. 03事件和扩展配置指导
  4. 04安全与渐进增强实践
  5. 05调试及开发服务器说明

适用场景

  1. 01实现 htmx 表单、搜索或轮询
  2. 02排查 AJAX 请求与响应交换
  3. 03将 SPA 交互改为超媒体模式
  4. 04为 htmx 内容配置安全措施

MCP 相关

README 未说明与 MCP 的关系。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/lullabot/htmx-expert --skill htmx-expert

安装后在 Claude Code 中输入/htmx-expert确认已加载。

附带文件references/attributes-quick-ref.mdreferences/server-patterns.md

详细介绍

htmx 专家

本技能全面介绍 htmx 开发。htmx 是一个扩展 HTML 的库,让你无需 JavaScript 即可直接使用现代浏览器功能。

核心理念

htmx 代表着一种转向超媒体优先 Web 开发的范式变革。它不再将 HTML 视为搭配 JSON API 的展示层,而是扩展 HTML,使其能够直接处理 AJAX 请求、CSS 过渡、WebSocket 和服务器发送事件。服务器返回的是 HTML 片段,而不是 JSON。

何时使用本技能

  • 实现 htmx 属性和交互
  • 构建由超媒体驱动的应用
  • 调试 htmx 请求/响应周期
  • 将 SPA 模式转换为 htmx 方案
  • 了解 htmx 事件和生命周期
  • 配置 htmx 扩展
  • 实施恰当的安全措施

核心属性参考

HTTP 动词属性
属性用途默认触发方式
hx-get发起 GET 请求点击
hx-post发起 POST 请求点击(表单:提交)
hx-put发起 PUT 请求点击
hx-patch发起 PATCH 请求点击
hx-delete发起 DELETE 请求点击
请求控制
  • hx-trigger:自定义请求触发时机

    • 修饰符:changed、delay:Xms、throttle:Xms、once
    • 特殊触发器:load、revealed、every Xs
    • 扩展选项:from:<selector>、target:<selector>
  • hx-include:在请求中包含其他元素的值

  • hx-params:筛选要发送的参数(*、none、not <param>、<param>)

  • hx-headers:添加自定义标头(JSON 格式)

  • hx-vals:向请求添加值(JSON 格式)

  • hx-encoding:设置编码(上传文件时使用 multipart/form-data)

响应处理
  • hx-target:指定响应内容的放置位置

    • 扩展选择器:this、closest <sel>、next <sel>、previous <sel>、find <sel>
  • hx-swap:设置内容插入方式

    • innerHTML(默认)、outerHTML、beforebegin、afterbegin、beforeend、afterend、delete、none
    • 修饰符:swap:Xms、settle:Xms、scroll:top、show:top
  • hx-select:选择响应中要交换的部分

  • hx-select-oob:选择要进行带外交换的元素

状态管理
  • hx-push-url:将 URL 压入浏览器历史记录
  • hx-replace-url:替换历史记录中的当前 URL
  • hx-history:控制历史快照行为
  • hx-history-elt:指定要生成快照的元素
UI 指示器
  • hx-indicator:指定请求期间要显示的元素(添加 htmx-indicator 类)
  • hx-disabled-elt:指定请求期间要禁用的元素
安全与控制
  • hx-confirm:请求前显示确认对话框
  • hx-validate:在非表单元素上启用 HTML5 验证
  • hx-disable:禁用元素及其后代元素上的 htmx 处理
  • hx-sync:协调不同元素之间的请求

实现模式

基本 AJAX 模式
<button hx-get="/api/data"
        hx-target="#result"
        hx-swap="innerHTML">
  Load Data
</button>
<div id="result"></div>
主动搜索
<input type="search"
       name="q"
       hx-get="/search"
       hx-trigger="keyup changed delay:300ms"
       hx-target="#search-results">
<div id="search-results"></div>
无限滚动
<div hx-get="/items?page=2"
     hx-trigger="revealed"
     hx-swap="afterend">
  Loading more...
</div>
轮询
<div hx-get="/status"
     hx-trigger="every 5s"
     hx-swap="innerHTML">
  Status: Unknown
</div>
表单提交
<form hx-post="/submit"
      hx-target="#response"
      hx-swap="outerHTML">
  <input name="email" type="email" required>
  <button type="submit">Submit</button>
</form>
带外更新

服务器响应可以更新多个元素:


<div id="main-content">Updated content</div>


<div id="notification" hx-swap-oob="true">New notification!</div>
<span id="counter" hx-swap-oob="true">42</span>
加载指示器
<button hx-get="/slow-endpoint"
        hx-indicator="#spinner">
  Load
</button>
<img id="spinner" class="htmx-indicator" src="/spinner.gif">

指示器的 CSS:

.htmx-indicator {
  opacity: 0;
  transition: opacity 200ms ease-in;
}
.htmx-request .htmx-indicator {
  opacity: 1;
}

服务器响应模式

返回 HTML 片段

服务器端点返回 HTML,而不是 JSON:

# Flask example
@app.route('/search')
def search():
    q = request.args.get('q', '')
    results = search_database(q)
    return render_template('_search_results.html', results=results)
响应标头

htmx 可识别以下特殊标头:

标头用途
HX-Location客户端重定向(带上下文)
HX-Push-Url将 URL 压入历史记录
HX-Redirect完整页面重定向
HX-Refresh刷新页面
HX-Reswap覆盖 hx-swap 值
HX-Retarget覆盖 hx-target 值
HX-Trigger触发客户端事件
HX-Trigger-After-Settle在 settle 后触发
HX-Trigger-After-Swap在 swap 后触发
检测 htmx 请求

检查 HX-Request 标头,以区分 htmx 请求和常规请求:

if request.headers.get('HX-Request'):
    return render_template('_partial.html')
else:
    return render_template('full_page.html')

事件

关键事件
事件触发时机
htmx:load元素加载到 DOM 中时
htmx:configRequest发送请求前(修改参数/请求头)
htmx:beforeRequestAJAX 请求发送前
htmx:afterRequestAJAX 请求完成后
htmx:beforeSwap内容交换前
htmx:afterSwap内容交换后
htmx:afterSettleDOM 稳定后
htmx:confirm确认对话框显示前
htmx:validation:validate自定义验证钩子
事件处理

使用 hx-on*:

<button hx-get="/data"
        hx-on:htmx:before-request="console.log('Starting...')"
        hx-on:htmx:after-swap="console.log('Done!')">
  Load
</button>

使用 JavaScript:

document.body.addEventListener('htmx:configRequest', function(evt) {
  evt.detail.headers['X-Custom-Header'] = 'value';
});

安全最佳实践

  1. 转义所有用户内容:通过服务端模板转义防止 XSS
  2. 使用 hx-disable:阻止 htmx 处理不可信内容
  3. 限制请求来源:
    htmx.config.selfRequestsOnly = true;
    
  4. 禁用脚本处理:
    htmx.config.allowScriptTags = false;
    
  5. 包含 CSRF 令牌:
    <body hx-headers='{"X-CSRF-Token": "{{ csrf_token }}"}'>
    
  6. 内容安全策略:叠加浏览器级保护

配置

关键的 htmx.config 选项:

htmx.config.defaultSwapStyle = 'innerHTML';
htmx.config.timeout = 0; // Request timeout (0 = none)
htmx.config.historyCacheSize = 10;
htmx.config.globalViewTransitions = false;
htmx.config.scrollBehavior = 'instant'; // or 'smooth', 'auto'
htmx.config.selfRequestsOnly = false;
htmx.config.allowScriptTags = true;
htmx.config.allowEval = true;

或通过 meta 标签:

<meta name="htmx-config" content='{"selfRequestsOnly":true}'>

扩展

加载扩展
<script src="https://unpkg.com/htmx-ext-<name>@<version>/<name>.js"></script>
<body hx-ext="extension-name">
常见扩展
  • head-support:合并不同请求中的 head 标签信息
  • idiomorph:形态转换式交换(保留元素状态)
  • sse:支持服务器发送事件
  • ws:支持 WebSocket
  • preload:预加载内容
  • response-targets:基于 HTTP 状态码进行目标定位

调试

启用日志记录:

htmx.logAll();

在 Network 标签页中检查请求头:

  • HX-Request: true
  • HX-Target: <target-id>
  • HX-Trigger: <trigger-id>
  • HX-Current-URL: <page-url>

渐进增强

为优雅降级构建结构:

<form action="/search" method="POST">
  <input name="q"
         hx-get="/search"
         hx-trigger="keyup changed delay:300ms"
         hx-target="#results">
  <button type="submit">Search</button>
</form>
<div id="results"></div>

非 JavaScript 用户会提交表单;JavaScript 用户则会使用 AJAX。

第三方集成

在 htmx 加载的内容上初始化库:

htmx.onLoad(function(content) {
  content.querySelectorAll('.datepicker').forEach(el => {
    new Datepicker(el);
  });
});

对于通过编程方式添加的 htmx 内容:

htmx.process(document.getElementById('new-content'));

常见注意事项

  1. ID 稳定性:保持元素 ID 稳定,以便使用 CSS 过渡效果
  2. 交换时机:默认交换延迟为 0ms;使用 swap:100ms 实现过渡效果
  3. 事件冒泡:htmx 事件会冒泡;使用 event.detail 获取数据
  4. 表单数据:请求中只包含带有 name 的输入项
  5. 历史记录:历史快照存储的是 innerHTML,而非完整 DOM 状态

开发环境要求

htmx 需要 HTTP(而非 file://)

直接从文件系统打开 HTML 文件(file:// URL)时,htmx 将无法运行。这会导致 htmx:invalidPath 错误,原因如下:

  • 浏览器会阻止来自 file:// URL 的跨源请求
  • htmx 需要向端点发送 HTTP 请求

解决方案:始终通过 HTTP 服务端提供 htmx 应用:

# Simple Python server (recommended for development)
python3 -m http.server 8000

# Or create a custom server with API endpoints
python3 server.py
最简开发服务端模式

对于 htmx 示例和原型,请创建一个简单的 Python 服务端,以便:

  1. 提供静态文件(HTML、CSS、JS)
  2. 提供返回 HTML 片段的 API 端点
from http.server import HTTPServer, SimpleHTTPRequestHandler
from urllib.parse import urlparse, parse_qs

class HtmxHandler(SimpleHTTPRequestHandler):
    def do_GET(self):
        path = urlparse(self.path).path

        if path.startswith("/api/"):
            # Return HTML fragment
            self.send_response(200)
            self.send_header("Content-Type", "text/html")
            self.end_headers()
            self.wfile.write(b"<div>Response HTML</div>")
        else:
            # Serve static files
            super().do_GET()

HTTPServer(("", 8000), HtmxHandler).serve_forever()

实践经验

使用 CSS 加载指示器

使用纯 CSS 加载动画,而非图像文件,以获得更好的性能:

<button hx-get="/api/slow"
        hx-indicator="#spinner">
    Load
    <span id="spinner" class="spinner htmx-indicator"></span>
</button>

<style>
.htmx-indicator { display: none; }
.htmx-request .htmx-indicator { display: inline-block; }

.spinner {
    width: 20px;
    height: 20px;
    border: 2px solid #f3f3f3;
    border-top: 2px solid #3d72d7;
    border-radius: 50%;
    animation: spin 1s linear infinite;
}
@keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}
</style>
使用合适的触发器进行输入搜索

使用 input changed 而非 keyup changed,以获得更好的用户体验(可捕获粘贴和自动填充):

<input type="search"
       name="q"
       hx-get="/api/search"
       hx-trigger="input changed delay:300ms, search"
       hx-target="#results">

search 触发器会处理搜索输入框的清除按钮(X)。

轮询时以自身为目标

对于会替换自身的元素(轮询),请使用 hx-target="this":

<div hx-get="/api/time"
     hx-trigger="load, every 2s"
     hx-target="this"
     hx-swap="innerHTML">
    Loading...
</div>
使用 closest 更新行

对于每行都有独立更新按钮的列表项:

<li id="item-1">
    <span>Item 1</span>
    <button hx-get="/api/update-item/1"
            hx-target="closest li"
            hx-swap="outerHTML">
        Update
    </button>
</li>

服务端会返回完整的 <li> 元素,并保留其中新的 htmx 属性。

事件属性语法

hx-on:: 语法使用双冒号表示 htmx 事件:


<button hx-on::before-request="console.log('starting')">


<button hx-on:htmx:before-request="console.log('starting')">
组合多个触发器

使用逗号分隔触发器:

<div hx-get="/api/data"
     hx-trigger="load, every 5s, click from:#refresh-btn">
带加载状态的表单 POST

组合使用 hx-indicator 和 hx-disabled-elt,以提供完整的用户体验:

<form hx-post="/api/submit"
      hx-target="#result"
      hx-indicator="#spinner"
      hx-disabled-elt="find button">
    <input name="email" required>
    <button type="submit">
        Submit
        <span id="spinner" class="spinner htmx-indicator"></span>
    </button>
</form>

其他资源

如需详细参考,请查阅:

评论

支持 Markdown