Exclosured.LiveView
(exclosured v0.1.4)
Copy Markdown
LiveView integration for Exclosured WASM modules.
Provides call/5 to invoke WASM functions from a LiveView process,
and sandbox/1 as a HEEx component for embedding WASM modules.
Declarative State Sync
The sandbox component supports a sync attribute that automatically
pushes assign changes to the WASM module:
<Exclosured.LiveView.sandbox
module={:renderer}
sync={%{speed: @speed, color: @color}}
canvas
/>When @speed or @color changes, the new values are automatically
pushed to the WASM module via wasm:state. No manual push_event
calls needed.
Manual Usage
defmodule MyAppWeb.ProcessorLive do
use Phoenix.LiveView
def handle_event("run", %{"input" => input}, socket) do
socket = Exclosured.LiveView.call(socket, :my_mod, "process", [input])
{:noreply, socket}
end
def handle_info({:wasm_result, :my_mod, "process", result}, socket) do
{:noreply, assign(socket, result: result)}
end
def handle_info({:wasm_emit, :my_mod, "progress", payload}, socket) do
{:noreply, assign(socket, progress: payload)}
end
end
Summary
Functions
Call a WASM function on the client. The result will arrive as a
{:wasm_result, module, func, result} message via handle_info/2.
Push a state update to a WASM module.
HEEx component that renders a WASM sandbox container element.
Call a WASM function that streams results back incrementally.
Build a sync map from assigns using a list of keys.
Check if a WASM module has reported ready for this socket.
Functions
Call a WASM function on the client. The result will arrive as a
{:wasm_result, module, func, result} message via handle_info/2.
Options
:fallback- a function that receives the args list and returns a result. If WASM is not loaded yet, the fallback runs on the server and delivers the result via{:wasm_result, module, func, result}, the same shape as a WASM result. Yourhandle_infoworks identically either way.
Example
socket = Exclosured.LiveView.call(socket, :my_mod, "process", [text],
fallback: fn [text] -> String.split(text) |> length() end
)
# This handler works regardless of whether WASM or fallback ran:
def handle_info({:wasm_result, :my_mod, "process", result}, socket) do
{:noreply, assign(socket, result: result)}
end
Push a state update to a WASM module.
HEEx component that renders a WASM sandbox container element.
Attributes
module(required) - The WASM module name (atom)id- Element ID (defaults to "wasm-{module}")sync- Map of values to auto-sync to WASM on change (default: nil). When any value in the map changes between renders, the component pushes the entire sync map to the WASM module viawasm:state.canvas- Whether to include a canvas element (default: false)width- Canvas width (default: 800)height- Canvas height (default: 600)subscribe- List of broadcast channels to subscribe toclass- CSS class for the container
Attributes
module(:atom) (required)id(:string) - Defaults tonil.sync(:map) - Defaults tonil.canvas(:boolean) - Defaults tofalse.width(:integer) - Defaults to800.height(:integer) - Defaults to600.subscribe(:list) - Defaults to[].class(:string) - Defaults tonil.
Call a WASM function that streams results back incrementally.
Instead of waiting for a single {:wasm_result, ...} message, this
sets up handlers for streaming emit("chunk", ...) events from WASM.
Each chunk triggers the on_chunk callback. When WASM emits "done",
the on_done callback fires and the stream handler is cleaned up.
Options
:on_chunk(required) -fn payload, socket -> socketcalled for each chunk:on_done-fn socket -> socketcalled when streaming completes (default: identity):chunk_event- event name WASM emits for chunks (default:"chunk"):done_event- event name WASM emits on completion (default:"done")
Example
# In your LiveView:
def handle_event("analyze", %{"data" => data}, socket) do
socket =
socket
|> Exclosured.LiveView.stream_call(:processor, "analyze", [data],
on_chunk: fn chunk, socket ->
update(socket, :results, &[chunk | &1])
end,
on_done: fn socket ->
assign(socket, processing: false)
end
)
{:noreply, assign(socket, processing: true, results: [])}
end
# In your Rust WASM:
# for item in data.chunks(100) {
# exclosured::emit("chunk", &process(item));
# }
# exclosured::emit("done", "{}");
Build a sync map from assigns using a list of keys.
Shorthand for building the sync attribute on the sandbox component.
Bare atoms use the same name as the assign key. Keyword pairs map an
assign to a different key name.
<%# Same-name shorthand: %>
sync={sync(assigns, ~w(frequency amplitude speed)a)}
<%# Mixed: four same-name, one renamed: %>
sync={sync(assigns, [:frequency, :amplitude, :speed, :color, wave: :wave_type])}
<%# The above produces: %>
%{frequency: @frequency, amplitude: @amplitude, speed: @speed,
color: @color, wave: @wave_type}
Check if a WASM module has reported ready for this socket.