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 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