npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kirigami/php-wasm

v8.5.11-4

Published

A custom PHP-WASM build for Node.js — JSPI only, no browser support. Built for the Kirigami project.

Readme


@kirigami/php-wasm

A custom PHP 8.5 WebAssembly build for Node.js — JSPI-only, no browser target.
Built for the Kirigami static site generator.

npm version License: GPL-2.0-or-later Node.js >=24.0.0 PHP 8.5.11 Website


Overview

@kirigami/php-wasm ships a PHP 8.5.11 WebAssembly binary compiled by Kirigami's own toolchain, php-wasm-compiler, together with its Node.js loader and runtime helpers. It is built for exactly what the Kirigami project needs:

  • ✅ JSPI (JavaScript Promise Integration) target only
  • ✅ Node.js runtime only
  • ❌ No browser build
  • ❌ No WORKER / IFRAME targets

This intentional reduction keeps the package lean and avoids shipping browser-specific glue code that would never be used inside Kirigami's server-side execution environment.

Part of the Kirigami project ecosystem.


Table of contents


What's new in 8.5.11-4

  • mdhtml 0.1.6: a block plugin's body may contain other {% … %} tags. The block ends at the %} that balances its own {% (before, at the first %}), and the body still reaches the plugin raw — so a block can list {% img-asset %} codes, for example. An unclosed stray {% in a body falls back to the first %}, as before.

What's new in 8.5.11-3

Two native extensions join the binary:

| Extension | Capability | |---|---| | fastcsv | Streaming CSV reading and writing: FastCSVReader (headers, nextRecord(), seek(), record count), FastCSVWriter (writeRecord(), writeRecordMap()) and FastCSVConfig (delimiter, enclosure, escape, encoding, BOM, strict mode, empty-line skipping, field trimming). | | aspect | The Memoize class, for caching the results of a function. |


What's new in 8.5.11

The runtime includes a broader native extension set for Kirigami's PHP templates:

| Extension | Capability | Compared with the previous README snapshot | |---|---|---| | bz2 | BZip2 compression/decompression, compress.bzip2://, and stream filters | Newly present in the current phpinfo output | | mdhtml | Native Markdown rendering through cmark-gfm, used by php-prepros's MD:: wrapper | Already present in the previous snapshot; now documented as the active wrapper backend | | yaml | Native YAML parsing through LibYAML, used by YAML:: | Already present in the previous snapshot; YAML 1.1 scalar behavior now documented | | jsonk | Native JSON Schema support and replacement of json_encode() / json_decode() | Already present and enabled in both snapshots | | lexbor | HTML5 parsing and CSS selection | Already present in both snapshots | | navicat | HTTP-tunnel database bridge for MySQL, PostgreSQL, and SQLite | Already present in both snapshots | | norm | Unicode normalization and the Normalizer API | Already present in both snapshots | | apcu / igbinary | In-memory caching and compact serialization | Already present in both snapshots |

Comparison source: the README's previously committed phpinfo() snapshot versus node packages/cli/bin/kiri.js phpinfo -m on 2026-09-20. Only bz2 is a newly listed extension in that direct comparison; the other extensions should not be described as newly added relative to that snapshot. The current cURL/Navicat build also reports an updated linked zlib library. This comparison describes the local binary, not the contents of a published npm release.

For the PHP wrappers, quote YAML string keys/values such as "NO" to avoid YAML 1.1 boolean coercion. Markdown footnotes now use <section class="footnotes" data-footnotes>; custom CSS should target .footnotes instead of the old div tag. See php-prepros.


Build origin

The WASM binary (jspi/8_5_11/php_8_5.wasm) and the Emscripten-generated loader (jspi/php_8_5.js) are produced by php-wasm-compiler, Kirigami's own PHP → WebAssembly compiler (Docker + Emscripten). A single config.yaml there drives the PHP version, the statically compiled extensions, the third-party libraries, and the build options; it targets JSPI and Node.js only, with no Asyncify build, browser polyfills, or DOM stubs.

