Deploy targets
Write a PRISM app once, then run it as a native binary, a desktop app, an edge function, a client-side browser app, a mobile app, or an embedded ARM build. Same source, same rendered HTML, six different runtimes.
The idea is swap the runtime, not the code. The AOT exporter turns your app into one bundle of plain JavaScript that references host globals by name. What changes per target is not your code, it is which JS engine evaluates the bundle and which shell serves it. Nothing about your routes, loaders, or islands is target-aware.
The four layers#
Every target is the same stack with two of its four layers swapped:
| Layer | What it does | Portability |
|---|---|---|
| Compile | ts-rs transpiles your TSX and turbopack bundles each route to CJS that references host globals by name. You ship JavaScript, there is no compiler on the device. | Fully portable |
| Render | A JS engine evaluates the bundle plus a pure-JS polyfill layer, then calls __bextPrismRender(props) to produce HTML. V8 on desktop and server, QuickJS everywhere else. |
Polyfills portable |
| Host | Rust implementations of the bridge functions: filesystem, HTTP, SQLite (KV + per-app DB), object storage, env, logging. | Re-implemented per target |
| Shell | A tiny single-app router that matches a route and serves the result, or a WebView, or an edge fetch handler. No WAF, no TLS, no multi-tenancy. | Small per target |
Compile and the polyfills are identical everywhere. Only the render engine, the host bridge, and the shell change. That is the whole portability story.
Renders are byte-identical across engines. A compiled PRISM bundle produces the exact HTML the bext server would, verified the same on QuickJS and V8, so what you test is what every device ships.
Single static binary#
What it is. A 1.8 MB self-contained host plus your exported bundle, in one file. No Node, no V8 cage, no install step. Double-click to run.
- Engine: V8 or QuickJS
- Footprint: ~4 MB total
- When to use it: internal tools, side-car services, air-gapped deploys.
It ships a hand-rolled HTTP/1.1 server with static assets, dynamic routes, and ISR. Single-tenant and fast. Cross-compiles to Linux, macOS, and Windows from one command.
bext-lite serve ./dist
./dist is a bundle you exported first with bext-lite build.
Desktop app#
What it is. A native desktop app, roughly 10x smaller than an Electron build. A Tauri shell wraps the embedded host and points a native WebView at it.
- Engine: V8 (snapshot React)
- Footprint: 5 to 20 MB
- When to use it: cross-platform SaaS-as-a-desktop-app from the same codebase.
It bundles to .dmg, .msi, .deb, and AppImage with code-signing and
notarization built in, and an auto-update channel ships out of the box.
bext-lite build ./app --target desktop
Edge functions#
What it is. PRISM at the edge, compiled to a QuickJS-on-WASM module (Javy / WASI) that runs in any V8-isolate edge.
- Engine: QuickJS on WASM
- Footprint: sub-MB cold
- When to use it: globally-distributed SSR with no servers to run.
Platform adapters bridge to Workers KV, D1, and the native fetch. Deploy to Cloudflare Workers or Fastly Compute with no cold-start tax.
bext-lite build ./app --target edge
Browser / offline#
What it is. The whole app running client-side, inside a service worker. Fully client, installable, offline-first.
- Engine: QuickJS on WASM
- Footprint: ~3.5 MB cached
- When to use it: demos, local-first apps, zero-backend tools.
The island runtime is already pure browser JS, so hydration is free. Reads and writes go to local SQLite (WASM) with optional sync.
bext-lite build ./app --target browser
Mobile (iOS & Android)#
What it is. A real native app that calls device APIs from your web code. The
camera, biometrics, geolocation, secure storage, haptics, share, and
notifications are all reachable from JS through bext.device.*. One SDK channel,
no native code to write.
- Engine: QuickJS (no JIT)
- Footprint: per-render <5 MB
- When to use it: real native apps (camera, Face ID, offline data) from one web codebase.
QuickJS is a pure interpreter with no JIT, so it ships through App Store review by
construction. React "use client" pages and string-builder pages both
AOT-compile to native bundles. A local SQLite ships for offline data, and
deep-link and notification-tap events stream back into the app.
bext-lite mobile run ios
The full mobile story is in /docs/mobile; the CLI is in /docs/mobile-cli; the device API reference is in /docs/device-sdk.
IoT / embedded#
What it is. The same app on a Raspberry Pi or a small ARM box. QuickJS is a pure interpreter with no 4 GB pointer cage, so it runs on tiny hardware.
- Engine: QuickJS
- Footprint: <5 MB plus your app
- When to use it: kiosks, gateways, on-prem appliances.
Cross-compiles to aarch64 and armv7 from the same exported bundle, with local SQLite and storage and no cloud dependency.
bext-lite build ./app --target aarch64
How they compare#
| bext-lite | Electron | Workers | |
|---|---|---|---|
| Footprint | ~4 MB | 150 MB+ | platform-bound |
| Desktop | yes | yes | no |
| Edge / WASM | yes | no | yes |
| Mobile | yes | no | no |
| Embedded / ARM | yes | no | no |
| One codebase | yes | per-target | per-target |
The engine split is the design in one line: V8 where you have the room (desktop, server), QuickJS where you do not (edge, browser, mobile, embedded). QuickJS is a pure interpreter, so it is small, jitless, and legal on iOS, at the cost of raw render speed you do not need on those targets.