lustre/server_component
Server components are an advanced feature that allows you to run components or full Lustre applications on the server. Updates are broadcast to a small (10kb!) client runtime that patches the DOM and events are sent back to the server component in real-time.
-- 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. Here are a few examples we’ve developed so far:
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.
Types
A type representing the messages sent to the server component client runtime. This instruct the client runtime to do things like update the DOM or emit an event from the element.
pub type ClientMessage(msg) =
transport.ClientMessage(msg)
The type of transport the client runtime should use to communicate with your
server component. This is set by the method
attribute on the
server component element.
pub type TransportMethod {
WebSocket
ServerSentEvents
Polling
}
Constructors
-
WebSocket
-
ServerSentEvents
-
Polling
Values
pub fn client_message_to_json(message: ClientMessage(a)) -> Json
Encode a message you can send to the client runtime to respond to. The server component runtime will send messages to any registered clients to instruct them to update their DOM or emit events, for example.
Because your WebSocket server sits between the two parts of the runtime, you need to encode these actions and send them to the client runtime yourself.
pub fn deregister_callback(
callback: fn(ClientMessage(a)) -> Nil,
) -> Message(a)
Deregister a callback to be called whenever the server component runtime
produces a message. The callback to remove is determined by function equality
and must be the same function that was passed to register_callback
.
Note: server components running on the Erlang target are strongly encouraged to use
register_subject
instead of this function.
pub fn deregister_subject(
client: Subject(ClientMessage(a)),
) -> Message(a)
Deregister a Subject
to stop receiving messages and updates from Lustre’s
server component runtime. The subject should first have been registered with
register_subject
otherwise this will do nothing.
pub fn element(
attributes: List(Attribute(a)),
children: List(Element(a)),
) -> Element(a)
Render the server component custom element. This element acts as the thin client runtime for a server component running remotely. There are a handful of attributes you should provide to configure the client runtime:
-
route
is the URL your server component should connect to. This must be provided before the client runtime will do anything. The route can be a relative URL, in which case it will be resolved against the current page URL. -
method
is the transport method the client runtime should use. This defaults toWebSocket
enabling duplex communication between the client and server runtime. Other options includeServerSentEvents
andPolling
which are unidirectional transports.
Note: the server component runtime bundle must be included and sent to the client for this to work correctly. You can do this by including the JavaScript bundle found in Lustre’s
priv/static
directory or by inlining the script source directly with thescript
element below.
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 the 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 include(
event: Attribute(a),
properties: List(String),
) -> Attribute(a)
Properties of a JavaScript event object are typically not serialisable. This means if we want to send them to the server we need to make a copy of any fields we want to decode first.
This attribute tells Lustre what properties to include from an event. Properties
can come from nested fields by using dot notation. For example, you could include
the
id
of the target element
by passing ["target.id"]
.
import gleam/dynamic/decode
import lustre/element.{type Element}
import lustre/element/html
import lustre/event
import lustre/server_component
pub fn custom_button(on_click: fn(String) -> msg) -> Element(msg) {
let handler = fn(event) {
use id <- decode.at(["target", "id"], decode.string)
decode.success(on_click(id))
}
html.button(
[server_component.include(["target.id"]), event.on("click", handler)],
[html.text("Click me!")],
)
}
pub fn pid(runtime: Runtime(a)) -> Pid
Recover the Pid
of the server component runtime so that it can be used in
supervision trees or passed to other processes. If you want to hand out
different Subject
s to send messages to your application, take a look at the
select
effect.
Note: this function is not available on the JavaScript target.
pub fn register_callback(
callback: fn(ClientMessage(a)) -> Nil,
) -> Message(a)
Register a callback to be called whenever the server component runtime
produces a message. Avoid using anonymous functions with this function, as
they cannot later be removed using deregister_callback
.
Note: server components running on the Erlang target are strongly encouraged to use
register_subject
instead of this function.
pub fn register_subject(
client: Subject(ClientMessage(a)),
) -> Message(a)
Register a Subject
to receive messages and updates from Lustre’s server
component runtime. The process that owns this will be monitored and the
subject will be gracefully removed if the process dies.
Note: if you are developing a server component for the JavaScript runtime, you should use
register_callback
instead.
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 runtime_message_decoder() -> Decoder(Message(a))
The server component client runtime sends JSON-encoded messages 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 script() -> Element(a)
Inline the server component client runtime as a <script>
tag. Where possible
you should prefer serving the pre-built client runtime from Lustre’s priv/static
directory, but this inline script can be useful for development or scenarios
where you don’t control the HTML document.
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
Subject
s andSelector
s don’t exist, and is the equivalent of returningeffect.none()
.
pub fn subject(runtime: Runtime(a)) -> Subject(Message(a))
Recover the Subject
of the server component runtime so that it can be used
in supervision trees or passed to other processes. If you want to hand out
different Subject
s to send messages to your application, take a look at the
select
effect.
Note: this function is not available on the JavaScript target.