Datastar:面向未来的 Web Framework?

Chris Malek - 技术文章

首页 关于 标签 文章

Datastar:面向未来的 Web Framework?

2025-01-15 [最后修改时间:2025-01-20] /posts/data-star-first-impressions/ map[email:chris.malek@cedarhillsgroup.com location:San Diego, CA name:Chris Malek]

目录

# TLDR Datastar 是一个新的 hypermedia framework,它可以更简单高效地构建实时 web 应用程序。它优先考虑服务器端逻辑,使用 "signals" 来自动更新 UI,并利用 Server-Sent Events 实现闪电般的速度。如果你正在寻找传统 JavaScript frameworks 或 HTMX 的精简替代方案,Datastar 值得探索。 但是,它要求你以全新的视角进行 web 开发,拥抱服务器驱动的架构和 reactive programming。

# 介绍 我最近一直在深入研究 hypermedia,寻找可以用来构建新产品并帮助客户快速创建概念验证和 web 工具的 frameworks 和 libraries。 在撰写本文时,HTMX 基本上受到了 hypermedia 领域的所有关注。它的演示效果非常好,示例也很棒。但是,本文不是关于 HTMX 的。 我相信 hypermedia 和 HTMX 提供了一个有希望的方向,但是,当我尝试使用 HTMX 开发一个新产品时,我感到很苦恼,因为在搞清楚项目结构、HTML 结构(结合过多的 HTMX tags)以及意识到 HTMX 无法交互式地处理前端时遇到了挑战,为此,你必须引入像 AlpineJS 这样的东西。(我说过我讨厌 javascript 吗?)。HTMX 很酷,但我认为在你用它开始一个新项目之前,你可能需要看看 Datastar。 在评估 HTMX 时,我过去曾看过 Datastar,但在我第二次查看并开始感觉到 HTMX 的一些不足之处之前,我并没有理解它相对于 HTMX 的潜力。你的结果可能会有所不同。

## 我的背景 首先,让我们了解我带有偏见的观点。每个人的背景都不同,我认为了解我的出发点非常重要。

# 我在寻找什么? 我一直在寻找一个 framework 或一组 libraries 来构建一个现代高效的新产品。我一直在研究 Hypermedia 来做到这一点。 我的大致要求是:

之前我一直在研究 Phoenix,但不想转移到另一种语言。如果你在 google 上搜索 "hypermedia",你会看到很多关于 HTMX 的文章,并且 GitHub 上似乎有很多使用它的项目。我认为它是目前最流行的 hypermedia library。我一直在努力研究 Live Golang Library

## 我对 HTMX 的第一印象 当然,我研究了 HTMX,并开始对它的潜力感到兴奋。因此,在部署了一些客户用于一些一次性任务的相当简单的 "web 工具" 之后,我开始开发一个真正的应用程序。 我为自己和客户创建了一些简单的工具。我不需要任何前端状态或交互。我只是从服务器更新 UI,其中大部分是由于 field change 或点击而产生的新 HTML fragments。在这些简单的情况下,HTMX 可以很好地处理这些问题。 在我对更复杂的应用程序进行 HTMX prototyping 时,HTML 代码变成了一团糟,其中一些非平凡的部分使用了 HTMX tags。我发现自己很难理解项目结构和大量的 HTMX tags,以及如何管理前端和后端。我还需要一些前端功能和状态,而 HTMX 并不是为处理这些而设计的。使用 HTMX,你必须导入 AlpineJS,我开始出现荨麻疹,因为我讨厌 JavaScript 😢。 当我开始处理应用程序中更复杂的部分时,我感觉 HTMX 有点碍事了。 我开始看到一个巨大的 lint ball 正在建立。我的直觉告诉我,我正在朝着错误的方向前进。我按下了暂停按钮,开始寻找替代方案,因为我感觉自己工作得太辛苦了,代码变得太复杂了。

