Using Haxe Conditional Compilation to Target JS and C++ Without Duplication
Learn how Haxe’s #if/#else directives let you write platform‑specific code while keeping a single source file, with a concrete file‑system wrapper example and practical checks.
11 Jul 2025, 04:32 UTC

The Problem: Writing Platform‑Specific Code Without Duplication
When a project must run on both JavaScript (e.g., in a browser) and native C++ (e.g., desktop or mobile), you often need different implementations for the same functionality—file access, networking, or UI. Duplicating business logic across separate source files leads to drift and maintenance overhead. Haxe offers a compile‑time mechanism that lets you keep one codebase while emitting only the relevant bits for each target.
How Haxe Conditional Compilation Works
The Haxe compiler evaluates #if, #elseif, #else, and #end directives before code generation. If the condition matches the current target (or a custom build flag), the block is kept; otherwise it is omitted from the output entirely. The omitted code is still parsed for syntax errors, so you get early feedback, but it does not increase the size of the generated JavaScript file or C++ binary.
Conditions can be simple target identifiers like js or cpp, or they can combine flags with &&&, ||, and parentheses. You can also define custom flags via the -D compiler option.
Worked Example: A Minimal File‑System Wrapper
Suppose you need a function logMessage that writes a line to a file on C++ targets and to the browser console on JavaScript targets. The following snippet shows how conditional compilation keeps the implementation separate while sharing the public API.
class Main {
static function main() {
#if js
// JavaScript: use the browser console
trace('JS: logging via console');
#elseif cpp
// C++: use standard file IO
var sys = require('sys');
sys.puts('CPP: logging to file');
#else
// Fallback for other targets (e.g., hl, flash)
trace('Unknown target');
#end
}
}
To verify that only the relevant block appears in the output, compile the file for each target and inspect the generated code.
- Save the code above as
Main.hxin an empty directory. - Compile for JavaScript:
- Open
output.jsand search for the stringlogging via console. You should find it; the C++‑specific lines (require('sys')andsys.puts) should be absent. - Compile for C++:
- Look inside
output/Main.cpp(or the generated source file) forlogging to file. The JavaScripttracecall should not be present.
haxe -main Main -js output.js
haxe -main Main -cpp output
These steps confirm that the compiler removed the inactive branch, keeping the final artifact lean.
Trade‑offs and Limitations
- Readability: Heavy use of
#ifblocks can scatter platform logic throughout a file, making it harder to follow the overall flow. Consider isolating platform‑specific code in separate classes or modules and using conditional compilation only at the import/instantiation level. - Hidden bugs: Because inactive blocks are still type‑checked, a typo or API misuse may not surface until you compile for that target. Regularly test each target to catch target‑only issues early.
- Build‑flag proliferation: Defining many custom
-Dflags can become confusing. Document the meaning of each flag in a README or build script.
Actionable Closing
Start small: wrap a single piece of platform‑dependent functionality (like the file‑system example) with #if js / #elseif cpp. Verify the output with the steps above, then expand the pattern as needed. Keep the conditional blocks short, favor composition over deep nesting, and maintain a test suite for each target to ensure that the omitted code remains correct when you later enable it.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.