在本文中,我將向您介紹 Alpine.js 的絕佳替代品,它將幫助您使用伺服器端 HTML 完成相同的操作(甚至更多)。
幾個月前,我寫了一篇關於 HTMX 的類似文章,現在我終於可以寫一下使用HMPL而不是Alpine.js可以獲得的好處。
我認為這個想法比迄今為止所做的有更大的潛力。
好吧,讓我們開始吧!
首先,所有比較都將在伺服器連線的上下文中進行。我們不會在這裡考慮常規客戶端功能的選項。儘管一切都在客戶端完成,但仍然存在嚴重的差異。
比較時我們將考慮以下參數:
渲染
自訂伺服器請求
磁碟空間
模組內部是什麼樣的
我們還將討論支援、安裝簡易性和其他一些要點。
在現代Web開發中,介面渲染方式的選擇扮演著關鍵的角色。我們將考慮兩種根本不同的方法:HMPL 中的範本編譯和 Alpine.js 的宣告式方法。這些技術為使用者介面的工作提供了不同的範例,每個範例都有其自身的優點和實作特點。
HMPL
HMPL 使用客戶端模板編譯,這表示標記被轉換成動態產生 HTML 的 JavaScript 函數。
const templateFn = hmpl.compile(`
<div>
{{#request
src="/api/my-component"
indicators=[
{
trigger: "pending"
content: "<p>Loading...</p>"
}
]}}
{{/request}}
</div>
`);
// Usage
const component = templateFn();
該模板被編譯一次為可執行函數,該函數:
建立優化的渲染函數
快取結果以供重複使用
為將來的更新準備 DOM 結構
Alpine.js
Alpine.js 提供了一種聲明式風格-您可以透過屬性( x-data
、 x-show
、 x-text
)直接在 HTML 中描述行為。
<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>Loading...</div>
</template>
<div x-show="user" class="user-card">
<h2 x-text="user.name"></h2>
<span x-show="user.isPremium" class="badge">Premium</span>
</div>
</div>
以下是 Alpine.js 程式碼工作原理的簡明 4 點細分:
此元件使用 Alpine.js 指令直接在 HTML 標記中定義其反應狀態和資料擷取邏輯。
x-init
鉤子在元件安裝時自動觸發資料加載,管理請求和狀態更新。
Alpine 的x-if
和x-show
指令根據載入狀態和資料可用性處理動態 UI 渲染。
當狀態改變時,模板會自動重新渲染,使 UI 與底層資料保持同步,而無需手動操作 DOM。
比較
HMPL 提供了一種具有內建請求處理和模板的自動渲染方法,非常適合複雜資料驅動的元件但需要編譯。 Alpine.js 透過明確取得呼叫和反應狀態管理提供更透明的控制,更適合輕量級互動。選擇取決於專案需求 - HMPL 擅長結構化模板,而 Alpine.js 則擅長快速原型設計和簡單的動態元素。
在建立動態 Web 應用程式時,有效地管理伺服器請求至關重要。與 HTMX 的自訂範圍有限不同,Alpine.js 提供了更廣泛的自訂範圍,自訂本身幾乎就像在 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)">
</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 FormatData(event.target, event.submitter),
}
});
這裡 js 部分和 html 部分有明確的分離。因此,你不會依賴模組的新版本,因為描述 js 時所需要的一切,你總是可以在那裡寫,即使是 1.0.0 版本,即使是 3.0.1 版本。
此外,在具有此類語法的 Alpine.js 中存在 XSS 注入的風險。是的,當然沒有真正的eval
,但是相同,但具有有限的語法,可以防止大多數危險,但仍然存在危險。這不僅是它的問題,也是所有模組(包括 HMPL)的問題,但順便說一下,在 HMPL 中,有一個選項可以sanitize
來自DOMPurify
傳入 HTML。
這可能是比較時最簡單、最容易理解的事。只需編寫相同的程式碼並進行比較(但我們必須明白,如果應用程式很大,那麼程式碼也會呈指數增加)。
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()">Click!</button><div>Clicks: <span x-text="c"></span></div></div>`;
HMPL
document
.querySelector("#app")
.append(
hmpl.compile(
`<div><button>Click!</button><div>Clicks: {{#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 || 'Loading...'"></div>
</div>
儘管這種eval
方法有缺點,但像這樣切換仍然是一種非常方便的選擇,並且為此 Alpine.js 可以獲得加分。在同一個 HTMX 中,您將只使用XMLHTTPRequest
而無法變更它。
HMPL
不幸的是(或者更好的是,幸運的是),HMPL 不支援XMLHTTPRequest
請求,並且一切都建立在fetch
之上,這是不可替代的。請求發生在模組內部,您只需描述它們:
<div>
<button data-action="increment" id="btn">Click!</button>
<div>Clicks: {{#request
src="/api/clicks"
after="click:#btn"
}}{{/request}}</div>
</div>
這也有一個好處,因為then
、 catch
和其他東西都是在模組內部實現的,所以你不必像在 jsx 中那樣寫它。
當然,您可以在專案中使用最適合您的方法。假設我們在文章中研究了與伺服器請求相關的部分,但 Alpine.js 還有另一部分,這使得它成為一種輕量級框架。但是,如果我們使用伺服器,那麼我建議(誰會懷疑呢👽)使用 HMPL,因為它更適合這項任務。所以,這兩個選擇都相當不錯!
HMPL - https://github.com/hmpl-language/hmpl
Alpine.js - https://github.com/alpinejs/alpine
非常感謝大家閱讀這篇文章!