How to Compile a TypePHP Project for the Browser Using WebAssembly (WASM)

TypePHP provides native WASM compilation through a three-stage build process involving the SDK builder (wasm/build-sdk.sh), the program compiler (wasm/build-program.sh), and a browser loader (typephp.js).

TypePHP is a compiled PHP implementation maintained in the swoole/typephp repository that targets WebAssembly, enabling PHP applications to run client-side in modern browsers without a server-side runtime. This guide walks through the complete workflow to transform TypePHP source code into standalone WASM modules using the official build toolchain.

Prerequisites for WASM Compilation

Before compiling, install Clang/LLVM with support for the wasm32-unknown-wasi target and Node.js (version 14 or higher) for JavaScript glue generation. The build scripts located in wasm/ automatically detect these toolchains, but you can override paths via environment variables such as LLVM_DIR and NODE_BIN. Additionally, ensure Emscripten is available, as the SDK build process relies on emconfigure and emmake to compile the C++ sources.

The Three-Stage Build Process

TypePHP compiles to WebAssembly through three distinct stages: building the SDK static library, compiling your specific PHP program, and deploying the resulting module to the browser.

Stage 1 – Build the WASM SDK

Execute wasm/build-sdk.sh to compile the underlying C/C++ runtime engine into a WASM-compatible static library. This script processes the source files under src/ using Emscripten, producing two key artifacts:

  • libtypephp.a: The static library containing the compiled TypePHP engine
  • typephp.js: A generic JavaScript loader that handles WebAssembly module instantiation, memory allocation, and string conversion between JavaScript and the WASM runtime

Run this stage once per environment setup:

cd wasm
./build-sdk.sh

Stage 2 – Compile Your PHP Program

With the SDK built, use wasm/build-program.sh to translate your PHP source files into an executable WebAssembly module. This script wraps the phpc compiler (TypePHP's compiler) and invokes it with the --wasm flag to perform static analysis, build an intermediate representation, and emit the final binary.

Execute the compiler pointing to your source directory:

./build-program.sh myapp/ output.wasm

This command generates output.wasm (the compiled module) and output.js (a project-specific loader pre-wired to your particular module). The generated loader re-exports the same API as the generic typephp.js but is already configured to load your specific WASM file.

Stage 3 – Load and Run in the Browser

Host the generated output.wasm and output.js files on any static web server. Include the loader in an HTML page using ES modules:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>TypePHP WASM Demo</title>
</head>
<body>
  <script type="module">
    import init from './output.js';
    // init returns a promise that resolves when the WASM instance is ready
    init().then(({ php }) => {
      // php is the entry point exported by the compiled module
      php(); // Executes your PHP script client-side
    });
  </script>
</body>
</html>

Opening this page in a modern browser downloads the .wasm file, instantiates it via the WebAssembly API, and executes your PHP code entirely client-side.

Enabling WASI for File System Operations

To use PHP file functions such as file_get_contents() or file_put_contents(), compile with the --wasi flag to enable WASI (WebAssembly System Interface) support:

./build-program.sh . file_demo.wasm --wasi

The WASI implementation provides a sandboxed virtual filesystem. Pre-populate files at initialization by passing a preloadFiles configuration to the loader:

<script type="module">
  import init from './file_demo.js';
  init({
    preloadFiles: [{ name: '/data.txt', content: 'Initial file contents' }]
  }).then(({ php }) => php());
</script>

This maps PHP's POSIX-like file calls onto the WASI system interface, allowing scripts to read from and write to an in-memory virtual filesystem. See docs/zh-cn/WASI_BUILD.md for detailed WASI configuration options.

Complete Working Examples

Hello World Compilation

Create a simple PHP script named hello.php:

<?php
echo "Hello, WebAssembly!\n";
?>

Build the project using the two-stage process:

cd wasm
./build-sdk.sh                      # Build SDK once

./build-program.sh . hello.wasm    # Compile hello.php

Serve hello.wasm and hello.js alongside this HTML:

<!DOCTYPE html>
<html>
<body>
  <script type="module">
    import init from './hello.js';
    init().then(({ php }) => php());
  </script>
</body>
</html>

Opening the page prints "Hello, WebAssembly!" to the browser console.

Virtual File System Demo

For applications requiring file I/O, create file_demo.php:

<?php
file_put_contents('data.txt', 'WASM FS demo');
echo file_get_contents('data.txt');
?>

Compile with WASI support and preload a virtual file:

./build-program.sh . file_demo.wasm --wasi

Then initialize with filesystem preloading:

<script type="module">
  import init from './file_demo.js';
  init({
    preloadFiles: [{ name: '/data.txt', content: '' }]
  }).then(({ php }) => php());
</script>

The script writes to and reads from the virtual /data.txt file, demonstrating how PHP's native filesystem functions map onto the WASI runtime provided by the TypePHP engine.

Summary

  • Run wasm/build-sdk.sh once to generate libtypephp.a and the generic typephp.js loader from the C++ sources in src/
  • Use wasm/build-program.sh <source_dir> <output_name> to compile PHP projects to WASM via the phpc --wasm compiler
  • Deploy the generated .wasm binary and accompanying .js loader to any static web server
  • Enable --wasi flag when using PHP file functions, and pre-populate the virtual filesystem via preloadFiles configuration
  • Refer to wasm/README.md for build troubleshooting and docs/zh-cn/PHPX_WASM_BUILD.md for detailed deployment guides

Frequently Asked Questions

What is the difference between build-sdk.sh and build-program.sh?

build-sdk.sh compiles the TypePHP C++ engine (located under src/) into the static library libtypephp.a and generates the generic JavaScript runtime glue typephp.js. build-program.sh invokes the phpc compiler to translate your specific PHP source files into a WebAssembly module and produces a project-specific loader script that references your compiled .wasm binary.

Can I use standard PHP file functions in the browser?

Yes, when you compile with the --wasi flag enabled. The WASI implementation maps standard PHP file operations (such as fopen, fread, and file_put_contents) to an in-memory virtual filesystem. You can pre-populate files and directories at runtime using the preloadFiles option in the loader initialization object, as documented in docs/zh-cn/WASI_BUILD.md.

How do I customize the toolchain paths for LLVM or Node.js?

Set the LLVM_DIR and NODE_BIN environment variables before executing the build scripts. The SDK builder automatically detects standard installation paths, but respects these overrides when provided, allowing you to use specific versions of Clang/LLVM or Node.js for the wasm32-unknown-wasi target compilation.

Why is Emscripten required for the build process?

The TypePHP engine relies on Emscripten's emconfigure and emmake tools to compile the C++ source code into WebAssembly-compatible static libraries. Emscripten also generates the critical JavaScript glue code (typephp.js) that bridges WebAssembly memory management, string marshalling, and exception handling between the browser's JavaScript environment and the compiled PHP runtime.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →