在这篇文章中,我会告诉你一下一个很棒的 Alpine.js 替代方案,它能帮助你在服务器端 HTML 的实现中实现相同的功能(甚至更多功能)。
几个月前我写了一篇类似的关于HTMX的文章,现在我终于可以写写使用HMPL而不是Alpine.js带来的好处了。
在我看来,这个想法比到目前为止所做的更有潜力。
那咱们开始吧!
……
👀 咱们要怎么比较呢?首先,我们要明确的是,所有的比较都将在与服务器连接相关的背景下进行。在这里,我们不会讨论常规客户端功能的选项。虽然所有的操作都是在客户端完成的,但是还是有一些显著的不同之处。
在比较过程中,我们将考虑以下参数。
- 渲染,
- 自定义服务器请求,
- 磁盘空间,
- 模块的内部实现。
我们还将谈到支持、安装的简便以及其他一些方面。
上色
在现今的 web 开发领域里,选择界面渲染方式起着关键作用。我们将考虑两种根本不同的方法:HMPL 的模板编译和 Alpine.js 的声明式方法。这些技术提供了与用户界面交互的不同方法,各自具有其优势和特点。
HMPL
HMPL使用客户端模板编译技术,这意味着模板标记会被转换为JS函数,用于动态生成HTML。
const templateFn = hmpl.compile(`
<div>
{{#request
class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="/api/my-component"
indicators=[
{
trigger: "pending",
content: "<p>Loading...</p>"
}
]}}
{{/request}}
</div>
`);
// 用法
const component = templateFn();
切换到全屏,退出全屏
模板编译成一个可执行的函数,一次,可以执行如下:
(这里省略了冒号后面的内容,如果有具体内容,请提供完整句子以便翻译。)
-
创建一个高效的渲染方法
-
缓存结果以备重用
- 准备 DOM 结构以便未来进行更新
Alpine.js (一个前端框架)
Alpine.js 采用了声明式的风格——你可以在 HTML 中直接通过属性(例如 x-data
, x-show
, x-text
等)描述行为。
<div x-data="{ user: null, loading: true }"
x-init="fetch('/api/user')
.then(r => r.json())
.then(data => { user = data; loading = false; })">
<template x-if="loading">
<div>正在加载...</div>
</template>
<div x-show="user" class="用户卡片">
<h2 x-text="user.name"></h2>
<span x-show="user.isPremium" class="badge">尊享会员</span>
</div>
</div>
点全屏,点退出全屏
这里是对 Alpine.js 代码工作原理的四点简洁概述:
-
该组件直接在 HTML 中使用 Alpine.js 的指令定义其响应状态和数据获取的逻辑。
-
x-init
钩子函数在组件挂载后会自动加载数据,同时处理请求和状态更新。 -
Alpine 的
x-if
和x-show
指令根据加载情况和数据是否可用动态渲染 UI。 - 模板在状态变化时自动重新渲染,使用户界面始终与底层数据保持同步,而无需手动修改 DOM。
我们来比较一下
HMPL 提供了一种内置请求处理和模板功能的自动化渲染方法,却需要编译,非常适合复杂的数据驱动组件。Alpine.js 通过显式的 fetch 调用和响应式状态管理来提供更透明的控制,更适合轻量级交互。选择取决于项目需求,比如 HMPL 在结构化的模板方面表现出色,而 Alpine.js 在快速原型设计和简单动态元素方面更胜一筹。
🪄 自定义服务器请求
在构建动态 web 应用时,高效管理服务器请求至关重要。与 HTMX 相比,定制选项很少,定制过程类似于在 JSX 中直接插入 JS 代码。当然,这受限于模板。
<div x-data="{ user: null, error: null }"
x-init="fetch('/api/user')
.then(r => r.json())
.then(data => user = data)
.catch(e => error = e)">
初始化时从 '/api/user' 获取用户数据,处理结果并设置用户数据或错误信息。
</div>
切换到全屏模式或退出全屏
用这种方法,我们发现它非常方便,但是问题在于它很可能更像是eval
,或者说按照预先设定的模式来处理模板。
这种方法有几个严重的缺点,因为每出一个新版本的JS,就会添加许多新特性,这种方法需要支持这些新特性。从jsx的角度来看,这一点是有道理的,因为实际上这是React的基础,而在这里,它只是一个可以通过<script>
标签引入的模块。我们变得严重依赖于版本更新,这使得这种方法虽然方便,但并不十分实用。
在HMPL中,采用了一种不同的方式,我们可以直接在模板中编写我们需要的最小部分,那里定义了我们如何接收HTML和其他内容的方法和路由,但整个js部分则直接写在js文件中。
const templateFn = hmpl.compile(...);
const elementObj = templateFn(({
request: { event, clearInterval }
})=>{
// 清除定时器(如果存在)
clearInterval?.();
return {
mode: "cors",
cache: "no-cache",
credentials: "same-origin",
headers: {
"Content-Type": "text/html",
},
redirect: "follow",
get: (prop, value) => {},
referrerPolicy: "no-referrer",
body: new 格式化数据(event.target, event.submitter),
}
});
点击全屏进入全屏,点击退出全屏退出
在这里,js部分和html部分有明确的分离。因此,你无需依赖模块的新版本,因为在描述js时,你所需要的一切都可以直接写进去,不论是1.0.0还是3.0.1。
此外,在使用 Alpine.js 的这种语法时,存在 XSS 注入的风险。虽然没有真正的 eval
,但这种受限的语法可以防止大多数危险,仍然存在风险。这不只是它的问题,也是所有模块包括 HMPL 在内的普遍问题。顺便说一下,在 HMPL 中有一个选项可以使用 DOMPurify
对传入的 HTML 进行 sanitize
(净化)处理。
📁 磁盘里的空间
这里可能是最简单和最直观的比较方式。只需写相同的代码然后比较(但必须明白,如果应用程序较大,代码量也会随之呈指数级增长)。
Alpine.js
document.querySelector(
"#app"
).innerHTML = `<div x-data="{ count: 0, l() { fetch('/a').then(r => r.text()).then(d => this.c = d)}}"><button @click="l()">点我!</button><div>点击数: <span x-text="c"></span></div></div>`;
点击此处全屏观看 点击此处退出全屏
HMPL
document
.querySelector("#app")
.append(
hmpl.compile(
`<div><button>点击</button><div>点击数: {{#r src= "/api/clicks" after="click:button" }}{{/r}}</div></div>`
)().response
);
全屏模式退出全屏
这里,很明显写下来会更简洁。
但是,如果你仍然需要全面的测试,有一个包含测试的仓库。还有一个第二版本的模块,它更简洁,但第三版的核心内容没有变化。
看看这张图:
无论是大型还是小型项目,这些数据都最接近实际结果。
⚙️ 模块内部的奥秘
这指的是发送请求的技术。我不会详细解释RegExp
如何处理模板,也不会解释数组如何保存元素——这些内容对客户端来说可能不太感兴趣。
我们将主要讨论的是 XMLHTTPRequest
和 fetch
支持。关于这个话题,我们有以下几点:
Alpine.js
Alpine.js 完全兼容 fetch
和 XMLHTTPRequest
等方法。这意味着在某些情况下,可以通过发起更精确的请求来实现更多功能,比如说通过 overrideMimeType
等。
<div x-data="{ data: null }"
x-init="
const xhr = new XMLHttpRequest();
xhr.overrideMimeType('text/html');
xhr.open('GET', '/api/data');
xhr.onload = () => { data = JSON.parse(xhr.responseText) };
xhr.send();
">
<div x-text="data?.message || '正在加载...'"></div>
</div>
退出全屏 进入全屏模式
尽管这种类型的eval
方法存在不足之处,它仍然是一个非常方便的选择来实现这样的切换,因此对于 Alpine.js 来说可以得到好评。而在 HTMX 中,你只能使用 XMLHTTPRequest
并且无法更改。
HMPL
不幸的是(或者说还好),HMPL 不支持 XMLHTTPRequest
请求,一切都是通过 fetch
实现的,并且 fetch
不能被替换。请求都在模块内部发生,你只需要描述就行:
<div>
<button data-action="increment" id="btn">点击!</button>
<div>点击数:{{#request
class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="/api/clicks"
after="click:#btn"
}}{{/request}}</div>
</div>
全屏,退出全屏
这也带来了一个优点,因为 then
和 catch
以及其他功能都在模块内部实现,所以你不必像在 jsx 中那样写代码。
当然,你可以根据项目需要选择最适合的方法。我们刚刚看了与服务器请求相关的部分,但 Alpine.js 还有其他部分,使它成为一种轻量级的框架。不过,如果只考虑与服务器交互的部分,我还是建议(当然没有人会对此有疑问 👽)使用 HMPL,因为它更适合这个需求。所以,这两种选择都很不错!
🔗 模块连接:HMPL - https://github.com/hmpl-language/hmpl(HMPL 是一个开源项目,详情请见链接)
阿尔派恩·js - https://github.com/alpinejs/alpine
非常感谢大家读了这篇文章!
共同學(xué)習(xí),寫下你的評論
評論加載中...
作者其他優(yōu)質(zhì)文章