A Look at Datastar
本文为原文前 6,000 字符的节选翻译,完整内容请查看原文。
A Look at Datastar
A Look at Datastar04·11·2026 I have always been interested in frontend solutions for building web applications. Even though I’m more of a backend programmer, I really dig all the different techniques people come up with for creating browser-based apps that are both reactive (reflecting remote state in real-time) and interactive (responding to user actions).
我一直对构建 Web 应用程序的前端解决方案很感兴趣。尽管我更偏向于后端编程,但我非常欣赏人们为创建既具有响应式(实时反映远程状态)又具有交互性(响应用户操作)的浏览器应用所提出的各种技术。
Over the last few years, I’ve been looking at different frontend frameworks and libraries, to see how they work, and what I can learn from them. I adore Svelte, which is really smart and efficient. Its sister framework SvelteKit inspired the file-based routing in my own web framework for Ruby, Syntropy.
在过去的几年里,我一直在研究不同的前端框架和库,看看它们是如何工作的,以及我能从中学习到什么。我非常喜欢 Svelte,它既聪明又高效。它的姊妹框架 SvelteKit 启发了我为 Ruby 开发的 Web 框架 Syntropy 中的基于文件的路由设计。
I also really like the approach of the Phoenix framework to building reactive apps using LiveView, which is an amazing technology that builds on the solid concurrency foundation of Elixir/Erlang. Many people have tried to recreate the same functionality for other languages or frameworks, with varying degrees of success.
我也非常喜欢 Phoenix 框架使用 LiveView 构建响应式应用的方法,这是一种建立在 Elixir/Erlang 坚实并发基础之上的出色技术。许多人尝试为其他语言或框架重现相同的功能,但成功程度各不相同。
Recently, Marco Roth came up with ReActionView, which promises to bring reactive views to Rails apps, building on his previous work on Herb, a set of tools that improve on the developer experience of writing ERB templates for Rails apps. All those different technologies are certainly impressive, but they all are tied to specific languages/frameworks: Svelte/SvelteKit is a JS framework, LiveView is for Phoenix/Elixir, ReActionView is for Ruby on Rails, LiveWire is for Laravel/PHP, etc.
最近,Marco Roth 推出了 ReActionView,旨在为 Rails 应用带来响应式视图。它建立在他之前开发的 Herb 之上,Herb 是一套旨在改善 Rails 应用 ERB 模板编写体验的工具。所有这些不同的技术固然令人印象深刻,但它们都绑定在特定的语言或框架上:Svelte/SvelteKit 是 JS 框架,LiveView 针对 Phoenix/Elixir,ReActionView 针对 Ruby on Rails,LiveWire 针对 Laravel/PHP 等。
And then there’s Datastar, which dubs itself “the Hypermedia framework”. It is somewhat similar to other frontend declarative frameworks/libraries such as HTMX or Alpine.js, in that interactivity/reactivity is done declaratively by adding attributes to HTML tags.
然后是 Datastar,它自称为“超媒体框架”。它在某种程度上类似于 HTMX 或 Alpine.js 等其他前端声明式框架/库,因为交互性和响应性是通过向 HTML 标签添加属性来声明式完成的。
But what is different about Datastar is that it insists on keeping state where it should be, such that it doesn’t try to copy server state to the client and keep it in sync with the server. Instead, the server continuously sends updates (in the form of HTML patches, or signal patches) to the client.
但 Datastar 的不同之处在于,它坚持将状态保留在应有的位置,因此它不会试图将服务器状态复制到客户端并保持与服务器同步。相反,服务器会持续向客户端发送更新(以 HTML 补丁或信号补丁的形式)。
In that respect, Datastar is more like LiveView, but without the coupling to Phoenix/Elixir. In fact, Datastar offers SDKs for different languages and frameworks, including Ruby (the Ruby SDK is maintained by Ismael Celis). I find this approach so refereshing! With Datastar, you can use any backend technology in order to build reactive web apps.
在这方面,Datastar 更像 LiveView,但没有与 Phoenix/Elixir 的耦合。事实上,Datastar 为不同的语言和框架提供了 SDK,包括 Ruby(Ruby SDK 由 Ismael Celis 维护)。我觉得这种方法非常令人耳目一新!使用 Datastar,你可以使用任何后端技术来构建响应式 Web 应用。
Furthermore, Datastar relies on SSE (server-sent events) for streaming updates from the server to the client, instead of the somewhat older approach of using a WebSocket connection used in technologies such as LiveView, or HotWire (the current go-to solution for Rails apps).
此外,Datastar 依赖 SSE(服务器发送事件)将更新从服务器流式传输到客户端,而不是使用像 LiveView 或 HotWire(目前 Rails 应用的首选方案)等技术中使用的 WebSocket 连接这种稍显陈旧的方法。
While SSE does not provide a full-duplex connection to the server (meaning, you can’t use an SSE request/connection to send data from the client to the server), it provides two important advantages over WebSocket: A WebSocket connection normally requires establishing a separate HTTP/1.1 connection to the server (there’s partial support for WebSocket over HTTP/2, but that depends on the browser and the server/reverse proxy used. HTTP/3 support is very limited as far as I could tell).
虽然 SSE 不提供与服务器的全双工连接(意味着你不能使用 SSE 请求/连接将数据从客户端发送到服务器),但它比 WebSocket 具有两个重要优势:WebSocket 连接通常需要与服务器建立单独的 HTTP/1.1 连接(虽然 HTTP/2 对 WebSocket 有部分支持,但这取决于浏览器和所使用的服务器/反向代理。据我所知,HTTP/3 的支持非常有限)。
In contrast, SSE requests can be multiplexed on top of an HTTP/2 or HTTP/3 connection, along with any number of other concurrent requests. Secondly, because SSE requests are just normal (streaming) HTTP requests, the server can use standard HTTP compression in order to achieve significant reduction in bandwidth.
相比之下,SSE 请求可以在 HTTP/2 或 HTTP/3 连接之上进行多路复用,并与其他任意数量的并发请求共存。其次,由于 SSE 请求只是普通的(流式)HTTP 请求,服务器可以使用标准的 HTTP 压缩来显著减少带宽占用。
Let’s look at a simple example of working with Datastar. Here’s the markup for a simple reactive app that displays the current time on the server:
让我们看一个使用 Datastar 的简单示例。这是一个简单的响应式应用的标记,它显示服务器上的当前时间:
<html>
<head>
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.4/bundles/datastar.js">
</script>
</head>
<body>
<p data-init="@get('/ds/time_stream')">
<span>Time:</span> <span data-text="$time" />
</p>
</body>
</html>
This example does three things: load the datastar library (BTW it’s only about 15KB!), set up a connection to an SSE stream at /ds/time_stream using the @get action, and binds a span to a signal named $time.
这个示例做了三件事:加载 datastar 库(顺便说一下,它只有大约 15KB!),使用 @get 操作建立到 /ds/time_stream 的 SSE 流连接,并将一个 span 绑定到名为 $time 的信号。
On the server side, the controller looks as follows (using Syntropy):
在服务器端,控制器如下所示(使用 Syntropy):
# /ds/time_stream.rb
export ->(req) {
req.respond_sse {
send_time_update(req)
@machine.periodically(1) { send_time_update(req) }
}
}
def send_time_update(req)
json = JSON.dump(time: Time.now.to_s)
req.send_event('datastar-patch-signals' => "signals #{json}")
end
The controller code is quite simple: after calling respond_sse, which sends some HTTP response headers, we send a first update, then we send further updates once per second. What actually gets sent over the wire looks as follows:
控制器代码非常简单:在调用 respond_sse(发送一些 HTTP 响应头)之后,我们发送第一次更新,然后每秒发送一次后续更新。实际在网络上传输的内容如下所示:
HTTP/1.1 200
Content-Type: text/event-stream
event: datastar-patch-signals
data: signals { "time": "2026-11-04 11:54:55 +0200" }
event: datastar-patch-signals
data: signals { "time": "2026-11-04 11:54:56 +0200" }
event: datastar-patch-signals
data: signals { "time": "2026-11-04 11:54:57 +0200" }
...
The Datastar wire protocol is based on the SSE response format, where each event contains an (optional) event: entry and one or more data: entries. Distinct events are separated by double newline characters (\n\n). In this case, we’re sending a datastar-patch-signals event that tells the browser to update signal values. The patched values are expressed as a JSON object where the keys correspond to signal names and the values correspond to the signal values.
Datastar 的传输协议基于 SSE 响应格式,其中每个事件包含一个(可选的)event: 条目和一个或多个 data: 条目。不同的事件由双换行符 (\n\n) 分隔。在这种情况下,我们发送一个 datastar-patch-signals 事件,告诉浏览器更新信号值。补丁值表示为一个 JSON 对象,其中键对应于信号名称,值对应于信号值。
You can look at this example on a separate page, but for the sake of convenience I’ll go ahead and embed it right here: Time: Although this is a very simple example, look at all the things we’re not doing 😜: we don’t need to set up an EventSource object, or use fetch to start the SSE connection on the client side, we don’t need to go through the DOM and update anything that’s bound to the $time signal.
你可以在单独的页面上查看此示例,但为了方便起见,我直接将其嵌入此处:时间:虽然这是一个非常简单的示例,但看看我们省去了多少工作 😜:我们不需要设置 EventSource 对象,也不需要在客户端使用 fetch 来启动 SSE 连接,我们不需要遍历 DOM 并更新任何绑定到 $time 信号的内容。
On the server side we just stream changes to the server state to the client (eventually though, we might want to introduce some abstractions for sending a datastar-patch-signals event or oth
在服务器端,我们只是将服务器状态的更改流式传输到客户端(不过最终,我们可能希望引入一些抽象来发送 datastar-patch-signals 事件或其他内容)。