· 5 min read

The engine stays home

Porffor points toward JavaScript at the edge by compiling each program ahead of time into Wasm while leaving the engine, parser, and optimization work at build time.

A restored green-and-steel horizontal mill engine with a tall flywheel, standing in a museum engine house under a wooden roof.
Daderot, CC0

I put the Porffor homepage beside Cloudflare's WebAssembly guide and felt the pieces line up. The Workers documentation warns that compiling a language to Wasm often drags its runtime along, making the module larger and slower to start. Porffor begins with no interpreter or JIT and no fixed prelude shipped with every program.

That is why I am excited. Porffor compiles one JavaScript or TypeScript program ahead of time, emits the Wasm itself, and leaves the language engine on the developer's machine. An edge host receives a specialized module instead of a small application strapped to a general-purpose JavaScript engine.

The project calls itself experimental and warns people away from serious use. Good. Its gaps show where the architecture ends and the product work begins.

Pay for JavaScript once#

The common way to run JavaScript inside Wasm is to compile an interpreter, place the source or bytecode beside it, and let that interpreter do the real work after deployment. Compatibility comes from carrying the machine that understands the language. Every tenant gets a copy of the same fixed machinery before their own code contributes a useful instruction.

Porffor takes the expensive route during development. Its design notes describe a from-scratch AOT compiler that writes final Wasm binaries without Binaryen and ships no constant runtime or prelude. Acorn supplies the parser. Porffor owns the value model, code generation, optimizer, built-ins, and binary assembly, aimed at exactly the fixed cost an edge platform wants to remove.

Engine in WasmPorffor AOT
ArtifactInterpreter or runtime plus source or bytecodeSpecialized module with no fixed prelude
Startup workStart the engine, then load the programInstantiate the finished program
Compatibility costBroad semantics arrive with the embedded engineThe compiler must implement every supported semantic
Host boundaryThe interpreter's embedding APIExplicit imports and Porffor's value ABI
The AOT column removes the fixed interpreter from the deployed artifact and moves compatibility work into the compiler.

A small policy at every edge#

I started with the kind of function an edge service actually repeats all day: choose an origin from a subscription plan and a risk score. It has no filesystem, network, event loop, or package graph. It has three returns.

policy.jsjs
export function chooseOrigin(plan, risk) {  if (risk >= 80) return 2;  if (plan === 0) return 1;  return 0;}

The build command is already pleasantly literal: porf wasm policy.js policy.wasm. A JavaScript Worker can receive the request, read configuration, and pass two numbers to the compiled policy. The host keeps its network and storage capabilities, while Wasm runs the tenant-defined decision behind linear memory and an explicit import boundary.

Cloudflare's JavaScript integration guide already supports importing a .wasm module and instantiating it once in module scope, before requests arrive. Workers also gives startup a one-second ceiling. Module size and initialization become operational concerns there. Why are we shipping a whole language engine to evaluate two comparisons?

The import object is the project#

The attractive diagram ends at the boundary between the Wasm module and its host. I sketched the Worker side and reached the empty object immediately:

worker-adapter.tsts
import policyModule from "./policy.wasm";const imports = {  "": {    // Porffor's stable edge host contract belongs here.  },};const policy = await WebAssembly.instantiate(  policyModule,  imports,);

Porffor's current JavaScript wrapper fills that namespace with printing and clock functions, instantiates the module, then decodes tagged values from linear memory into JavaScript values. That private bridge serves the CLI and playground. An edge adapter needs a stable version, plus error mapping, memory ownership, cancellation, and narrow host capabilities.

The absence of eval and Function bothers me less. AOT compilation has to reject source code that appears after compilation, and an edge policy language benefits from refusing that capability. Async and the host ABI block the useful subset today. Dynamic evaluation merely marks the boundary of the bet.

Start with kernels#

Full Worker compatibility would require Porffor or an adapter to expose Fetch, streams, storage bindings, timers, and a large async surface before anyone could use the first module. A smaller target keeps those APIs in the host and compiles synchronous kernels: routing rules, validation, feature decisions, or tenant scoring. JavaScript remains the authoring language, while the deployment artifact behaves like a capability-limited plugin.

Porffor is already probing adjacent deployments. Its repository contains an experimental Lambda path that lowers JavaScript through C into a native bootstrap. The browser playground imports the compiler, emits Wasm after an edit, instantiates it, and calls the result; the same build-first idea is visible there even though the host contract remains private.

In my notes, worker-adapter.ts still ends at the empty import object. The policy function has three returns. The adapter owes a value contract, error mapping, memory rules, and a capability story. I keep the file because the remaining work has an address now: the seam between a tiny Wasm program and the edge host.