How one app
runs everywhere.
Compile ahead of time, render with a tiny engine, bridge to the host, serve. Four layers, one compiled bundle. Two of the four are fully portable, the client runtime is free, and only the JS engine is decided per target. There is no compiler on the device.
Compile, render, host, serve
You ship JavaScript and a set of polyfills, not a toolchain. The compile output and the polyfills are fully portable; the host and shell are re-implemented small per target; and the client island runtime runs unchanged on every one of them, for free.
Every route is a pure function
The compile step turns each route into one exported function that takes props and returns an HTML string. No DOM, no platform, no I/O at the boundary - just a pure transform the engine can call. That purity is what lets the same bundle run on two different engines and emit the same bytes.
// every route compiles to a pure function
globalThis.__bextPrismRender(props) // => HTML string
// + 122 KB of pure-JS, engine-agnostic polyfills
// (Intl, Buffer, TextEncoder, crypto, URL, fetch, streams, timers, process)
// runs the same on V8 and QuickJS
The polyfills are themselves plain JavaScript, so they travel with the bundle rather
than living in a runtime. Evaluate the bundle, evaluate the polyfills, call __bextPrismRender - that is the entire render path, and
it has no dependency on which engine is doing the evaluating.
V8 or QuickJS, per target
The one per-target decision is which JS engine evaluates the bundle. It is forced by two hard external facts, not by preference.
iOS forbids JIT for third-party apps. V8 compiles JavaScript to machine code at runtime, so its JIT cannot run inside an iOS app. QuickJS is a pure interpreter - it executes the same bundle with no code generation, so it ships through App Store review.
You cannot embed V8 inside a WASM module. Edge platforms (Cloudflare Workers, Fastly Compute) and the browser run your code as WASM, so they need an interpreter that compiles to WASM itself. QuickJS does, via Javy/WASI; V8 does not.
So bext-lite runs V8 where it can - desktop and the single-binary server, where it is already fast and boots React from a snapshot - and QuickJS everywhere a JIT or a native engine is off the table: IoT, iOS, Android, edge/WASM and the browser. The compiled bundle does not change; the engine underneath it does.
Byte-faithful, every target
The reason two engines is safe rather than a liability: they produce the same output, down to the byte.
Every target renders the exact HTML the bext server would. The compiled bundle plus the 122 KB of pure-JS polyfills produce byte-identical output on QuickJS and on V8 - verified across string-builder and React routes. What you test is what every device ships.
The client side is free
The four layers cover the server render. The client half costs nothing extra on any target.
The island hydration runtime is pure browser JS with zero server coupling. It has no dependency on the engine, the host, or the shell - so it runs unchanged inside a Tauri WebView, a mobile WebView, the browser or a service worker. Every target gets interactive islands at no extra cost, because the client runtime is the same file everywhere.
See it on every target
One bundle, six runtimes - pick the shape that fits, then ship it in two commands.