lustre/server_component
Note: server components are currently only supported on the erlang target. If it’s important to you that they work on the javascript target, open an issue and tell us why it’s important to you!
Server components are an advanced feature that allows you to run entire Lustre applications on the server. DOM changes are broadcasted to a small client runtime and browser events are sent back to the server.
-- SERVER -----------------------------------------------------------------
Msg Element(Msg)
+--------+ v +----------------+ v +------+
| | <-------------- | | <-------------- | |
| update | | Lustre runtime | | view |
| | --------------> | | --------------> | |
+--------+ ^ +----------------+ ^ +------+
#(model, Effect(msg)) | ^ Model
| |
| |
DOM patches | | DOM events
| |
v |
+-----------------------+
| |
| Your WebSocket server |
| |
+-----------------------+
| ^
| |
DOM patches | | DOM events
| |
v |
-- BROWSER ----------------------------------------------------------------
| ^
| |
DOM patches | | DOM events
| |
v |
+----------------+
| |
| Client runtime |
| |
+----------------+
Note: Lustre’s server component runtime is separate from your application’s WebSocket server. You’re free to bring your own stack, connect multiple clients to the same Lustre instance, or keep the application alive even when no clients are connected.
Lustre server components run next to the rest of your backend code, your services, your database, etc. Real-time applications like chat services, games, or components that can benefit from direct access to your backend services like an admin dashboard or data table are excellent candidates for server components.
Examples
Server components are a new feature in Lustre and we’re still working on the
best ways to use them and show them off. For now, you can find a simple
undocumented example in the examples/
directory:
Getting help
If you’re having trouble with Lustre or not sure what the right way to do something is, the best place to get help is the Gleam Discord server. You could also open an issue on the Lustre GitHub repository.
Functions
pub fn component(attrs: List(Attribute(a))) -> Element(a)
Render the Lustre Server Component client runtime. The content of your server component will be rendered inside this element.
Note: you must include the lustre-server-component.mjs
script found in
the priv/
directory of the Lustre package in your project’s HTML or using
the script
function.
pub fn data(json: Json) -> Attribute(a)
Ocassionally you may want to attach custom data to an event sent to the server. This could be used to include a hash of the current build to detect if the event was sent from a stale client.
Your event decoders can access this data by decoding data
property of the
event object.
pub fn decode_action(
dyn: Dynamic,
) -> Result(Action(a, ServerComponent), List(DecodeError))
The server component client runtime sends JSON encoded actions for the server runtime to execute. Because your own WebSocket server sits between the two parts of the runtime, you need to decode these actions and pass them to the server runtime yourself.
pub fn emit(event: String, data: Json) -> Effect(a)
Instruct any connected clients to emit a DOM event with the given name and
data. This lets your server component communicate to frontend the same way
any other HTML elements do: you might emit a "change"
event when some part
of the server component’s state changes, for example.
This is a real DOM event and any JavaScript on the page can attach an event listener to the server component element and listen for these events.
pub fn encode_patch(patch: Patch(a)) -> Json
Encode a DOM patch as JSON you can send to the client runtime to apply. Whenever the server runtime re-renders, all subscribed clients will receive a patch message they must forward to the client runtime.
pub fn include(properties: List(String)) -> Attribute(a)
Properties of a JavaScript event object are typically not serialisable. This means if we want to pass them to the server we need to copy them into a new object first.
This attribute tells Lustre what properties to include. Properties can come
from nested objects by using dot notation. For example, you could include the
id
of the target element
by passing ["target.id"]
.
import gleam/dynamic
import gleam/result.{try}
import lustre/element.{type Element}
import lustre/element/html
import lustre/event
import lustre/server
pub fn custom_button(on_click: fn(String) -> msg) -> Element(msg) {
let handler = fn(event) {
use target <- try(dynamic.field("target", dynamic.dynamic)(event))
use id <- try(dynamic.field("id", dynamic.string)(target))
Ok(on_click(id))
}
html.button([event.on_click(handler), server.include(["target.id"])], [
element.text("Click me!")
])
}
pub fn route(path: String) -> Attribute(a)
The route
attribute tells the client runtime what route it should use to
set up the WebSocket connection to the server. Whenever this attribute is
changed (by a clientside Lustre app, for example), the client runtime will
destroy the current connection and set up a new one.
pub fn script() -> Element(a)
Inline the Lustre Server Component client runtime as a script tag.
pub fn select(
sel: fn(fn(a) -> Nil, Subject(b)) -> Selector(a),
) -> Effect(a)
On the Erlang target, Lustre’s server component runtime is an OTP
actor that can be
communicated with using the standard process API and the Subject
returned
when starting the server component.
Sometimes, you might want to hand a different Subject
to a process to restrict
the type of messages it can send or to distinguish messages from different
sources from one another. The select
effect creates a fresh Subject
each
time it is run. By returning a Selector
you can teach the Lustre server
component runtime how to listen to messages from this Subject
.
The select
effect also gives you the dispatch function passed to effect.from
.
This is useful in case you want to store the provided Subject
in your model
for later use. For example you may subscribe to a pubsub service and later use
that same Subject
to unsubscribe.
Note: This effect does nothing on the JavaScript runtime, where Subjects
and Selectors
don’t exist, and is the equivalent of returning effect.none()
.
pub fn set_selector(: Selector(Action(a, b))) -> Effect(b)
Deprecated: The implementation of this effect is broken in ways that cannot be fixed without changing the API. If you'd like other Erlang actors and processes to send messages to your Lustre server component, take a look at the `select` effect instead.
pub fn subscribe(
id: String,
renderer: fn(Patch(a)) -> Nil,
) -> Action(a, ServerComponent)
A server component broadcasts patches to be applied to the DOM to any connected clients. This action is used to add a new client to a running server component.
pub fn unsubscribe(id: String) -> Action(a, ServerComponent)
Remove a registered renderer from a server component. If no renderer with the given id is found, this action has no effect.