Skip to main content

docs/guides/17-wasm-deployment.md

# WASM Deployment

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-unknown` target:
  `rustup target add wasm32-unknown-unknown`
- **wasm-pack**: install via https://rustwasm.github.io/wasm-pack/
- **Plushie Rust source checkout**: set `PLUSHIE_RUST_SOURCE_PATH` to
  the local `plushie-rust` directory. The WASM build requires
  the `plushie-renderer-wasm` crate, which lives in the source
  checkout.

## Building

```bash
# 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`:

```elixir
config :plushie,
  artifacts: [:bin, :wasm],              # build both by default
  wasm_dir: "priv/static/wasm"          # WASM output directory
```

CLI flags override config:

```bash
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:

```elixir
# 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:

```bash
cd priv/static
python3 -m http.server 8080
```

## Web page integration

The JavaScript module exports an initialization function. Load it
from an HTML page:

```html
<!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_open`
  are 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 `.wasm` binary must be downloaded and compiled
  by the browser on first load. Use `--release` builds 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:

```bash
# 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.