First steps
View SourceCompatibility warning
Popcorn currently only works OTP 26.0.2 and Elixir 1.17.3. We're working to lift this requirement.
Popcorn requires just a few short steps to setup. Firstly, add the project to dependencies and ensure you have an application start callback:
# mix.exs
def application do
[
extra_applications: [],
mod: {MyApp.Application, []}
]
end
def deps do
[
{:popcorn, "~> 0.1.0"}
]
endThe application should start a worker process:
# lib/my_app/application.ex
defmodule MyApp.Application do
use Application
@impl true
def start(_type, _args) do
children = [
MyApp.Worker
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
endA minimal worker should register itself using Popcorn.Wasm.register/1:
# lib/my_app/worker.ex
defmodule MyApp.Worker do
use GenServer
@process_name :main
def start_link(args) do
GenServer.start_link(__MODULE__, args, name: @process_name)
end
@impl true
def init(_init_arg) do
Popcorn.Wasm.register(@process_name)
IO.puts("Hello from WASM!")
state = %{}
{:ok, state}
end
endThen, configure the output directory for compiled artifacts:
# config/config.exs
import Config
config :popcorn, out_dir: "dist/wasm"Next, fetch dependencies and compile your Elixir code to WebAssembly:
$ mix deps.get
$ mix popcorn.cook
Setting up the JavaScript build
Popcorn uses a JavaScript bundler to package the runtime and your compiled code for the browser. A generator scaffolds the required files:
$ mix popcorn.gen.js
This creates an assets/ directory with:
package.json— npm project with@swmansion/popcornandesbuildbuild.mjs— esbuild configuration with the Popcorn pluginindex.js— JavaScript entry point that initializes Popcornindex.html— HTML template
The generated index.js looks like this:
// assets/index.js
import { Popcorn } from "@swmansion/popcorn";
await Popcorn.init({ bundlePath: "/wasm/bundle.avm", onStdout: console.log });bundlePath tells Popcorn where to find the compiled Elixir bytecode, matching the out_dir you configured earlier. onStdout receives anything your Elixir code prints with IO.puts/1.
Install the npm dependencies and build:
$ npm install --prefix assets
$ npm run build --prefix assets
The build step bundles your JavaScript, copies the WebAssembly runtime assets, and outputs everything to the dist/ directory.
Other bundlers
The generator uses esbuild, but Popcorn also ships plugins for Vite (@swmansion/popcorn/vite) and Rollup (@swmansion/popcorn/rollup). You can swap the bundler by editing build.mjs after scaffolding.
Vite is especially convenient for development — its dev server provides hot reloading out of the box. See the hello-react example for a working Vite setup.
Serving the artifacts
Run mix popcorn.server to start a local server. Then, at http://localhost:4000, you should see Hello from WASM printed in the browser console.
The webpage can also be hosted with any HTTP static file server, but it must add the following HTTP headers:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corpOtherwise, browsers refuse to run WASM.
Popcorn project files structure
Here's the breakdown of a Popcorn project:
your-app
├── assets Source files (JavaScript, HTML)
│ ├── build.mjs esbuild config with Popcorn plugin
│ ├── index.html HTML template
│ ├── index.js JS entry point — initializes Popcorn
│ └── package.json npm dependencies
├── config
│ └── config.exs Popcorn configuration (out_dir)
├── dist Build output (generated)
│ ├── AtomVM.mjs Runtime glue code for browser APIs
│ ├── AtomVM.wasm Compiled AtomVM runtime
│ ├── index.html Copied HTML
│ ├── index.js Bundled JavaScript
│ └── wasm
│ └── bundle.avm Bundled Elixir bytecode
└── lib
└── my_app
├── application.ex Application supervisor
└── worker.ex Worker process