The same compiler also builds and publishes the optional @kirigami/phpext-* extension packages described in Loading additional extensions.

The JavaScript side of this package (index.js, runtime/runtime.js: the networking proxy, extension discovery, and php.ini helpers) is Kirigami code. It plugs the loader into @php-wasm/universal, which still provides the PHP class and loadPHPRuntime().

The compiler's Docker recipes originally started from WordPress Playground's compile pipeline; see its NOTICE.md for provenance details.


Compatibility & Runtime Helpers

This package is a drop-in replacement for the loader module consumed by @php-wasm/universal. It exposes the raw PHPLoaderModule interface along with high-level runtime instantiators that include out-of-the-box networking capabilities.

| Export | Description | |---|---| | getPHPLoaderModule() | Returns the raw JSPI PHP 8.5 loader module | | jspi() | Detects JSPI support in the current runtime (re-exported from wasm-feature-detect) | | getPHPRuntime() | Returns a standard PHP instance. Memoized singleton — the first call creates it, subsequent calls return the same instance | | getPHPRuntimeWithNetwork() | Returns a PHP instance bound to a local TCP outbound proxy using Node built-ins and ws with SSL root certificates injected. Memoized singleton, separate from getPHPRuntime() | | createPHPRuntime({ network? }?) | Creates an independent owned runtime, without changing either singleton. Call php.exit() when finished; this also closes its network proxy and sockets when networking is enabled | | getLoadedExtensions() | Returns the names of every loaded PHP extension, sorted case-insensitively (e.g. ["Core", "curl", "gd", "imagick", "openssl", …]) | | exec(code, network?) | Executes a PHP code snippet against the standard runtime, or the network-enabled one if network is true. Returns { returnCode, stdout, stderr } | | phpversion() | Returns the running PHP interpreter's version string, e.g. "8.5.11" | | phpinfo() | Returns the HTML result of phpinfo() | | setPhpIniValues(php, values, iniPath?) | Updates or adds one or more php.ini directives on a PHP instance. Also available as php.setIniValues(values) on instances from getPHPRuntime() / getPHPRuntimeWithNetwork() | | getPhpIniValue(php, key, iniPath?) | Reads the current value of a single, active (uncommented) php.ini directive |


Requirements

  • Node.js >= 24.0.0
  • npm >= 10.2.3

JSPI support: use the exported async jspi() feature check before creating a runtime. The package requires Node.js >= 24.0.0; embedded editor runtimes must be checked independently.


Installation

npm install @kirigami/php-wasm

Usage

1. High-level execution with Outbound Networking

The package provides a built-in proxy architecture (node:http, node:net & node:dgram) that routes Emscripten SOCKFS actions into genuine outbound TCP traffic, and UDP for datagram sockets (one WebSocket message per datagram). It also automatically binds your Node environment's root certificates (node:tls) to the PHP layer so cURL and OpenSSL HTTPS requests work immediately.

UDP works through PHP streams (stream_socket_client('udp://host:port'), fsockopen('udp://…')) with their usual read timeouts. With the sockets extension, socket_sendto()/socket_recvfrom() relay too: a blocking receive waits for a datagram (up to SO_RCVTIMEO when set), and socket_select() reports a UDP socket readable only once a datagram is queued, so C libraries that wait with select()/poll() work (ext/snmp over UDP, for example). connect() waits for the proxy to reach the destination and fails with ECONNREFUSED when it can't; a non-blocking connect returns EINPROGRESS. Blocking TCP reads wait for data, EOF or SO_RCVTIMEO. TCP_NODELAY and SO_KEEPALIVE are applied by the proxy to the real connection (also when set before connect()), SO_RCVTIMEO/SO_SNDTIMEO are handled inside PHP-WASM, and other options fail with ENOPROTOOPT.

import { getPHPRuntimeWithNetwork, jspi } from '@kirigami/php-wasm';

// Guard: verify JSPI is available before proceeding
if (!(await jspi())) {
  throw new Error('WASM JSPI is not available in this runtime.');
}