# 重新审视 Datastar 和一个转折点 我一直忙于在客户现场工作,我暂停了对 HTMX alterntives 的研究或一些 non-trival TODO application examples。然后有一天,在我的 YouTube feed 上,我看到了一个 对 Datastar 创建者的采访,它让我再次审视 Datastar。如果我没有使用 HTMX 的一些经验,我就不会 "准备好" 接受那次采访,并且理解他所提出的一些观点。 以下是 AI 生成的采访要点。我用粗体标出的那些要点让我再次审视 Datastar:

  1. Delaney 探索了 HTMX 之外的 hypermedia,重点关注实时应用程序。
  2. HTMX 被视为解决了 1999 年的 hypermedia 问题,但并没有推动当前的界限。
  3. Server-sent events (SSE) 提供有效的实时更新,超越了传统的 AJAX。
  4. Datastar 是 HTMX 的模块化替代方案,旨在简化代码并提高性能。
  5. 游戏开发的效率可以激发 web 开发的速度和能力。
  6. SSE 可以实现微秒级的更新,挑战了 HTMX 中轮询的局限性。
  7. event-driven architecture 对于可扩展、高效的实时应用程序至关重要。
  8. 与 HTMX 相比,Datastar 的声明式方法降低了复杂性。
  9. 强调服务器控制,SSE 可以优化和简化 web 交互。
  10. Delaney 提倡一种范式转变,即更智能地使用 hypermedia 技术。

在评估 HTMX 时,我之前曾看过 Datastar 的 documentation。我可能是从 Reddit 上的一些讨论中找到它的。但是,我之前很难掌握它的用途,并且发现它的文档令人困惑且密集。说实话,当我第一次阅读 Datastar docs 时,它只是超出了我的理解能力。 我还没有准备好理解它。它声称是 HTMX 和 AlpineJS 的更好替代品。另一件让我反感的事情是,该项目没有大量的贡献者。但是,在观看了 Datastar 作者的采访后,我意识到他可能有一些深刻的见解,我应该再看一遍。他也是 HTMX 的贡献者。 我最初发现 Datastar 令人困惑的两件事是:

那次采访让我再次查看了 documentation,documentation 已经进行了一些更新。在我花了一些时间阅读和重新阅读文档并查看 examples 后,然后尝试了一些我自己的 "hello world" 示例,然后灵感就来了。 Datastar 可能是我一直在寻找的 library。在我开始剥开一些洋葱皮后,它看起来很有希望。

# 什么是 Datastar? 似乎作者 delaneyj 正在采用 HTMX 和 Hypermedia 的一些基本 primitives,并使它们更容易使用,而 Datastar 是他的答案。至少这是他们的主张。在撰写本文时,我仍在用它创建我的第一个应用程序。我还没有准备好给出完整的评论。但我对它的潜力感到兴奋。 似乎作者也是 Go 的忠实粉丝,这对我很有帮助,因为任何示例和 libraries 都将包含 Go 示例。 首先,让我澄清一下。我不是 Datastar 的专家。我只是刚开始学习它。我也 NOT 是 Datastar 的贡献者或作者。 我没有功劳。我只是想传播关于它的消息,因为我不认为它受到了应有的关注。 根据我目前对 Datastar 的理解,有一些关键概念构成了 Datastar 的基础:

你在 HTML 中包含 Datastar JavaScript library,然后你可以开始使用这些概念来构建你的应用程序。你还需要组织你的服务器来处理 SSE requests 和 GET/POST/PUT/DELETE requests。 你的后端选择并不重要。

## 与 HTMX 比较 我主要将其与 HTMX 进行比较,因为这是我目前的视角,并且 HTMX 受到了大量的关注。

因此,仅从查看依赖项来看,Datastar 为你提供了一个 JavaScript library,它可以处理前端(signals)上的状态,并使 HTML 属性执行 actions (GET/POST/PUT/DELETE) 并处理来自服务器的更新。服务器 100% 负责生成 HTML fragments 和对 signals 的更新。

## 什么是 "Signal",什么是 "Reactive Programming"? 我认为我在最初阅读文档时错过的一件大事是 signal 的概念。这并非由 Datastar 发明,我相信它是使用另一位开发人员的 library 在 Datastar 中实现的。 我相信我只是落后了,你可能已经知道什么是 signal。我将尝试解释它。从根本上理解 signal 代表什么以及可以为你做什么,是 Datastar 力量的源泉。 它使创建用户界面更加简单和可维护。 在我们讨论 signals 之前,让我们先讨论一下 reactive programming,因为它们是相关的。Reactive programming 使你的应用程序代码自动 "react" 数据变化,并将这些变化自动地在应用程序中传播。你不用告诉计算机如何逐步执行操作,而是告诉计算机当数据变化时应该发生什么,计算机会找出如何执行操作。它允许你定义数据源和数据消费者之间的关系。当数据源发生变化时,数据消费者会自动更新。在一个非 reactive 的系统中,当数据源发生变化时,你必须手动更新数据消费者。这通常以 "on-change" Javascript events 和函数的形式来绑定所有数据和 UI。 好吧,好吧,这仍然太多的术语了!!!!

