Plushie apps can run in the browser via WebAssembly. The renderer
compiles to WASM using wasm-pack, producing a JavaScript module
and a .wasm binary that can be served from any static file host.
Prerequisites
- Rust toolchain with the
wasm32-unknown-unknowntarget:rustup target add wasm32-unknown-unknown - wasm-pack: install via https://rustwasm.github.io/wasm-pack/
- Plushie Rust source checkout: set
PLUSHIE_RUST_SOURCE_PATHto the localplushie-rustdirectory. The WASM build requires theplushie-renderer-wasmcrate, which lives in the source checkout.
Building
# Build the WASM renderer (debug, for development)
PLUSHIE_RUST_SOURCE_PATH=~/projects/plushie-rust mix plushie.build --wasm
# Build with optimizations (for production)
PLUSHIE_RUST_SOURCE_PATH=~/projects/plushie-rust mix plushie.build --wasm --release
# Build both native binary and WASM in one command
PLUSHIE_RUST_SOURCE_PATH=~/projects/plushie-rust mix plushie.build --bin --wasm
The build produces two files:
plushie_renderer_wasm.js(JavaScript glue module)plushie_renderer_wasm_bg.wasm(compiled WebAssembly binary)
By default these are installed to the WASM output directory configured
via config :plushie, :wasm_dir or the --wasm-dir CLI flag.
Configuration
Set artifact and output preferences in config.exs:
config :plushie,
artifacts: [:bin, :wasm], # build both by default
wasm_dir: "priv/static/wasm" # WASM output directoryCLI flags override config:
mix plushie.build --wasm --wasm-dir assets/wasm
Serving the output
The WASM files are static assets. Serve them from any web server or
CDN. The .wasm file should be served with the application/wasm
MIME type for optimal browser loading.
With Phoenix:
# In endpoint.ex, ensure the WASM directory is served as static:
plug Plug.Static,
at: "/",
from: :my_app,
gzip: true,
only: ~w(wasm css fonts images js favicon.ico robots.txt)With a standalone static server:
cd priv/static
python3 -m http.server 8080
Web page integration
The JavaScript module exports an initialization function. Load it from an HTML page:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My Plushie App</title>
<style>
body { margin: 0; overflow: hidden; }
canvas { width: 100vw; height: 100vh; }
</style>
</head>
<body>
<canvas id="plushie-canvas"></canvas>
<script type="module">
import init from "./plushie_renderer_wasm.js";
await init();
</script>
</body>
</html>The WASM renderer connects back to the Elixir backend over a WebSocket to receive the wire protocol messages (snapshots, patches) and send events. Your Elixir app runs on the server; the WASM renderer runs in the browser.
Limitations vs native
The WASM renderer has several differences from the native renderer:
- No native file dialogs. Platform effects like
Effect.file_openare not available. File operations must use browser APIs (e.g.,<input type="file">via JavaScript interop) or be handled server-side. - No clipboard access without user gesture. Browser security policies restrict clipboard operations to user-initiated events.
- No system notifications. The Web Notifications API can be used as an alternative via JavaScript interop.
- No multi-window. WASM apps render into a single canvas element. Multiple windows are not supported.
- Performance. The WASM renderer uses software rendering. Complex scenes with many animated elements may be slower than the native GPU-accelerated renderer.
- Startup time. The
.wasmbinary must be downloaded and compiled by the browser on first load. Use--releasebuilds and enable gzip/brotli compression on your server to minimize this. - No native widgets. Native widget crates that compile Rust code for the renderer are not currently supported in WASM builds. Only pure Elixir widgets work.
Development workflow
During development, use the native renderer (mix plushie.gui) for
fast iteration. Build and test the WASM version periodically to catch
browser-specific issues:
# Develop with native renderer
mix plushie.gui MyApp --watch
# Periodically verify WASM build
mix plushie.build --wasm
# Serve and test in browser
Download vs build
mix plushie.download --wasm downloads a precompiled WASM renderer
for released versions. This skips the Rust toolchain requirement but
does not support native widgets (which require a custom build). For
most apps using only built-in and pure Elixir widgets, the precompiled
download is sufficient.