Keep One Codebase for Multiple Platforms with Haxe Conditional Compilation
Learn how to use Haxe’s #if directives and -D flags to keep one codebase that compiles cleanly for JS, HXCPP, and other targets. Follow a step‑by‑step guide to set up flags, write platform‑specific blocks, verify builds, and avoid common pitfalls.
11 Aug 2025, 12:11 UTC

Goal: One source, many targets
When you write a library or game in Haxe, you often want it to run in the browser, on desktop, and on mobile. Conditional compilation lets you keep all platform‑specific code in the same file, while the compiler removes the parts that aren’t relevant for the chosen target. The result is a clean build pipeline and no runtime checks for platform.
Prerequisites
- Haxe installed (≥4.3). Verify with
haxe -version. - Basic understanding of Haxe’s build system (
hxmlfiles orbuild.hxml). - Target runtimes you plan to support (e.g.,
js,hxcpp,java). - Optional: a script or CI pipeline that can pass custom defines to the compiler.
Step 1 – Define your target flags
Haxe allows you to pass arbitrary symbols to the compiler with the -D switch. For each supported target we’ll define a flag that matches the target name. In a build.hxml file you can add:
# Target‑specific flags
-Dtarget_js
-Dtarget_hxcpp
When you build for a specific platform you only pass the flag that applies to that platform. For example:
haxe build.hxml -Dtarget_js
haxe build.hxml -Dtarget_hxcpp
If you prefer command‑line only, skip the build.hxml and run:
haxe -Dtarget_js -js output.js
haxe -Dtarget_hxcpp -cpp build
Step 2 – Write platform‑specific code blocks
Enclose code that should only be compiled for a particular target inside #if blocks. Haxe processes these directives at compile time, so the code inside an inactive block is never type‑checked or emitted.
class PlatformSpecific {
public static function init():Void {
#if target_js
trace("Running in a browser");
// Browser‑only API
js.Browser.window.alert("Hello JS!");
#elseif target_hxcpp
trace("Running on desktop");
// Native C++ API
cpp.Lib.println("Hello HXCPP!");
#else
trace("Unknown target – default behaviour");
#end
}
}
Nested #if blocks let you combine multiple flags, e.g., #if target_js && !debug, to create fine‑grained build variants.
Step 3 – Verify that the compiler respects the flags
- Compile for JS:
haxe build.hxml -Dtarget_jsOpen the generated
output.jsand confirm the line containingjs.Browser.window.alertis present, while thecpp.Lib.printlnline is absent. - Compile for HXCPP:
haxe build.hxml -Dtarget_hxcppCheck the
buildfolder for acppsource that includescpp.Lib.printlnand no JS alert call. - Test inactive block safety:
// Inside an inactive block #if target_js var x:NonExistentType = 5; // type error #endWhen compiling for HXCPP, the compiler should ignore this block and not report the type error. Switching to
-Dtarget_jsshould then surface the error, proving the block was indeed ignored.
Step 4 – Common pitfalls and recovery
- Spaghetti code: Over‑nesting
#ifstatements can make the file unreadable. Keep each block small and document the purpose of each flag in comments. - Hidden bugs: Code inside inactive blocks is never type‑checked until that flag is enabled. Run the compiler with all flags enabled in a CI job to surface such bugs.
- Runtime surprises: Conditional compilation is compile‑time only. Avoid using
#ifto switch logic based on runtime conditions; useif (Sys.systemName() == "Windows")instead. - Missing flag: If a target flag is omitted, the compiler will fall back to the
#elseblock. Ensure your build scripts always supply the correct flag.
Step 5 – Integrate into CI/CD
In a CI pipeline you can run two jobs, one for each target:
jobs:
build-js:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Haxe
run: sudo apt-get install haxe
- name: Build JS
run: haxe build.hxml -Dtarget_js
build-hxcpp:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Haxe
run: sudo apt-get install haxe
- name: Build HXCPP
run: haxe build.hxml -Dtarget_hxcpp
This ensures that any platform‑specific errors are caught early.
Conclusion
Conditional compilation in Haxe is a powerful way to keep a single codebase clean while targeting multiple runtimes. By passing target flags via -D, wrapping code in #if blocks, and verifying the output for each target, you avoid runtime checks and keep the build fast. Remember to keep the code readable, test all flags, and integrate the build steps into your CI pipeline for continuous safety.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.