## 通过电子表格理解 Reactive Programming 我认为理解 reactive programming 的最佳方式是考虑电子表格。

电子表格的惊人之处在于它是 reactive 的。 当你更改单元格中的值时,所有依赖的单元格都会自动更新。这是 reactive programming 的本质。在电子表格中,你定义了 "cells" 或数据元素之间的关系,并且底层 framework 会在数据更改时传播更改。这非常强大,可以简化你的代码并使其更易于维护。 这是一个粗略的示意图,其中箭头表示单元格之间的依赖关系: 图表 Datastar 通过 signal 的概念在 web 应用程序中为你提供了一些相同的功能。 我从概念上将 Datastar signal 视为 "cell" 或 HTML elements 之间的链接。我一开始没有建立这种联系。

Signals 是 Datastar 应用程序的一部分。你将 signals 放在页面上,UI 可以自动更新。请参阅 模型绑定示例Signals 更改示例 服务器可以向下发送对 "signals" (电子表格单元格内容) 的更新,甚至可以向下发送更新 UI 的新 HTML fragment。用电子表格术语来说,这就像从服务器添加新的单元格、图表等。 你可以用 signals 做更多的事情。如果你阅读了文档仍然不理解,我建议你重新阅读它们。我不得不阅读它们几次才能理解。

# Actions - GETS/POSTS/PUTS/DELETES 当/如果你开始研究 HTMX,你会看到你可以使用 GET/POST/PUT/DELETE 在服务器上触发 actions。这在 Datastar 中也是一样的。你可以使用 signal 触发这些 actions。 HTMX 和 Datastar 都会触发这个对服务器的请求来更新或获取更新的 UI elements。不同之处在于,Datastar 使用 SSE 将更新发送回客户端。我一直在挠头,直到我开始阅读更多关于它的信息并查看示例。 SSE 非常简单。它只是文本,我以前可能读到过,但由于我没有真正的开发工作经验,所以我不理解。我每天都在使用 HTTP web services,并且对 HTTP 的工作原理有充分的了解。 你可以向 HTML elements 添加一些 "tags" (单击按钮、更改输入等),然后当用户与页面交互时,服务器可以向下发送对 signals 的更新。 来自 Datastar 示例

<div id="contact_1">
 <label>First Name: John</label>
 <label>Last Name: Doe</label>
 <label>Email: joe@blow.com</label>
 <div>
  <button data-on-click="@get('/examples/click_to_edit/contact/1/edit')">
   Edit
  </button>
  <button data-on-click="@get('/examples/click_to_edit/contact/1/reset')">
   Reset
  </button>
 </div>
</div>

在浏览器中运行的 Datastar Javascript library 通过保持打开的连接(直到服务器关闭它)连接到服务器。服务器可以向下发送对 signals 的更新。服务器还可以向下发送更新 UI 的新 HTML fragments。 Datastar 和 HTMX 具有类似的概念,但 Datastar 开箱即用地构建,以使用 element 的 "ID" 处理更新页面的任何部分。这在 HTMX 中是可能的,但需要一些额外的工作/tags。 基本上,Datastar "actions" 可以做 HTMX 可以做的任何事情。

## 理解 SSE - 它只是文本 首先,让我们快速理解 SSE。正如我们将看到的,SSE 只是文本。它不是某种神奇的协议。它只是 HTTP 上的文本,带有一些特殊的 headers,并且浏览器开箱即用地支持它。 Datastar 利用 SSE,并且 Javascript library 以某种方式期望和解释来自服务器的 responses。服务器可以向下发送对 signals 的更新,或将更新 UI 的新 HTML fragments。服务器也可以在完成后关闭连接。 有关权威来源,请参阅 Datastar SEE 参考 在 Datastar 中,你可以向 HTML elements 添加一些 "tags" (按钮 "on click"、输入 "on change" 等),这会导致浏览器向服务器发送一个请求,以打开 SSE 连接。该连接保持打开状态,直到服务器关闭它。服务器可以向下发送对 signals 的更新,或将更新 UI 的新 HTML fragments。 对于大多数 CRUD 应用程序,你将发送将更新 UI 的 HTML fragments。然后关闭连接。如果你正在制作某种实时仪表板,你将保持连接打开,并在服务器发现数据发生变化时向下发送对 UI 的更新。服务器可能会监视数据库或某些其他数据源,并在它们更改时向下发送对 UI 的更新。 首先,让我们看一下最简单的例子,它最像 HTMX 示例,并且更符合 CRUD 应用程序的要求。 你将在 HTML element 上添加一些属性,这将触发对服务器的 SSE 调用。为简单起见,我们假设它是一个按钮点击。

