Docs - Simple CGI Example
How can I setup a simple working implementation of the CGI web worker?
The docs show how to instantiate PhpCgiWorker but what am I supposed to do with the service worker? Are there some simple messages I can send to the service worker to see if it works correctly?
The demo-web seems to link a bunch of shared libraries. Is that required for a simple implementation?
The shared libs aren't required to run php-wasm, except for libxml, which should be loaded automatically.
The service worker will respond to any HTTP requests made to the domain that registered it, so the PHP handler will activate for certain paths, and handle the request without actually sending it to the server. Other requests are sent to the network as normal.
Building the service worker was the most challenging thing for me. I pieced together a working service worker by using this setup. I had to use webpack instead of vite.
sw.mjs:
import { PhpCgiWorker } from "php-cgi-wasm/PhpCgiWorker.mjs";
const sharedLibs = [
`php${PHP_VERSION}-zlib.so`,
`php${PHP_VERSION}-zip.so`,
`php${PHP_VERSION}-gd.so`,
`php${PHP_VERSION}-iconv.so`,
`php${PHP_VERSION}-intl.so`,
`php${PHP_VERSION}-openssl.so`,
`php${PHP_VERSION}-dom.so`,
`php${PHP_VERSION}-mbstring.so`,
`php${PHP_VERSION}-sqlite.so`,
`php${PHP_VERSION}-pdo-sqlite.so`,
`php${PHP_VERSION}-xml.so`,
`php${PHP_VERSION}-simplexml.so`,
{ url: "libxml2.so", ini: false },
];
const files = [
{ parent: "/preload/", name: "icudt72l.dat", url: "./icudt72l.dat" },
];
// Log requests with more detail
const onRequest = (request, response) => {
const url = new URL(request.url);
const logLine =
`[${new Date().toISOString()}] ` +
`${request.method} ${url.pathname} - ${response.status}`;
console.log(logLine);
// Log errors in detail
if (response.status >= 400) {
console.error("Service Worker Error Response:", {
url: url.pathname,
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries(response.headers.entries()),
});
}
};
// Spawn the PHP-CGI binary - revert to working prefix
const php = new PhpCgiWorker({
onRequest,
sharedLibs,
files,
staticFS: false,
prefix: "/php-wasm/cgi-bin/",
docroot: "/persist/www/",
types: {
jpeg: "image/jpeg",
jpg: "image/jpeg",
gif: "image/gif",
png: "image/png",
svg: "image/svg+xml",
},
});
// Set up the main event handlers
self.addEventListener("install", (event) => {
console.log("Service Worker: Install");
php.handleInstallEvent(event);
});
self.addEventListener("activate", (event) => {
console.log("Service Worker: Activate");
php.handleActivateEvent(event);
});
self.addEventListener("fetch", (event) => {
php.handleFetchEvent(event);
});
self.addEventListener("message", (event) => {
php.handleMessageEvent(event);
});
webpack.config.mjs
import path from "node:path";
import "webpack-dev-server";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const config = {
mode: "development",
devtool: "inline-source-map",
module: {
rules: [
{
test: /\.mjs$/,
exclude: /node_modules/,
use: { loader: "babel-loader" },
},
],
},
entry: { "service-worker": "./src/sw.mjs" },
output: {
path: path.resolve(__dirname, "public"),
filename: "sw.js",
},
target: "webworker",
};
export default config;
bun-build.sh:
#/usr/bin/env bash
set -eux;
if [ -d 'public/static/media/mapped' ]; then {
rm public/static/media/*.map || true
rm -rf public/static/media/mapped
}
fi
PHP_VERSION=8.3
ls node_modules/*/*.so node_modules/php-wasm-intl/icudt72l.dat | while read FILE; do {
BASENAME=`basename ${FILE}`;
if [[ ${BASENAME} == php8.* ]]; then
if [[ ${BASENAME} != php${PHP_VERSION}* ]]; then
continue;
fi;
fi;
cp ${FILE} public/;
}; done;
rm -f build/*.wasm;
rm -f build/*.data;
rm -f build/*.map;
rm -f build/*.js;
rm -f public/*.wasm;
rm -f public/*.data;
rm -f public/*.map;
rm -f public/*.js;
bunx webpack --config webpack.config.mjs;
package.json:
{
"name": "tanstack-start-example-basic-react-query",
"private": true,
"sideEffects": false,
"type": "module",
"scripts": {
"dev": "vite dev",
"build": " bun run sw && vite build && tsc",
"sw": "bash ./bun-build.sh",
"start": "bun run .output/server/index.mjs",
"format": "biome format --write src",
"lint": "biome lint --write src",
"check": "biome check --write src"
},
"dependencies": {
"@codemirror/lang-php": "^6.0.1",
"@jsdevtools/rehype-toc": "^3.0.2",
"@mdx-js/mdx": "^3.1.0",
"@mdx-js/react": "^3.1.0",
"@mdx-js/rollup": "^3.1.0",
"@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-select": "^2.2.5",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-tabs": "^1.1.12",
"@radix-ui/react-tooltip": "^1.2.7",
"@shikijs/langs": "^3.6.0",
"@shikijs/themes": "^3.6.0",
"@tailwindcss/postcss": "^4.1.10",
"@tanstack/react-query": "^5.0.0-alpha.91",
"@tanstack/react-query-devtools": "^5.0.0-alpha.91",
"@tanstack/react-router": "^1.121.0-alpha.27",
"@tanstack/react-router-devtools": "^1.121.0-alpha.27",
"@tanstack/react-router-with-query": "^1.121.0-alpha.27",
"@tanstack/react-start": "^1.121.0-alpha.27",
"@tanstack/server-functions-plugin": "^1.121.0-alpha.26",
"@types/mdx": "^2.0.13",
"babel-loader": "^10.0.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"codemirror": "^6.0.1",
"lucide-react": "^0.513.0",
"next-themes": "^0.4.6",
"php-cgi-wasm": "0.0.9-alpha-32",
"php-wasm-dom": "0.0.9-alpha-32",
"php-wasm-gd": "0.0.9-alpha-32",
"php-wasm-iconv": "0.0.9-alpha-32",
"php-wasm-intl": "0.0.9-alpha-32",
"php-wasm-libxml": "0.0.9-alpha-32",
"php-wasm-libzip": "0.0.9-alpha-32",
"php-wasm-mbstring": "0.0.9-alpha-32",
"php-wasm-openssl": "0.0.9-alpha-32",
"php-wasm-phar": "0.0.9-alpha-32",
"php-wasm-simplexml": "0.0.9-alpha-32",
"php-wasm-sqlite": "0.0.9-alpha-32",
"php-wasm-tidy": "0.0.9-alpha-32",
"php-wasm-xml": "0.0.9-alpha-32",
"php-wasm-yaml": "0.0.9-alpha-32",
"php-wasm-zlib": "0.0.9-alpha-32",
"posthog-js": "^1.255.1",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-resizable-panels": "^3.0.2",
"redaxios": "^0.5.1",
"rehype-highlight": "^7.0.2",
"rehype-slug": "^6.0.0",
"remark-frontmatter": "^5.0.0",
"remark-gfm": "^4.0.1",
"remark-mdx-frontmatter": "^5.2.0",
"shiki": "^3.6.0",
"sonner": "^2.0.5",
"tailwind-merge": "^3.3.1",
"tailwindcss-animate": "^1.0.7",
"thememirror": "^2.0.1",
"vite": "^6.3.5"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@tailwindcss/typography": "^0.5.16",
"@types/node": "^22.15.31",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"autoprefixer": "^10.4.21",
"postcss": "^8.5.5",
"tailwindcss": "^4.1.10",
"tw-animate-css": "^1.3.4",
"typescript": "^5.8.3",
"vite-tsconfig-paths": "^5.1.4",
"webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.2"
}
}