This example shows a typed Elixir-first authoring style: use Haxe as a typed host language while leaning on Phoenix/Elixir extern APIs directly.
@:liveviewmodule authored in Haxe, compiled to idiomatic Phoenix LiveView callbacks.- Typed boundary decoding from
Termusingelixir.Kerneltype guards. - Domain flow modeled with
haxe.functional.Result(Ok/Error). - Search logic implemented with Elixir extern surfaces (
elixir.Enum,elixir.ElixirString). - A runnable handwritten Elixir interop boundary (
LegacySlug) consumed from Haxe via typed extern + wrapper. - LiveView browser boot compiled from Haxe (Genes) with a Phoenix-style JS wrapper.
build.hxml enables:
-D reflaxe_elixir_strict
This is the user-facing strict mode. It rejects untyped, explicit Dynamic, and ad-hoc app externs in project-local sources.
This example intentionally does not use -D reflaxe_elixir_strict_examples.
That flag is a repository policy guard for shipped examples, while reflaxe_elixir_strict is what real projects should rely on.
cd examples/13-elixir-first-liveview
mix deps.get
mix deps.compile
mix assets.build
mix compile
mix test
mix phx.serverOpen http://localhost:4000/.
Open http://localhost:4000/interop for the handwritten-Elixir interop sample.
examples/13-elixir-first-liveview/src_haxe/ElixirFirstLiveview.hx- OTP application entrypointexamples/13-elixir-first-liveview/src_haxe/ElixirFirstLiveviewRouter.hx- typed router via module-levelfinal routesexamples/13-elixir-first-liveview/src_haxe/live/SearchLive.hx- LiveView callbacks and renderexamples/13-elixir-first-liveview/src_haxe/live/InteropLive.hx- dedicated interop demo routeexamples/13-elixir-first-liveview/src_haxe/live/SearchDomain.hx- pure domain logicexamples/13-elixir-first-liveview/src_haxe/interop/LegacySlugExtern.hx- thin typed extern to a hand-written Elixir moduleexamples/13-elixir-first-liveview/src_haxe/interop/LegacySlugBridge.hx- app-facing wrapper over that externexamples/13-elixir-first-liveview/src_haxe/client/Boot.hx- Genes-compiled LiveSocket bootstrapexamples/13-elixir-first-liveview/test_haxe/web/SearchLiveIntegrationTest.hx- Haxe-authored Phoenix LiveView integration testsexamples/13-elixir-first-liveview/test_haxe/live/SearchDomainTest.hx- Haxe-authored ExUnit tests
This example keeps one intentional handwritten Elixir module:
examples/13-elixir-first-liveview/src_elixir/elixir_first_liveview/legacy_slug.ex
And consumes it from Haxe using the default interop pattern:
- thin typed extern (
LegacySlugExtern) - small app wrapper (
LegacySlugBridge) - normal Haxe call sites (
InteropLive)
This keeps the boundary explicit while the rest of the app remains Haxe-first.
mix testruns the aliashaxe.compile.tests, which executeshaxe build-tests.hxml.build-tests.hxmlcompiles Haxe ExUnit modules intotest/generated/**/*.exs.test/test_helper.exsauto-requires those generated*_test.exsfiles before ExUnit runs.
build-client.hxmlcompilesclient.Bootintoassets/js/_hx_app_tmp.js.- Mix alias
haxe.compile.clientpromotes output toassets/js/hx_app.jsfor stable esbuild imports. assets/js/phoenix_app.jsstays close to Phoenix defaults and imports./app.js(which importshx_app.js).- Layouts load
/assets/phoenix_app.jsusingphx-track-static.
This example keeps app logic close to Elixir/Phoenix APIs:
- Prefer
elixir.*/phoenix.*surfaces at integration boundaries. - Keep boundary terms explicit and decode early.
- Use
Resultfor explicit success/error flow.
It still uses basic Haxe language constructs (types, enums/results, functions), but avoids portability-first patterns where a BEAM-native API is clearer.