<button id="button1" data-on-click="@get('/example/buttonpress')">
 点击我
</button>

这将触发浏览器通过 SSE header 的 Accept: text/event-stream 向服务器发出的 HTTP 调用。

GET /example/buttonpress HTTP/1.1
Host: example.com
Accept: text/event-stream
Cache-Control: no-cache

可以选择发送额外的数据,Datastar 将自动发送页面上的任何本地 signals。这种自动 signal 发送是 Datastar 的一项功能,HTMX 中没有这项功能,我没有意识到它有多么强大。在上面的 HTTP 示例中,我没有显示任何 signals,因为在这个简单的示例中不需要它们。

HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: close
event: Datastar-merge-fragments
data: fragments <div id="button1">Button Pressed and removed.</div>

在上面的示例中,服务器发回一个新的 HTML fragment,它将替换被单击的按钮。在浏览器中运行的 Datastar JavaScript 将匹配 element 的 ID,并将其替换为新的 HTML fragment。 服务器可以发送几个 fragments 来更新页面的任何部分。HTMX 可以做到这一点,但我认为 Datastar 是开箱即用地构建来处理这个问题的。

HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
event: Datastar-merge-fragments
data: fragments <div id="truckstatus">The Truck is under a bridge</div>
event: Datastar-merge-fragments
data: fragments <div id="truckstatus">The truck is at Jersey Mikes and the driver is enjoying a sandwich</div>

在 HTMX 中,你必须实现轮询,它可以工作,但不如 SSE 有效。 如果你查看 进度条示例,你可以看到那里有一个像这样的 SEE endpoint:

GET https://data-star.dev/examples/progress_bar/data

它会发送一系列更新到 title 和 id="progress_bar"div。当浏览器接收到更新时,它会实时更新 UI。浏览器保持一个 SSE 连接打开,并且服务器可以向下发送对 signals 的更新。服务器还可以向下发送更新 UI 的新 HTML fragments。

HTTP/1.1 200 OK
cache-control: no-cache
connection: keep-alive
content-type: text/event-stream
date: Thu, 16 Jan 2025 05:36:26 GMT
fly-request-id: 01JHPSSQHJMTZ82JYZXE5T43BM-sjc
server: Fly/3f202fc64 (2025-01-13)
transfer-encoding: chunked
via: 1.1 fly.io
event: Datastar-merge-fragments
retry: 1000
data: fragments <div id="progress_bar"><svg width="200" height="200" viewbox="-25 -25 250 250" style="transform: rotate(-90deg)"><circle r="90" cx="100" cy="100" fill="transparent" stroke="#e0e0e0" stroke-width="16px" stroke-dasharray="565.48px" stroke-dashoffset="565px"></circle> <circle r="90" cx="100" cy="100" fill="transparent" stroke="#6bdba7" stroke-width="16px" stroke-linecap="round" stroke-dashoffset="559px" stroke-dasharray="565.48px"></circle> <text x="44px" y="115px" fill="#6bdba7" font-size="52px" font-weight="bold" style="transform:rotate(90deg) translate(0px, -196px)">1%</text></svg></div>
event: Datastar-merge-fragments
retry: 1000
data: selector title
data: fragments <title>1%</title>
event: Datastar-merge-fragments
retry: 1000
data: fragments <div id="progress_bar"><svg width="200" height="200" viewbox="-25 -25 250 250" style="transform: rotate(-90deg)"><circle r="90" cx="100" cy="100" fill="transparent" stroke="#e0e0e0" stroke-width="16px" stroke-dasharray="565.48px" stroke-dashoffset="565px"></circle> <circle r="90" cx="100" cy="100" fill="transparent" stroke="#6bdba7" stroke-width="