// Spins up the runtime and its companion local proxy on a random free port
const php = await getPHPRuntimeWithNetwork();

php.writeFile('/network-demo.php', `<?php
  // Native HTTPS request inside WASM using cURL!
  $ch = curl_init("https://api.github.com/zen");
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_USERAGENT, "Kirigami-PHP-WASM");

  $response = curl_exec($ch);

  echo "GitHub says: " . $response;
`);

const streamedResponse = await php.runStream({ scriptPath: '/network-demo.php' });

console.log(await streamedResponse.stdoutText);

// Clean up the proxy server when done if necessary
if (php._networkProxyServer) {
  php._networkProxyServer.close();
}

2. Quick execution with exec()

For one-off PHP snippets, exec() skips the manual writeFile/runStream dance: it writes your code to a temporary file, runs it, cleans up, and gives you back a plain result object.

import { exec } from '@kirigami/php-wasm';

// Standard runtime (no networking)
const { returnCode, stdout, stderr } = await exec('echo "Hello, Kirigami!";');
console.log(returnCode, stdout, stderr); // 0 "Hello, Kirigami!" ""

// Pass `true` as the second argument to run against the network-enabled runtime
const net = await exec(`
  $ch = curl_init("https://api.github.com/zen");
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  echo curl_exec($ch);
`, true);
console.log(net.stdout);

The <?php opening tag is added automatically if you don't include it.

Two small helpers are built on top of exec():

import { phpversion, phpinfo } from '@kirigami/php-wasm';

console.log(await phpversion()); // "8.5.11"
console.log(await phpinfo());    // full phpinfo() HTML output

3. Standard isolated runtime

If you need the raw PHP instance instead of the exec() shorthand — for example to keep writing files to its virtual filesystem across multiple calls — use the lightweight isolated helper:

import { getPHPRuntime } from '@kirigami/php-wasm';

const php = await getPHPRuntime();
php.writeFile('/version.php', '<?php echo PHP_VERSION;');
const streamedResponse = await php.runStream({ scriptPath: '/version.php' });
console.log(await streamedResponse.stdoutText); // "8.5.11"

getPHPRuntime() and getPHPRuntimeWithNetwork() are memoized: every call within the same process returns the same shared instance, so state persists between calls.

4. Low-level configuration (Manual)

For manual construction, use loadPHPRuntime() and pass its runtime ID to the PHP constructor:

import { getPHPLoaderModule } from '@kirigami/php-wasm';
import { PHP, loadPHPRuntime } from '@php-wasm/universal';

const loaderModule = await getPHPLoaderModule();
const runtimeId = await loadPHPRuntime(loaderModule);
const php = new PHP(runtimeId);

php.writeFile('/hello.php', '<?php echo "Hello, Kirigami!";');
const streamedResponse = await php.runStream({ scriptPath: '/hello.php' });
console.log(await streamedResponse.stdoutText); // Hello, Kirigami!

Security considerations

exec() and the low-level PHP instance run inside the compiled WASM/Emscripten sandbox: PHP code sees a virtual filesystem (writeFile/unlink operate on it, not on your real disk) unless the host explicitly mounts files or installs bridges for filesystem, environment, or process access. php-prepros configures additional integration; do not treat arbitrary project PHP as untrusted isolated input. This is not equivalent to child_process.exec(), which runs directly on the host.

Two things narrow that isolation and are worth keeping in mind:

  • getPHPRuntimeWithNetwork() gives the sandboxed PHP instance genuine outbound TCP and UDP access via the local proxy (not just HTTP/HTTPS). The proxy itself binds to 127.0.0.1 only, but the PHP code running inside can now reach out to the network like any other client.
  • WASM sandboxing reduces host exposure but isn't a substitute for a security boundary like a container or VM if you're running fully untrusted PHP (e.g. user-submitted code) — apply the isolation appropriate to your threat model on top.

TypeScript

