减少使用 Htmx,效果更佳
减少 Htmx 的使用,效果更佳
2024 年 10 月 02 日
自从我用 htmx 编写我的第一个生产环境 Web 服务以来,已经两年了。两年时间不算长,但早期的迹象表明,我用 htmx 编写的软件项目,用户体验更好,维护起来也比它们替代的软件项目容易几个数量级。它们可能比我写过的任何其他东西(到目前为止)都更有用。非常好!
像任何新工具一样,尤其是像 htmx 这样迅速流行起来的工具,关于如何最好地使用它,存在不同的观点。我的方法——我认为要实现上述结果是必须的——要求你内化 htmx 肯定暗示,但没有强制执行的东西:尽可能使用纯 HTML。
一旦你掌握了窍门,htmx 就会开始把你推向这个方向,你会越来越少地使用 htmx。但这需要一种思维方式的转变,特别是如果你不习惯使用 HTML 功能构建页面行为。
我们应该如何使用 htmx?
在我看来,大多数网站应该使用 htmx 来实现以下两种情况:
- 用户不希望在刷新(或新页面加载)时看到的更新
- 刷新(或新页面加载)时也会出现的更新
其他一切都应该使用常规链接和常规表单,进行标准的、整页导航。
你目前还需要 htmx(或类似的库)来支持常规表单上的 PUT
和 DELETE
方法。更多信息请参见注释部分。
假设你要创建一个显示今天棒球比赛的网站,并且你希望它实时更新统计数据。这是我会如何处理它的。
网站的首页应该显示所有正在进行的比赛,并显示每个比赛的实时比分。每个实时比分牌都使用 htmx 以固定的时间间隔轮询服务器以获取更新。点击比分牌标题(这是一个普通的 <a>
链接)会将你带到该游戏自己的 URL 页面。该游戏页面不仅包含比分,还包含投球数、游戏的完整统计数据等等;这些也使用 htmx 进行更新。
这里的想法是,网站仍然具有良好的 URL 结构,该结构由核心浏览器功能管理,而交互性则小心地分层,并进行有针对性的更新。具体哪些内容需要有针对性的更新,哪些内容需要新页面,取决于你要构建的内容,但你应该有一种区分它们的心智模型。
使用 htmx 来启用特定的、孤立的功能——而不是让它驱动整体体验——可以说像使用库一样使用它,而不是像使用框架一样使用它。
不幸的是,许多初学者指南建议你可以通过使用 hx-boost
“升级”所有链接来轻松入门。我不同意这种做法。虽然 htmx 对于有针对性的页面更新来说非常棒,但我强烈建议不要使用它来接管 所有 页面导航。
什么是 hx-boost?
hx-boost
是一个属性,可以将“常规”链接转换为“增强”链接:
<!-- 常规链接 -->
<a href=example.com>Example</a>
<!-- 增强链接 -->
<a href=example.com hx-boost=true>Example</a>
当点击“增强”链接时,htmx 不会进行完整的页面导航,而是会向链接的 URL 发出 HTTP 请求,并将页面的 <body>
替换为响应的内容。理论上,这感觉更“流畅”,因为它只重新绘制页面的部分内容,模仿了单页应用程序 (SPA) 的感觉。
hx-boost 有什么问题?
它解决的问题可以用其他方法更好地解决,而且它本身会产生很多问题。
使用 hx-boost
足够长的时间,就会出现问题。你点击后退按钮,只会看到部分页面更新;你刷新页面,它会变成空白;你正在使用的另一个库会崩溃;元素会以你意想不到的方式进入或退出 DOM。
从编码的角度来看,这不是任何人的错——hx-boost
承诺的功能是不可能实现的。 hx-boost
使用 JavaScript History API,该 API 旨在让单页应用程序 (SPA) 连接到会话管理功能,最显著的是浏览器的前进和后退按钮。在实践中,这几乎不可能做到正确,并且实现起来非常烦人,以至于 htmx 的创建者 Carson Gross 为此制作了一个梗图。
核心问题在于,通过正常的页面导航,你点击的每个链接都会重置 JavaScript 环境并触发完整的页面生命周期事件。这是一件非常好的事情。这意味着你包含在页面上的每个附加脚本都有一个标准化的方式来跟踪发生了什么。如果你用临时的、基于脚本的导航替换此过程,则会删除页面上每个其他库访问该通用语言的权限。你还会启动一个长期存在的 JavaScript 环境,该环境可能会最终进入某种不良状态。
这个问题是 SPA 固有的,并且只能通过不编写 SPA 来解决。所以不要使用将你的 htmx 站点变成 SPA 的属性。
我应该怎么做呢?
使用常规链接。 hx-boost
承诺增强常规链接的体验;跳过中间人,直接使用它们。
常规链接提供更好的用户体验和开发者体验,完全停止使用 hx-boost
。
hx-boost 的好处呢?
你第一次使用 hx-boost
时,会感觉页面“无缝”更新非常神奇,但是你可以使用浏览器功能来实现所有相同的好处,而不会带来麻烦。
发送缓存头以在页面加载之间重复使用 CSS 和 JS
基本上所有静态文件服务器都支持 ETag。当服务器向浏览器发送文件时,它还可以发送一个唯一的字符串,该字符串标识该文件的 该版本。下次你尝试加载该文件时(例如,在导航到使用相同 CSS 的新页面之后),浏览器会询问你的服务器“它仍然是这个吗?”,并发送该 ETag 字符串。如果文件没有更改,服务器只会响应 304 Not Modified 标头,并且浏览器会使用其缓存版本。
在大多数情况下,此过程几乎不会增加你的加载时间。浏览器无论如何都必须与服务器通信以获取下一个页面上的任何信息,并且它重复使用相同的 TCP 连接来执行此操作。 GET -> 304
来回是在已经打开的套接字上的一些额外字节。
但是,如果你甚至不希望浏览器 询问,则可以通过设置缓存控制头来做到这一点。
这是我在所有使用 htmx 的网站中加载 htmx 的方式。我们将使用 1.9.3 版本作为示例。我在标头中包含一个这样的脚本标签:
<script src="/htmx-1.9.3.js"></script>
当用户第一次加载页面时,他们的浏览器会向我的服务器发送 HTTP 请求 GET /htmx-1.9.3.js
。服务器将发送回类似这样的响应:
HTTP/2 200
accept-ranges: bytes
cache-control: public, max-age=31536000
last-modified: Fri, 06 Sep 2024 22:09:43 GMT
etag: W/"24b79-191c962d458"
content-type: application/javascript; charset=UTF-8
这意味着:“从我的服务器下载 htmx 1.9.3 仅一次,然后在 整整一年 内都不要再问我了。” 从那时起,一年内,每次该浏览器在同一域中加载包含 htmx 1.9.3 的页面时,浏览器甚至都不会询问服务器,它只会使用保存的版本。如果我想将每个人都升级到新版本,我只需更改 URL 中的版本号:
<!-- 从这个... -->
<script src="/htmx-1.9.3.js"></script>
<!-- ...到这个 -->
<script src="/htmx-1.9.4.js"></script>
下次我的每个用户加载该页面时,他们的浏览器会看到该页面需要一个它没有的新文件,并再次向服务器询问它。
如果我什至不想包含版本号——也许是像 stylesheet.css
这样的文件——我可以使用 URL 查询。
<!-- 浏览器会将这两个文件视为不同的文件,
但你的服务器会知道它们是相同的 -->
<link rel="stylesheet" href="/stylesheet.css">
<link rel="stylesheet" href="/stylesheet.css?id=1">
同样,基本上每个静态文件服务器都支持这种模式。
使用同源链接来获取部分页面更新
此网站 (unplannedobsolescence.com) 仅使用常规链接,如果你点击顶部周围,你会看到标头在很大程度上保持不变。现在,对于具有相同结构和样式表(如我上面向你展示的)的页面的同源链接,这会自动发生。
这是 Chrome 团队宣布此功能:
尝试 Chrome Canary(Chrome 76)中的 Paint Holding,让我们知道你的想法。开发者不必担心对他们的页面进行任何修改即可利用它。
Chrome 76 于 2019 年,四年前发布。每个使用常规链接构建其网站的人都获得了对其网站的重大、免费的性能升级,并推送给数十亿人;对于那些试图用 JavaScript 替换该功能的人来说,情况并非如此。
利用 HTML 获取免费的性能升级
使用标准 HTML 功能允许浏览器以 JavaScript 绝对无法做到的方式优化性能和 UX。每次浏览器更新时,它都会在加载、解析和呈现网页方面变得更好。页面历史记录、加载栏、后退按钮、取消按钮、URL 栏等,每次都默认在每个浏览器上正常工作。
那么 hx-boost 为什么存在呢?
htmx 是在 SPA 似乎是 Web 开发不可避免的未来的时期创建的。为了在该环境中竞争,它必须证明它可以复制大多数人认为 SPA 的杀手级功能:不重新绘制整个页面。如果这曾经是必要的——我对此表示怀疑——现在肯定不是必要的了。
现在 htmx 已经在大脑生态系统中证明了自己,并且开发人员开始再次信任多页网站,我认为现在是时候提出更难但最终更具影响力的案例:HTML 和 HTTP 具有构建绝大多数网站功能所需的功能;它们比脚本替代方案更容易使用,并且它们的使用寿命更长,维护更少。
顺便说一句,用户总是信任多页网站。我们现在才开始再次倾听他们的声音。
构建好的网站需要放弃 hx-boost
的糖分,并说“这是使用缓存头的方法”。
是否有时我应该使用 htmx 来制作 SPA?
我的朋友 Aram 制作了一个名为 Song Obsessed 的网站,该网站具有一个持久的音乐播放器,即使你在网站上导航,它也能保持其状态。 hx-boost
非常适合这种情况,因为它允许你将你的网站构建为一系列 URL;你可以只将 hx-boost
贴在所有内容上,并且稍作调整,你可以让 htmx 单独保留音乐播放器,同时替换页面的其余部分。你仍然失去了硬页面加载固有的可靠性,但是你获得了真正新颖的功能作为交换,在这种情况下,这是一个很好的折衷方案。在 HTML 具有 API 以保持实时内容在页面导航中持久存在之前,需要一些 SPA 功能才能实现这一点。
SPA 是一种 高级 工具,该行业欺骗性地将其宣传为一种简单的工具。 Aram 是一位经验丰富的 Web 开发人员,他使用 hx-boost
来突破页面导航可能性的界限;大多数只想在他们的网页上添加一些交互性的人应该坚持使用最简单的可用工具:一个常规链接。
感谢 Carson Gross 对本文草稿的反馈。评论可在 lobste.rs 上找到
注释
- Triptych——Carson 和我正在研究的 HTML 提案——将使 htmx 对于我在此处描述的网站类型变得过时。更高级的 htmx 功能,例如 David Guillot 和 Contexte 使用的那些功能,在可预见的未来仍然需要 htmx。
- 要将
PUT
(和DELETE
)支持添加到带有 htmx 的“常规”表单,请将hx-put
添加到表单,然后让服务器响应状态代码 200 和HX-Redirect
标头;htmx 将告诉浏览器进行整页导航,而不是进行部分页面替换。这模仿了POST-Redirect-GET
模式,但使用标头而不是 303 响应。 - 理想情况下,htmx 应该能够拦截正常的 303 响应并使用
location
标头,而不是自定义标头,但由于fetch
API 的限制(manual
重定向 隐藏所有标头),它不能。老实说,我不太明白这有什么安全目的,但这有点令人遗憾,因为它意味着 你不能为PUT
和DELETE
表单创建一个合适的 polyfill。 - turbo 和 datastar 都使用 Carson 的 idiomorph 算法将更新合并到页面中,但 Carson 最终拒绝了 idiomorph 作为 htmx 的默认合并算法,因为它太复杂了——即使他才是最初创建它的人!默认的 htmx 交换策略 是直接清除
innerHTML
中的内容并用响应替换它——与默认页面导航清除环境并为你提供一个全新的环境没有什么不同。简单有效。