Packages

Run WebAssembly from Elixir. Load WASM modules in Rust, Go, C β€” call them like native functions.

Current section

Files

Jump to
firebird lib mix tasks firebird.phoenix.ex
Raw

lib/mix/tasks/firebird.phoenix.ex

defmodule Mix.Tasks.Firebird.Phoenix do
@moduledoc """
Compile and test Phoenix WASM components.
## Usage
# Build all Phoenix WASM modules from Rust sources
mix firebird.phoenix build
# Run the Phoenix WASM benchmark suite
mix firebird.phoenix bench
# Show information about loaded Phoenix WASM modules
mix firebird.phoenix info
## Build
Compiles the Rust source files in `fixtures/phoenix_*/` to WASM modules.
Requires Rust with the `wasm32-unknown-unknown` target installed.
## Bench
Runs performance benchmarks comparing WASM-accelerated Phoenix components
against pure Elixir implementations.
## Info
Shows the available Phoenix WASM modules, their sizes, and exported functions.
"""
use Mix.Task
@shortdoc "Phoenix WASM tools (build, bench, info)"
@phoenix_modules ~w(phoenix_router phoenix_template phoenix_plug phoenix_endpoint phoenix_view phoenix_controller phoenix_channel phoenix_json phoenix_session phoenix_live phoenix_validator phoenix_form phoenix_csrf phoenix_rate_limiter)
@impl Mix.Task
def run(args) do
case args do
["build" | _] ->
build()
["bench" | _] ->
bench()
["info" | _] ->
info()
_ ->
Mix.shell().info("""
Usage: mix firebird.phoenix <command>
Commands:
build Build Phoenix WASM modules from Rust sources
bench Run performance benchmarks
info Show module information
""")
end
end
defp build do
Mix.shell().info("πŸ”₯ Building Phoenix WASM modules...\n")
fixtures_dir = Path.join(File.cwd!(), "fixtures")
results =
Enum.map(@phoenix_modules, fn module ->
src_dir = Path.join(fixtures_dir, module)
wasm_output = Path.join(fixtures_dir, "#{module}.wasm")
if File.dir?(src_dir) do
Mix.shell().info(" πŸ“¦ Building #{module}...")
{output, code} =
System.cmd(
"cargo",
[
"build",
"--target",
"wasm32-unknown-unknown",
"--release"
],
cd: src_dir,
stderr_to_stdout: true
)
if code == 0 do
# Copy the built WASM to fixtures root
built_wasm =
Path.join([
src_dir,
"target",
"wasm32-unknown-unknown",
"release",
"#{module}.wasm"
])
if File.exists?(built_wasm) do
File.cp!(built_wasm, wasm_output)
size = File.stat!(wasm_output).size
Mix.shell().info(" βœ… #{module}.wasm (#{format_bytes(size)})")
{:ok, module}
else
Mix.shell().info(" ❌ WASM file not found after build")
{:error, module}
end
else
Mix.shell().info(" ❌ Build failed: #{String.slice(output, 0..200)}")
{:error, module}
end
else
Mix.shell().info(" ⏭️ #{module} - no source directory")
{:skip, module}
end
end)
ok = Enum.count(results, &(elem(&1, 0) == :ok))
fail = Enum.count(results, &(elem(&1, 0) == :error))
Mix.shell().info("\nπŸ“Š Built #{ok} modules, #{fail} failures")
end
defp bench do
Mix.Task.run("app.start")
Mix.shell().info("πŸ”₯ Phoenix WASM Benchmarks\n")
components = ~w(router template plug endpoint json session channel server)a
for comp <- components do
try do
Firebird.Phoenix.Benchmark.run(comp, iterations: 1_000)
rescue
e -> Mix.shell().info(" ⚠️ #{comp}: #{Exception.message(e)}")
end
end
Mix.shell().info("\nβœ… Benchmarks complete")
end
defp info do
Mix.Task.run("app.start")
fixtures_dir = Path.join(File.cwd!(), "fixtures")
Mix.shell().info("πŸ”₯ Phoenix WASM Module Information\n")
Enum.each(@phoenix_modules, fn module ->
wasm_path = Path.join(fixtures_dir, "#{module}.wasm")
if File.exists?(wasm_path) do
size = File.stat!(wasm_path).size
Mix.shell().info(" πŸ“¦ #{module}")
Mix.shell().info(" Path: #{wasm_path}")
Mix.shell().info(" Size: #{format_bytes(size)}")
# Try to load and get exports
case Firebird.load(wasm_path) do
{:ok, instance} ->
exports = Firebird.exports(instance)
Mix.shell().info(" Exports: #{inspect(exports)}")
Firebird.stop(instance)
{:error, reason} ->
Mix.shell().info(" Error loading: #{inspect(reason)}")
end
Mix.shell().info("")
else
Mix.shell().info(" ❌ #{module} - not built (run `mix firebird.phoenix build`)\n")
end
end)
end
defp format_bytes(bytes) when bytes < 1024, do: "#{bytes} B"
defp format_bytes(bytes) when bytes < 1_048_576, do: "#{Float.round(bytes / 1024, 1)} KB"
defp format_bytes(bytes), do: "#{Float.round(bytes / 1_048_576, 1)} MB"
end