Best Practices¶
This guide provides recommendations and best practices for compiling C and C++ to the modern web using WebAssembly and Emscripten.
Following this guide when reporting bugs can also help speed up diagnosing and fixing issues since you will be on the well-trodden path.
In some cases, Emscripten will guide you towards these best practices by emitting warnings, but that is not always possible.
General Recommendations¶
Avoid high-frequency calls between WebAssembly and JavaScript; for example, avoid per-pixel or per-sample calls in favor of operations that process entire memory regions or buffers.
Avoid shipping debug information in production; see below for recommended release build flags.
Command-Line Style¶
Keeping the command line flag usage minimal and consistent helps with codebase understanding and avoids deviating from the well-lit path.
Don’t pass settings that are enabled by default. To keep command lines clean and concise, omit redundant options that are already default in modern Emscripten (such as
-sWASM=1).Prefer standard compiler flags over Emscripten-specific ones where possible (for example, prefer
-pthreadover-sUSE_PTHREADSand-m64over-sMEMORY64).Use simple comma-separated lists for list-based settings (for example,
-sEXPORTED_FUNCTIONS=_main,_mallocrather than JSON arrays like-sEXPORTED_FUNCTIONS=['_main','_malloc']).Avoid long lists on the command line; use a response file (
@filename) instead (for example,-sEXPORTED_FUNCTIONS=@exported_funcs.txt).Don’t include the “=1” suffix for boolean flags. For example, write
-sSTRICTand-sALLOW_MEMORY_GROWTHrather than-sSTRICT=1or-sALLOW_MEMORY_GROWTH=1.Use separate compilation by compiling
.cppsources to.oobject files before linking rather than combining everything into a single monolithic compiler invocation.
Recommended Flags¶
-sSTRICT: Opt into strict modern Emscripten behavior, disabling deprecated or legacy compatibility features.-sEXPORT_ES6: Output a modern ES6 module (module.mjs). This option implies-sMODULARIZEso the generated code will be encapsulated and not impact the global namespace.-sENVIRONMENT=web: Limit the runtime support to only the environments you are targeting. This reduces code size by, for example, omitting Node.js and compatibility code.-Werror -Wall: Treat warnings as errors to catch C++ bugs and invalid compiler settings early.-O3,-Os, or-Oz: For release builds, choose-O3when runtime performance is most critical, or-Os/-Ozwhen minimizing binary payload size is the priority.-flto: Enable Link-Time Optimization (LTO) during both the compilation and linking steps of release builds for maximum runtime performance and size reduction.
Debug vs. Release Profiles¶
When configuring build profiles, keep compile and link flags consistent within each configuration:
Release Builds: Use
-Oz,-Osor-O3combined with-flto. Add--closure=1to get minified JavaScript too, unless you plan to minify with an external tool.Debug Builds: Use
-gwhen compiling and either-g,-gline-tables-only, or-gsource-mapwhen linking. Avoid optimization flags (such as-O2or-O3) or-fltoduring debug builds for faster compilation and accurate debugging.
Common Pitfalls¶
Asynchronous Code Execution and Main Thread Blocking¶
Don’t run long synchronous loops on the browser main thread. The browser uses cooperative multitasking; blocking the main UI thread prevents rendering and freezes the web page.
Restructure infinite loops to yield to the event loop using
emscripten_set_main_loop()(see Implementing an asynchronous main loop in C/C++).For synchronous-looking C++ code that must pause (or interact with asynchronous JavaScript APIs such as
fetch()or Web Promises) without refactoring into callbacks, use Asyncify (-sASYNCIFY) or JavaScript Promise Integration (-sJSPI).Offload heavy compute or blocking operations to background workers using multithreading and pthreads (
-pthread).