Compile PHP.wasm main modules and side modules (dynamic extensions) for Node.js and web platforms. Use when recompiling PHP, adding Emscripten flags, modifying Dockerfiles, building extensions as SIDE_MODULE, upgrading Emscripten, or troubleshooting compilation failures.
npx skills add https://github.com/WordPress/wordpress-playground --skill compile-php-wasm
Patterns for compiling PHP.wasm main modules and dynamic extension side
modules in the WordPress Playground repository.
Requires: Docker, Node.js (version from .nvmrc), npm
npx nx recompile-php:all php-wasm-web -- --PHP_VERSION=8.5
npx nx recompile-php:all php-wasm-node -- --PHP_VERSION=8.5
npx nx recompile-php:jspi php-wasm-web -- --PHP_VERSION=8.5
npx nx recompile-php:asyncify php-wasm-web -- --PHP_VERSION=8.5
npx nx recompile-php:jspi php-wasm-node -- --PHP_VERSION=8.5
npx nx recompile-php:asyncify php-wasm-node -- --PHP_VERSION=8.5
npm run recompile:php:web
npm run recompile:php:node
RuntimeError: unreachable where you need to identify whichnpx nx recompile-php:all php-wasm-web -- --WITH_DEBUG=yes
npx nx recompile-php:all php-wasm-node -- --WITH_DEBUG=yes
npx nx recompile-php:all php-wasm-web -- --WITH_SOURCEMAPS=yes
npx nx recompile-php:all php-wasm-node -- --WITH_SOURCEMAPS=yes
node node_modules/.bin/nx reset
docker rmi php-wasm:latest
The build system lives in packages/php-wasm/compile/. The pipeline is:
Dockerfile (Emscripten + PHP source + patches)
↓ docker build
.wasm binary + .js glue file
↓ post-link Dockerfile patches (replace.sh)
Patched .js glue file
↓ NX executor copies to dist/
Final artifacts in package dist/
Key files:
| File | Purpose |
|------|---------|
| Dockerfile | Main build — downloads PHP source, applies patches, runs emcc |
| Makefile | Orchestrates Docker builds per PHP version |
| build.js | Node script invoked by NX executors |
| .emcc-php-wasm-flags | Emscripten linker flags (generated during build) |
| .emcc-php-wasm-sources | Source/library paths for linking (generated during build) |
| replace.sh / Dockerfile RUN sed | Post-link patches to the compiled JS glue file |
| Flag | Purpose |
|------|---------|
| ASYNCIFY | Enables Asyncify (unwind/rewind for async JS calls) |
| ASYNCIFY_ONLY=[func1,func2,...] | Whitelist of functions Asyncify instruments (critical for binary size) |
| JSPI | Enables JSPI (V8 native stack switching, replaces Asyncify) |
| JSPI_IMPORTS=[func1,...] | JS imports wrapped with WebAssembly.Suspending |
| JSPI_EXPORTS=[func1,...] | WASM exports wrapped with WebAssembly.promising |
| MAIN_MODULE=1 | Enables dlopen — exports all symbols for dynamic linking |
| MAIN_MODULE=2 | Like =1 but only exports explicitly listed symbols |
| EXPORTED_FUNCTIONS=[...] | C functions accessible from JS |
| EXPORTED_RUNTIME_METHODS=[...] | Emscripten runtime helpers accessible from JS |
| ENVIRONMENT=web,worker | Target environments (affects code generation) |
| INITIAL_MEMORY=256MB | Starting linear memory size |
When adding dlopen support (MAIN_MODULE=1):
-l flags for C libraries. Library directories contain both.a (static) and .so (WASM side module) files. With MAIN_MODULE, PIC
mode makes the linker prefer .so. When wasm-ld encounters a .so
under --whole-archive, it crashes with SIGSEGV. Fix: use explicit .a
paths in .emcc-php-wasm-sources instead of -l flags.
MAIN_MODULE=1 has linker limitations. wasm-ld cannot handle--whole-archive + --experimental-pic on archives as large as
libphp.a. This is a fundamental limitation. Start with =1 (exports
all symbols), then consider =2 for optimization.
environment, Emscripten hardcodes booleans (ENVIRONMENT_IS_WEB = true).
With multiple, it generates runtime detection. Post-processing regex
patterns in the Dockerfile must handle both forms.
The Dockerfile uses sed / replace.sh to modify the compiled JS glue
file after Emscripten runs. Common patches:
_malloc binding when it's not auto-exposed: PHPLoader['malloc'] = wasmExports['malloc'];
// Injected right after assignWasmExports() in the glue file
memory.grow() corruption duringhandleSleep() (see debug-php-wasm-main-module skill for details)
ENVIRONMENT_IS_* substitution for multi-environment buildsWhen upgrading Emscripten, expect these categories of breakage:
setErrNo() removed — use HEAP32[___errno_location() >> 2] = code_malloc/_free no longer auto-exposed — add to EXPORTED_FUNCTIONSHEAPU8/HEAPU32 need explicit EXPORTED_RUNTIME_METHODS-Wincompatible-pointer-types becomes an error. Fix: correct thetypes, not the warning level.
#if PHP_MAJOR_VERSION >= 8 may be too broad(e.g. zend_file_handle.filename is const char * in 8.0 but
zend_string * in 8.1+)
fd_close) may gain JS intermediateframes, breaking JSPI suspension. Symptom: startup hangs silently.
Fix: remove from JSPI_IMPORTS/JSPI_EXPORTS.
exitRuntime() → __funcs_on_exit() may trigger JSPI suspension.Add to JSPI_EXPORTS and EXPORTED_FUNCTIONS.
php*.patch) for older versions as neededSide modules are PHP extensions (Xdebug, intl, GD, etc.) compiled as
WASM shared libraries loaded via dlopen.
The extension build needs a minimal PHP installation (for phpize and
headers). Key Emscripten-specific requirements:
| Requirement | Detail |
| -------------------------- | ----------------------------------------------------------------------------------------------------- |
| Inline assembly patches | HAVE_ASM_GOTO, ZEND_USE_ASM_ARITHMETIC, __GNUC__, __clang__ — same patches as main Dockerfile |
| --without-pcre-jit | SLJIT uses x86 assembly, unavailable in WASM |
| PHP 8.4 flag change | --disable-libxml became --without-libxml |
| Remove -lm from Makefile | Math library is in the main module |
| EMCC_FLAGS | -sSIDE_MODULE -D__x86_64__ -sWASM_BIGINT |
| wasm-opt path | /root/emsdk/upstream/bin/wasm-opt (not on PATH) |
When the side module uses custom renamed imports (e.g. -Drecv=wasm_recv),
you MUST pass -sASYNCIFY_IMPORTS=<custom_name>:
export EMCC_FLAGS="-sSIDE_MODULE -sASYNCIFY -sASYNCIFY_IMPORTS=wasm_recv"
Without this, Binaryen won't instrument call sites for those imports —
locals won't be saved/restored, causing table index is out of bounds
during Asyncify rewind.
Libtool refuses to create WASM shared libraries. Two workarounds:
archive_cmds — replace $CC withemcc $EMCC_FLAGS -shared --whole-archive <static archives> --no-whole-archive
em++, discovering all.o files recursively (find . -path '*/.libs/*.o')
When using approach 2, check subdirectories — C++ libraries often produce
.o files in nested paths that build scripts miss.
ICU .a archives and other pre-built artifacts committed to the repo may
not match current build flags. When MAIN_MODULE=1 requires PIC,
pre-built non-PIC archives cause R_WASM_MEMORY_ADDR_SLEB relocation
errors. Rebuild from source if flags changed.
Docker BuildKit caches aggressively. Before rebuilding:
# Remove the image to force a true rebuild
docker rmi php-wasm:latest
# docker builder prune alone is NOT sufficient — BuildKit reuses
# intermediate layers from existing images
# Also reset NX cache
node node_modules/.bin/nx reset
When the build produces .wasm files that don't work correctly:
# List exports and imports
wasm-objdump -x module.wasm
# Disassemble
wasm-objdump -d module.wasm
# Print WAT form (verify Asyncify instrumentation)
wasm-opt --print module.so
# From JavaScript — inspect a side module
node -e "
const fs = require('fs');
const mod = new WebAssembly.Module(fs.readFileSync('module.so'));
console.log('exports:', WebAssembly.Module.exports(mod).map(e => e.name));
console.log('imports:', WebAssembly.Module.imports(mod).map(i => i.name));
"
Cross-reference symbol lists with:
ASYNCIFY_ONLY function list (main module)EXPORTED_FUNCTIONS in the Emscripten build flagsSIDE_MODULE / MAIN_MODULE dynamic linking expectations| Situation | Action |
| ---------------------------------- | ------------------------------------------------------------- |
| Build fails with compiler error | Read the error, fix C/Makefile, retry |
| Build succeeds but WASM won't load | List imports — runtime is missing something |
| Build succeeds but runtime crashes | List exports + check Asyncify/JSPI function lists |
| Behavior is wrong but no error | Add printf to C code, rebuild, trace |
| Extension fails as SIDE_MODULE | Check dynamic linking flags, verify symbol visibility |
| Linker SIGSEGV with MAIN_MODULE | Switch -l flags to explicit .a paths |
| R_WASM_MEMORY_ADDR_SLEB error | Pre-built archive not compiled with PIC — rebuild from source |
| Don't know what a build step does | Read the Dockerfile/Makefile line by line |
Integration with protocols.io API for managing scientific protocols. This skill should be used when working with protocols.io to search, create, update, or publish protocols; manage protocol steps and materials; handle discussions and comments; organize workspaces; upload and manage files; or integrate protocols.io functionality into workflows. Applicable for protocol discovery, collaborative protocol development, experiment tracking, lab protocol management, and scientific documentation.
Analyzes job descriptions and generates tailored resumes that highlight relevant experience, skills, and achievements to maximize interview chances
Generate Excalidraw diagrams from natural language descriptions. Use when asked to "create a diagram", "make a flowchart", "visualize a process", "draw a system architecture", "create a mind map", or "generate an Excalidraw file". Supports flowcharts, relationship diagrams, mind maps, and system architecture diagrams. Outputs .excalidraw JSON files that can be opened directly in Excalidraw.
Build and distribute Expo development clients locally or via TestFlight
Use when you have a written implementation plan to execute in a separate session with review checkpoints
Data structure for annotated matrices in single-cell analysis. Use when working with .h5ad files or integrating with the scverse ecosystem. This is the data format skill—for analysis workflows use scanpy; for probabilistic models use scvi-tools; for population-scale queries use cellxgene-census.
Benchling R&D platform integration. Access registry (DNA, proteins), inventory, ELN entries, workflows via API, build Benchling Apps, query Data Warehouse, for lab data management automation.
Comprehensive molecular biology toolkit. Use for sequence manipulation, file parsing (FASTA/GenBank/PDB), phylogenetics, and programmatic NCBI/PubMed access (Bio.Entrez). Best for batch processing, custom bioinformatics pipelines, BLAST automation. For quick lookups use gget; for multi-service integration use bioservices.
Take wordpress/compile-php-wasm from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference npx.
Without those the skill loads but fails at the first command.