Day-to-day development practices for building applications and contributing to the compiler.
# 1. Make changes to Haxe source files
vim src_haxe/MyModule.hx
# 2. Compile and test
npm test # Full validation (recommended)
npm run test:quick # Faster subset for quick iterations
# 3. Test specific functionality
haxe build.hxml # Basic compilation
haxe build.hxml -D source-map # With debugging support-D analyzer-optimize).
# Start automatic recompilation (todo-app example)
cd examples/todo-app
mix haxe.watch
# Make changes to .hx files → automatic recompilation
# Sub-second compilation times with hot reload
# Perfect for iterative developmentRun this once per clone:
npm run hooks:install- Enables repo-managed pre-commit checks (path leaks, secrets scan, staged
.hxauto-format, naming guards). - If you use
bd hooks install --chain, re-runningnpm run hooks:installupdates.git/hooks/pre-commit.oldso repo checks still run before thebdflush.
- The pre-commit hook blocks staged absolute local paths (for example
/Users/...,/home/...,/var/folders/...,C:\Users\...). - Dot-relative path references (
./...,../...) are allowed when they resolve inside the repo root. - Dot-relative references that escape repo root are blocked by default.
- To bypass relative-path blocking for an intentional case:
ALLOW_RELATIVE_PATH_REFERENCES=1 git commit ...
- This guard is enforced from
scripts/hooks/pre-commitviascripts/lint/local_path_guard_staged.sh.
Reflaxe.Elixir coordinates two development ecosystems for practical end-to-end validation:
Purpose: Build and test the Haxe→Elixir compiler itself
- Package Manager:
lix(modern Haxe package manager) - Runtime:
npmscripts orchestrate the workflow - Dependencies: Reflaxe framework, tink_unittest
- Output: Generated Elixir source code files
npm install # Installs lix package manager locally
npx lix download # Downloads Haxe dependencies (project-specific versions)
haxe TestMain.hxml # Compile using your Haxe toolchain
# If `haxe` is not on your PATH, use the project-local wrapper:
# ./node_modules/.bin/haxe TestMain.hxmlWhy lix?
- ✅ Locked Haxe library versions (avoids "works on my machine")
- ✅ GitHub + haxelib sources (always latest libraries)
- ✅ Locked dependency versions (zero software erosion)
- ✅ Scoped installs (keeps Haxe libs out of global state)
Purpose: Test and run the generated Elixir code
- Package Manager:
mix(native Elixir build system) - Dependencies: Phoenix, Ecto, GenServer, LiveView
- Output: Running BEAM applications
mix deps.get # Installs Phoenix, Ecto, etc.
mix test # Tests generated Elixir code and Mix tasks
mix ecto.migrate # Runs database migrations Why mix?
- ✅ Native Elixir tooling (industry standard)
- ✅ Phoenix integration (LiveView, router, etc.)
- ✅ BEAM ecosystem (OTP, GenServer, supervision trees)
# Test everything (recommended before commits)
npm test
# Snapshot-only run (fastest)
npm run test:quick
# Test Mix tasks + Elixir integration
npm run test:mix
# Compile-check every example under examples/
npm run test:examples
# Todo-app end-to-end build + boot (Phoenix runtime + optional Playwright)
npm run qa:sentinelFor details on non-blocking Phoenix validation (async runs, bounded log viewing, Playwright integration), see Phoenix E2E & QA Sentinel.
- Synchronous testing: Deterministic test execution
- Performance validation: Built-in benchmarking, <15ms targets
- Clean output: Clear success/failure reporting
- Framework-agnostic: Easy to switch between test frameworks
- Haxe Compiler Tests: Validate the compilation engine itself
- Elixir Runtime Tests: Validate the generated code and Mix integration
Some errors are expected during testing and appear as warnings:
# Expected in test environment - shows as warning
[warning] Haxe compilation failed (expected in test): Library reflaxe.elixir is not installedWhy this happens:
- Tests run in isolated environments without full library installation
- Test framework validates compilation behavior, not successful execution
- These warnings indicate the test is working correctly
Actual compilation problems show with error symbols:
# Real error - shows with ❌ symbol
[error] ❌ Haxe compilation failed: src_haxe/Main.hx:5: Type not found : MyClassHaxe Source Code (.hx files)
↓ (npm/lix tools)
Reflaxe.Elixir Compiler
↓ (generates)
Elixir Source Code (.ex files)
↓ (mix tools)
Running BEAM Application
Reflaxe.Elixir uses two compilation profiles that affect macros and AST transformers:
-
fast_boot– opt-in “fast profile” for large codebases- Minimal macro work:
- Macros like
RouterBuildMacro,HXX, andModuleMacrostill run, but avoid expensiveContext.getType/ project‑wide scans where possible. - Template processing uses memoization and cheap shape checks when enabled.
- Macros like
- Core semantic transforms only:
- Phoenix/Ecto/OTP shape‑driven transforms remain active.
- Ultra‑late cosmetic hygiene passes (naming, underscore promotion, unused assignment cleanup) are skipped when
fast_bootanddisable_hygiene_finalare defined.
- Goal: keep cold todo‑app builds bounded and responsive during day‑to‑day work.
- Implementation:
- Haxe macros read it via
Context.defined("fast_boot")and avoid project‑wide scans. - The AST pass registry gates selected expensive passes with
#if fast_boot. - It should never be required for correctness; it exists to trade “perfect hygiene” for speed.
- Haxe macros read it via
- Minimal macro work:
-
full_prepasses/ full hygiene – used for compiler/snapshot/CI runs- All macros and transforms are enabled.
- Hygiene and final sweep passes run to enforce the strictest shape and naming invariants.
- Intended for full validation, not for tight dev loops.
fast_boot is enabled by passing -D fast_boot to Haxe. For Mix builds, this repo treats it as opt-in via:
HAXE_FAST_BOOT=1 mix compileSee lib/haxe_compiler.ex for the injection point. Legacy todo-app perf/debug HXML experiments are kept in git history and are not required for normal development.
The Haxe compilation server (haxe --wait) is opt-in:
-
Default dev and CI workflows should act as if:
export HAXE_NO_SERVER=1and should not auto‑start the server.
-
You may explicitly start and use the server when iterating on the compiler:
# In a dev shell iex -S mix iex> {:ok, _} = HaxeServer.start_link([]) iex> HaxeServer.status() # In another shell, reuse the server: HAXE_USE_SERVER=1 haxe --connect 6116 examples/todo-app/build-server.hxml # When done: iex> HaxeServer.stop()
-
QA sentinel and CI use direct
haxeinvocations by default and only opt into the server via environment (HAXE_USE_SERVER=1) when explicitly configured. This avoids background high‑CPU processes and keeps build behavior predictable.
All compilation features meet <15ms performance requirements:
- Basic compilation: 0.015ms ✅
- Ecto Changesets: 0.006ms average ✅
- Migration DSL: 6.5μs per migration ✅
- OTP GenServer: 0.07ms average ✅
- Phoenix LiveView: <1ms average ✅
package.json: npm scripts, lix dependency.haxerc: Project-specific Haxe version (4.3.7)haxe_libraries/: lix-managed dependencies (tink_unittest, reflaxe)mix.exs: Elixir dependencies (Phoenix, Ecto)
test/Test.hxml: Snapshot test runner configurationtest/tests/: Individual test cases with expected outputsexamples/todo-app/: Integration test as real Phoenix application
src/reflaxe/elixir/ElixirCompiler.hx: Main compilersrc/reflaxe/elixir/helpers/: Feature-specific compilers (Changeset, OTP, LiveView, etc.)std/: Phoenix/Elixir type definitions and externs
Reflaxe.Elixir has a source mapping design (to map generated .ex back to .hx), but it is
currently experimental and not fully wired end‑to‑end in the AST pipeline.
See docs/04-api-reference/SOURCE_MAPPING.md for the current status and next steps.
Reflaxe.Elixir supports full-stack development with a single language:
- Server-side: Haxe → Elixir (Phoenix LiveView, Ecto, OTP)
- Client-side: Haxe → JavaScript with native async/await support
- Shared Types: Define data structures in
shared/directory - Dual Compilation: Build both targets with type-safe contracts
- Live Reload: Hot reload for both Elixir and JavaScript changes
- Type Safety: Compile-time checks across shared server/client contracts
See: Quick Start Patterns for copy‑paste, end‑to‑end patterns.
- Check existing implementations first - Search for similar patterns before starting
- Plan with documentation - Update roadmap with your feature
- Create helper compiler in
src/reflaxe/elixir/helpers/ - Add annotation support to main
ElixirCompiler.hx - Write tests using snapshot testing in
test/tests/ - Document thoroughly - Update guides and examples
- Run
npm testto validate (ALL tests must pass) - Mark task complete - Verify implementation meets requirements
// Create snapshot test in test/tests/new_feature/
// src_haxe/TestNewFeature.hx
class TestNewFeature {
public static function main() {
trace("Testing new feature");
// Your feature test code here
var result = MyNewFeature.doSomething();
trace('Result: $result');
}
}# Check the version this repo expects
cat .haxerc
# Check your installed Haxe
haxe --version
# Reset lix scope
npx lix scope create
npx lix download# Reset npm dependencies
rm -rf node_modules && npm install
# Re-download the toolchain + libraries (lix cache)
npx lix download
# If your lix cache is corrupted, remove it and retry:
# rm -rf ~/haxe && npx lix download
# Reset Elixir dependencies
mix deps.clean --all && mix deps.get# Run individual test components
npm run test:quick # Snapshot suite + Elixir validation (fast)
npm run test:mix # Elixir/Mix tests only
# Narrow scope
npm run test:failed
npm run test:changed
# Update test snapshots when output improves
npm run test:update# Clean and rebuild everything
rm -rf lib/*.ex lib/**/*.ex # Remove generated files
haxe build.hxml # Regenerate from Haxe
mix compile --force # Verify Elixir compilation- Prefer
haxefrom a proper Haxe install; if it’s not on your PATH, use the repo shim:./node_modules/.bin/haxe ...(provided bylix+.haxerc). - Run full test suite before committing changes
- Use source maps for debugging (
-D source-map) - Test todo-app integration after compiler changes
- Update documentation when adding features
- Follow existing patterns in the codebase
- Write thorough tests for all new functionality
- Document architectural decisions in appropriate guides
- Maintain performance targets (see Performance Guide; large modules may require
fast_bootduring iteration)
- Commit frequently with descriptive messages
- Test before pushing to ensure CI/CD success
- Update changelogs for user-facing changes
- Reference issues in commit messages
✅ Modern Haxe tooling (lix + tink_unittest)
✅ Native Elixir integration (mix + Phoenix ecosystem)
✅ End-to-end validation (compiler + generated code)
✅ Single command simplicity (npm test)
✅ Zero global state (project-specific everything)
✅ Fast compilation for typical modules (see docs/06-guides/PERFORMANCE_GUIDE.md)
- Phoenix Integration - Build Phoenix applications
- LiveView Architecture - Real-time UI patterns
- ExUnit Testing - Application testing strategies
- Compiler Architecture - How the compiler works
- AST Pipeline - TypedExpr → ElixirAST → transforms → print
- Testing Infrastructure - Snapshot testing system
- Troubleshooting Guide - Comprehensive problem solving
- Performance Guide - Compilation performance
Ready to build? Check out Phoenix Integration to start building applications.
Reflaxe.Elixir uses the Haxe compilation server (haxe --wait) to speed up
incremental builds during mix compile and mix phx.server.
Behavior (no configuration required):
- Auto‑start (dev by default): In
MIX_ENV=dev, Mix can auto-starthaxe --waitfor incremental compilation. - Direct compile (CI/prod/test by default): In short-lived builds, we default to direct
haxeto avoid leakinghaxe --waitOS processes. - Reuse (owned): If Mix started the server in this VM, it is reused automatically.
- Attach (opt‑in): If
HAXE_SERVER_ALLOW_ATTACH=1and the configured port is already bound by a compatible server, Mix attaches and uses it (including a prior server recorded in the cookie). - Relocate: If the configured port is already bound and attach is not enabled (or not compatible), Mix relocates to a free port and starts its own server.
- Cookie: Mix records the last working server info per project/toolchain in
.reflaxe_elixir/haxe_server.jsonto reduce port churn across restarts and enable stale-server cleanup. - Fallback: If the server cannot be reached, Mix compiles directly (no server).
Defaults and environment variables:
- Default server port:
6116(aligned with the QA sentinel). HAXE_NO_SERVER=1— disable the server for the current run (use direct Haxe).HAXE_SERVER_PORT=<port>— force a specific port (e.g.,6116).HAXE_SERVER_ALLOW_ATTACH=1— allow attaching to an externally-started compatible server on the configured port.HAXE_SERVER_AUTOSTART=dev|always|never— control when Mix should auto-start the server (default:dev).
Notes:
- The behavior is fully transparent; no flags are needed for normal use.
- The QA sentinel also uses a compile server and falls back to direct compilation
under strict timeouts. It uses the same default port (
6116) and relocates when busy.
If you repeatedly see messages like:
Haxe server port 6116 is in use; relocating to ...
it usually means a previous Mix VM crashed and left behind stale haxe --wait processes.
Clean them up (bounded, repo-local) and retry:
scripts/haxe-server-cleanup.sh