The homepage hero ships an interactive Elixir editor backed by Software Mansion's Popcorn, which compiles AtomVM + an Elixir handler module to WebAssembly.
The runtime is loaded from public/bundle.avm and served beneath Astro's
configured base path. The file is ~7 MB and is committed to the repo so
cloning gives you a working interactive demo out of the box.
When public/bundle.avm is missing the editor still works: it loads the
example snippets and shows their precanned outputs, and astro.config.ts
skips registering the Popcorn Vite plugin. Drop a valid bundle.avm
back into public/ and the live runtime turns on automatically — no
code changes needed.
Popcorn v0.3.0 hard-pins OTP 26.0.2 and Elixir 1.17.3. macOS ships much newer toolchains, so the safest way to reproduce the build is a one-shot Docker container.
-
Clone the Popcorn repository (anywhere on disk):
git clone https://github.com/software-mansion/popcorn.git ~/popcorn -
From the elixir-website repo root, run the pinned-toolchain build:
docker run --rm \ -v "$HOME/popcorn:/popcorn" \ -v "$PWD/public:/out" \ -w /popcorn/examples/eval-in-wasm \ -e MIX_HOME=/tmp/.mix -e HEX_HOME=/tmp/.hex \ -e LANG=C.UTF-8 -e LC_ALL=C.UTF-8 -e ELIXIR_ERL_OPTIONS="+fnu" \ erlang:26.0.2-slim bash -lc ' set -euo pipefail apt-get update -qq && apt-get install -y -qq curl unzip git ca-certificates >/dev/null curl -fsSL -o /tmp/elixir.zip https://github.com/elixir-lang/elixir/releases/download/v1.17.3/elixir-otp-26.zip mkdir -p /opt/elixir && unzip -q /tmp/elixir.zip -d /opt/elixir export PATH=/opt/elixir/bin:$PATH mix local.hex --force >/dev/null && mix local.rebar --force >/dev/null mix deps.get mix popcorn.cook cp dist/wasm/bundle.avm /out/bundle.avm '
-
The rebuilt
bundle.avmlands atpublic/bundle.avm. Commit it when the upstream Popcorn version changes or the handler module is updated.
- The Vite plugin (
@swmansion/popcorn/vite) is registered conditionally inastro.config.ts— only whenpublic/bundle.avmexists. It excludes Popcorn from Vite's dep prebundling (so the internal worker iframe import resolves) and serves the bundle with the SharedArrayBuffer-required headers in dev/preview. - GitHub Pages uses
public/coi-serviceworker.jsto provide the cross-origin isolation headers that the static host cannot configure itself. - The hero React island (
src/components/home/HeroCodeEditor.tsx) HEAD-probes the base-path-aware bundle URL; if the response is 200, it dynamically imports@swmansion/popcornand callspopcorn.call(["eval_elixir", code], { timeoutMs: 10_000 }). If anything fails it falls back to the precanned output for the active example.
- Popcorn v0.3.0 is described upstream as "early stages — may break"; the graceful fallback keeps the page demoable if the runtime fails to boot for any reason.
- The handler action name is
eval_elixir. If you customise the example Mix app (see~/popcorn/examples/eval-in-wasm/lib/eval_in_wasm.ex), keep the action name in sync withHeroCodeEditor.tsx. - The bundle includes the full Elixir + stdlib + logger BEAM files for the WASM target — that's why it weighs ~7 MB.