The instances returned by getPHPRuntime() and getPHPRuntimeWithNetwork() aren't plain @php-wasm/universal PHP objects — this package attaches its own convenience members to them, and the types reflect that:

  • getPHPRuntime() resolves to a KirigamiPHP, which extends PHP with a bound .setIniValues(values) method.
  • getPHPRuntimeWithNetwork() resolves to a KirigamiNetworkPHP, which further adds ._networkProxyServer (the node:http Server backing the outbound proxy).
import { getPHPRuntimeWithNetwork } from '@kirigami/php-wasm';

const php = await getPHPRuntimeWithNetwork();
php.setIniValues({ memory_limit: '256M' }); // typed, no cast needed
php._networkProxyServer.close();            // typed, no cast needed

Package contents

@kirigami/php-wasm
├── index.js              # ESM entry point (re-exports runtime + loaders)
├── index.d.ts            # TypeScript declarations
├── runtime/
│   └── runtime.js        # Networking proxy and runtime helpers
├── jspi/
│   ├── php_8_5.js        # Emscripten-generated Node.js loader (JSPI build)
│   └── 8_5_11/
│       └── php_8_5.wasm  # Compiled PHP 8.5.11 WebAssembly binary (size depends on the embedded build)
└── LICENSE

PHP version

This package ships PHP 8.5.11.

The version is encoded in the package version number (major.minor.patch → 8.5.11) so that the installed PHP version is always immediately visible from package.json.


Static extensions

Baked directly into the compiled php.wasm binary — always loaded, no separate install. Built by php-wasm-compiler, whose config.yaml is the single source of truth for this list.

| Extension | Purpose | |---|---| | curl | HTTP/HTTPS client — also what getPHPRuntimeWithNetwork()'s outbound proxy rides on | | openssl | TLS/crypto primitives | | iconv | Character set conversion | | libxml / dom / simplexml / xmlreader / xmlwriter | XML/DOM support | | mbstring | Multibyte string handling, built with oniguruma regex support | | gd | Image processing/generation | | imagick | ImageMagick bindings | | exif | Image metadata reading | | sockets | Low-level socket functions | | zip | ZIP archive read/write (from libzip) | | sqlite3 / pdo / pdo_sqlite | SQLite database + PDO abstraction | | bz2 | BZip2 compression — also gives Phar its .tar.bz2 archive support | | opcache | Bytecode caching (JIT disabled) | | yaml | YAML 1.1 parsing (LibYAML) — see the YAML 1.1 scalar-coercion note above | | jsonpath | JSONPath queries over decoded JSON (3.1.0, from supermetrics-public/pecl-jsonpath) | | apcu / igbinary | In-memory user cache + compact binary serialization (igbinary is also apcu's default serializer) |

Query getLoadedExtensions() at runtime for the exact installed inventory rather than assuming an extension from another PHP build is available.


In-house extensions

Four of the extensions above are Maxime Larrivée-Roy's own PHP extensions, purpose-built for Kirigami and vendored straight from their own repos rather than pecl.php.net:

  • jsonk — fast JSON encode/decode plus JSON Schema validation (vendored simdjson/yyjson); replaces the built-in json_encode()/json_decode() when jsonk.replace_json_functions is enabled, the default in this build
  • navicat — native client for Navicat Premium's HTTP-tunnel protocol (ntunnel_mysql.php and friends), reusing the same libcurl already linked in above
  • mdhtml — CommonMark+GFM Markdown rendering via cmark-gfm, the backend behind php-prepros's MD:: wrapper
  • norm — Unicode normalization (a Normalizer class plus normalizer_normalize()/normalizer_is_normalized()), wrapping utf8proc

Loading additional extensions

Beyond the static set above, more PHP extensions ship as separate, on-demand WASM side modules — install any @kirigami/phpext-* package and @kirigami/php-wasm picks it up automatically at runtime, no core rebuild needed (see Automatic extension discovery below for the mechanism). Published by php-wasm-compiler: anydoc, dba, enchant, fastchart, ffi, fileinfo, ftp, gettext, gmp, intl, ldap, mysqli (bundles mysqlnd), odbc, pdo_dblib, pdo_firebird, pdo_mysql (bundles mysqlnd), pdo_odbc, pdo_pgsql, pgsql, posix, rar, scanmeqr, snmp, soap, sodium, tidy, xsl. intl is about 38 MB, since it embeds the ICU data; anydoc is written in Rust, and a panic there aborts the PHP runtime.

