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 to WebSocket enabling duplex communication between the client and server runtime. Other options include ServerSentEvents and Polling 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 the script 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 method(value: TransportMethod) -> Attribute(a)
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 Subjects 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 Subjects and Selectors don’t exist, and is the equivalent of returning effect.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 Subjects to send messages to your application, take a look at the select effect.

Note: this function is not available on the JavaScript target.

Search Document