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.

Search Document