npm install @kirigami/phpext-pgsql

Related


Runtime extension inspection

Inspect the embedded runtime used by your checkout:

npx kiri phpinfo -m
# From the monorepo, without relying on a globally installed CLI:
node packages/cli/bin/kiri.js phpinfo -m

-m / --md selects Markdown output; -j / --json selects JSON. The full output includes runtime settings and environment data, so it is generated on demand instead of maintained as a large machine-specific snapshot here.

The local runtime inspection on 2026-09-20 confirmed mdhtml with cmark-gfm and yaml with LibYAML enabled. MD:: delegates to mdhtml; YAML:: delegates to native YAML. The PHP YAML path uses YAML 1.1 scalar rules, including implicit booleans; quote string keys/values such as "NO". Node’s kirigami.yaml parsing is a separate path. See the PHP class reference for wrapper behavior and Markdown migration notes.

Other bundled capabilities include JSON/JSONK, DOM/XML, GD/Imagick, cURL/OpenSSL, SQLite, APCu, igbinary, norm, lexbor, and navicat. Query getLoadedExtensions() for the exact installed inventory rather than assuming an extension from another PHP installation is available.

import { getLoadedExtensions } from '@kirigami/php-wasm';
console.log(await getLoadedExtensions());

The standard and network helpers each cache their runtime. Closing the network proxy is final for that cached instance; it is not a per-request cleanup step followed by automatic reopening.

Automatic extension discovery

At runtime creation, the loader searches for directories named phpext-*, including those under @kirigami, in node_modules and packages under the current working directory and its ancestors. It also searches npm's global root for globally installed extension packages. That root is computed the way npm resolves its global prefix (npm_config_prefix, then prefix= in ~/.npmrc, then the default next to the Node executable) rather than by spawning npm; a prefix set only in a global or builtin npmrc is not seen. Sibling projects are not searched, so an unrelated compiler checkout next to a Kirigami site cannot affect its runtime.

When a package's index.js exports a default register(phpVersion) function (every package generated by php-wasm-compiler does), the loader calls it with the runtime's PHP major/minor version. It returns the package's modules in load order as { name, soPath, iniEntries? } entries, so one package can bundle a dependency module ahead of its extension (phpext-mysqli ships mysqlnd, then mysqli). iniEntries (an object of php.ini settings, also read from manifest.json in the fallback below) are written as key=value lines after the module's extension directive. A module bundled by several packages is loaded once.

Otherwise, manifest.json may provide an extension name and an artifacts array with phpVersion and sourcePath. The loader prefers an artifact matching the runtime's PHP major/minor version, then a matching patch version, then a generic artifact. If no usable manifest artifact is found, it recursively selects the first .so file.

The resolved extensions are staged under /internal/shared/extensions, each with an NNN-<name>.ini file holding its extension directive. The numeric prefix keeps the discovery order, since PHP reads that directory alphabetically.

Set KIRIGAMI_PHPEXT_DISCOVERY to local to skip the global root, or to off to disable discovery entirely (the regression suite uses off). There is no explicit search-root option. Artifact selection does not establish binary compatibility. Use getLoadedExtensions() to check what actually loaded; finding or staging a .so does not prove that PHP accepted it. Cached runtimes are not rescanned for every execution.


License

This package is distributed under GPL-2.0-or-later and is treated as a separate runtime component from the project core. See LICENSE for the full text.

The repository root is licensed under GPL-3.0-or-later, but @kirigami/php-wasm remains independently licensed because it contains a compiled PHP/WASM runtime and binary assets with their own provenance and compatibility constraints. See NOTICE for the package-level summary.