Every PHPPHP PHP (recursive acronym for PHP: Hypertext Preprocessor) is a widely-used open source general-purpose scripting language that is especially suited for web development and can be embedded into HTML. https://www.php.net/manual/en/preface.php version WordPress Playground supports ships as a WebAssembly binary. Across all builds and versions, those binaries totaled 888 MB. Two compiler flag changes reduced that to 766 MB, a 122 MB saving (13.7%) without removing a single feature.
What changed?
Playground compiles PHP to WebAssembly using Emscripten. The compiler flag MAIN_MODULE controls which symbols get exported for dynamic linking. Previously, Playground used MAIN_MODULE=1, which exports everything, including thousands of symbols that no extension ever calls.
Switching to MAIN_MODULE=2 tells Emscripten to export only the symbols extensions actually use, similar to tree-shaking in JavaScriptJavaScript JavaScript or JS is an object-oriented computer programming language commonly used to create interactive effects within web browsers. WordPress makes extensive use of JS for a better user experience. While PHP is executed on the server, JS executes within a userโs browser.
https://www.javascript.com bundlers. The compiler drops everything else.
The challenge: keeping extensions alive
Flipping a compiler flag sounds simple, but MAIN_MODULE=2 aggressively eliminates code that the main module considers โdead.โ Playground loads extensions like Intl, Xdebug, Memcached, and Redis as dynamic .so modules, and those modules depend on specific symbols from the main PHP binary. Remove the wrong symbol, and the extension crashes on load.
PR #3244 first mapped the approach: scanning each compiled extension wasm-dis to extract which symbols it references. The build system then feeds those symbols back to the linker as explicit exports, keeping every extension working while eliminating thousands of unused ones.
Three extensions required direct fixes. Xdebug required a __c_longjmp WebAssembly tag injection. Memcached called recv and setsockopt through libmemcached internals, so the build added C shim wrappers. Intl alone needed over 5,000 export symbols, including libc++ functions.
Why two build types?
PHP is synchronous. Browsers are not. Playground solves this mismatch with two compilation strategies, and each one produces a separate set of binaries:
- Asyncify transforms compiled code at build time so it can pause and resume execution. This approach works in every browser but adds overhead to the binary. Learn more about Asyncify.
- JSPI (JavaScript Promise Integration) takes a different approach. It lets WebAssembly call async browser APIs directly, without transforming the compiled code. JSPI produces smaller binaries but currently requires V8-based browsers (Chrome, Edge). Learn more about JSPI.
Playground ships both variants for each PHP version, so the MAIN_MODULE=2 optimization is applied to both:
The numbers
| Metric | Before | After | Saved |
|---|---|---|---|
| Total binary size | 888 MB | 766 MB | 122 MB (13.7%) |
.wasm files | 680 MB | 571 MB | 109 MB (16%) |
| JS glue code | 22.7 MB | 8.2 MB | 14.5 MB (63%) |
Per-version .wasm sizes dropped consistently across all supported PHP versions:
| PHP Version | Before (avg) | After (avg) | Reduction |
|---|---|---|---|
| 7.4 | ~20 MB | ~17 MB | 17-19% |
| 8.2 | ~25 MB | ~21 MB | 15-17% |
| 8.4 | ~27 MB | ~23 MB | 14-16% |
| 8.5 | ~28 MB | ~24 MB | 14-15% |
The two PRs combined removed 554,793 lines of JavaScript glue code across all builds.
Why smaller binaries matter
Faster downloads and CI. 122 MB fewer bytes to transfer during repository clones, CI pipelines, and deployments. That adds up across all contributors and all automated runs.
Faster WebAssembly instantiation. Smaller .wasm files compile faster in the browser. Compilation cost scales roughly with binary size, so 16% smaller binaries translate directly to faster WordPress startup.
Lower memory usage. Fewer exported symbols produce a smaller function table. The runtime allocates less memory before PHP even starts.
Faster JavaScript parsing. 63% less JS glue code means the browser parses and evaluates significantly less JavaScript before handing control to PHP.
What this means for php-wasm packages
Version-specific packages like @php-wasm/web-8.4 and @php-wasm/node-8.4 ship lighter binaries. No APIAPI An API or Application Programming Interface is a software intermediary that allows programs to interact with each other and share data in limited, clearly defined ways. changes โ this is purely a build optimization. Existing code that depends on these packages works unchanged.
What comes next
The next step is to benchmark the runtime impact by measuring instantiation time, time-to-first-PHP-response, and memory usage across builds. Those numbers will confirm how the smaller binaries translate to real-world performance gains.
Props to @mho22 for implementing both PRs and reviewing the post, @brandonpayton for reviewing, and @adamziel for the architectural direction. See the full details in PR #3332 and PR #3335.
