PocketJS
Create on every screen you love. Build apps and games for your favorite
devices, from a PSP to your desktop, with familiar JavaScript components and a
compact native runtime.

Website ·
Playground ·
Documentation ·
Blog ·
Changelog ·
Discord ·
X
Applications are written in TypeScript with Solid, Vue Vapor or Octane
components, which all render to one native tree. A QuickJS guest runs the
application, and a Rust core performs flexbox layout and draws every pixel in
one thread inside one process. There is no DOM, no CSS engine and no
WebView. PocketJS is a Pocket Nexus project.
In this repository
Beside the PocketJS runtime, this repository holds the technology that Pocket
projects build on:
| PocketJS | The application runtime: TypeScript components, a QuickJS guest and a Rust core that lays out and draws every pixel | This README · pocketjs.pocket.nexus |
| Pocket3D | A hardware-native 3D stack: device kernels for GXM, GE and PICA200, and the mechanisms that purpose-built 3D engines share | pocket3d/ · 3d.pocket.nexus |
| MicroTS | An ahead-of-time compiler from TypeScript views and models to Rust, in development | microts/ |
Contents
Familiar tools
Three TypeScript framework adapters target the same native tree and run on the
same QuickJS guest. The choice changes application code and nothing below it.
| Solid | solid-js | TSX |
| Vue Vapor | vue | TSX and <script setup lang="ts"> single-file components |
| Octane | octane | Compiled hooks and TSX, with no virtual DOM |
Framework primitives are imported from solid-js, vue or octane. PocketJS
owns the runtime, host components, lifecycle wiring, input, animation, assets
and the native boundary.
import { createSignal, Show } from "solid-js";
import { mount } from "@pocketjs/framework/solid";
import { Text, View } from "@pocketjs/framework/solid/components";
function Counter() {
const [count, setCount] = createSignal(0);
return (
<View class="w-full h-full flex-col items-center gap-4 p-4 bg-slate-50">
<Text class="text-xl text-slate-950 font-bold">Count: {count()}</Text>
<View
class="px-4 py-2 rounded-xl shadow-md bg-blue-600 focus:bg-blue-500"
focusable
onPress={() => setCount(count() + 1)}
>
<Text class="text-base text-white font-bold">Press Circle</Text>
</View>
<Show when={count() > 3}>
<Text class="text-sm text-emerald-600">Reactive on real hardware.</Text>
</Show>
</View>
);
}
mount(() => <Counter />);
Styling
Class literals are compiled into a baked style table at build time. The runtime
resolves a class attribute by lookup, so there is no CSS parser, cascade,
specificity resolution or reflow on the device. The accepted vocabulary is a
fixed Tailwind subset, enumerated in
Styling.
One bundle, many screens
One bundle serves machines of different densities. An application declares its
viewport and required APIs in pocket.json, and a target profile must satisfy
that declaration before compilation and packaging proceed. Run from the
application directory:
pocket create my-app
pocket check --target psp
pocket check --target vita
From your component to a pixel
The guest emits tree mutations; the core owns layout, the style table and the
draw list; a per-target backend submits that draw list through GE, GXM, Metal,
wgpu, software rasterization, e-ink updates or another declared host.
PocketJS · 1 thread, 1 process
your component guest
renderer adapter guest
native tree core
flexbox layout, baked style table core
drawlist core
backend draw core
→ pixels
Browser or WebView · 4 threads, 2 processes
your component main
framework runtime, vdom diff main
dom mutation main
cssom, cascade, specificity main
style recalculation main
layout, reflow main
paint records main
commit the layer tree across threads
layer tree, tiling compositor
queue raster tasks, invalidations compositor
rasterization raster pool
ipc to the gpu process, sync fences
draw quads gpu
present gpu
→ pixels
See also: Architecture ·
Frameworks
Smooth motion
Keyframe timelines and spring curves are baked into the same style table at
build time and advanced by the Rust core on its own clock, so a screen animates
with no per-frame JavaScript. Motion Lab runs the yui540 studies in
WebAssembly on the website, inside an
interactive PSP model, and on the handheld they were written for.
With no browser engine in the pipeline, the cost of a screen stays close to what
the hardware can do. A complete application drawing an animated interface
occupies 8 MB on a single 333 MHz core: a quarter of the PSP's 32 MB, one
part in 1536 of a 12 GB iPhone 17 Pro Max, on a core clocked 13 times slower
than an A19 Pro performance core at 4.26 GHz.
On a Sony PSP
One MIPS core at 333 MHz, 32 MB of RAM, measured against the 16.67 ms budget for
60 fps:
| OpenStrike frame budget | 2.2 ms of JavaScript, 8.4 ms of total CPU work, worst observed frame 9.7 ms |
| Hero demo, cost of a virtual DOM | Solid 15.15 ms · Vue Vapor 16.74 ms · Vue with a virtual DOM 90.75 ms |
| Hero demo, the three shipped frameworks | Solid 3.66 ms · Vue Vapor 3.61 ms · Octane 6.53 ms |
Seven samples per application. The two hero-demo rows come from separate runs
with different toolchain versions, so each row is comparable internally but not
against the other.
On a desktop
Historical August 2026 results for the previous gpui host: the same markdown
editor built three ways on an Apple M3 Max
(full report, reproduced by
bun tools/bench-desktop.ts):
| Processes | 1 | 4 | 5 |
| Cold start to first painted frame | 149 ms | 380 ms | 301 ms |
| Idle resident memory | 83 MB | 193 MB | 382 MB |
| On disk | 10 MB | 9 MB | 242 MB |
With a document open and no input, the pocket build redraws about twice a
second, for the blinking caret. The report also records where the pocket
build loses: its storm CPU rises with document length, because the editor
re-wraps the whole document through the QuickJS interpreter on every keystroke.
See also: Shipping OpenStrike ·
Pocket Character ·
Twice the pixels, zero forks ·
The first iPhone
Replay every frame
PocketJS advances an application one frame(buttons) call at a time, and time
is the frame counter. A frame is a transaction that nothing outside it can
interrupt, and nothing waits on a wall clock, so tests replay the same sequence
as fast as the CPU allows without changing the timing they measure: a journey
that takes six seconds in front of a user is a few dozen frames in CI.
state n+1 = F(state n, input n)
pixels n = G(state n)
- Effects land on frame boundaries. A network reply that arrives partway
through frame +3 is queued, not applied. It is delivered at the start of
frame +4, in FIFO order, before any application hook runs. There are no
microtask races and no mid-frame callbacks, and
after() replaces
setTimeout with a deadline measured in frames.
- An async task lands on the same frame every run. Driven by
requestAnimationFrame against a wall clock, one awaited confirmation lands
on 22 different frames across 60 runs, and its timing assertion passes
9 times out of 60. On the frame clock it lands on frame 144 in every
run, 60 out of 60.
- History is a data structure. Tapes replay byte for byte, a session
subsampled to 2 Hz is byte-identical to its 60 Hz counterpart, and forking a
tape at frame 9 to splice in a different press produces the other outcome in
22 ms.
- Chaos mode checks the guarantee, injecting sleeps, allocation churn and
forced GC between frames without moving the trace by one bit.
See also: The runtime that can't flake ·
Time-travel DevTools ·
Determinism
Apps and games
Apps and games share the same runtime. Cores are independent native modules,
loaded the way a kernel loads drivers: an application takes the ones its
content needs, such as networking, audio or 3D, and the rest never enters the
build. The JavaScript uses them beside the same UI and input APIs.
TypeScript application → guest bundle, one frame at a time
ui tree, layout, draw, input, focus
net poll batches
audio pcm mixer
strike bsp, bots, hits
voxel chunks, meshing
See also: Architecture ·
The runtime family ·
Pocket3D
Choose your screen
PocketJS has booted on every operating system below, on the real machine. What
changes between them is one native host, never the application, and each row
links to the post or pull request that brought it up. Pocket Museum repairs and
maintains the older machines used to develop and test PocketJS.
Devices it has booted on: Sony PSP (2004), PS Vita (2011),
iPhone (2007), iPhone 4S (2011), iPod touch 6 (2015),
Nokia E7 (2011), Meizu M8 (2009), BlackBerry Classic (2014),
PocketBook reader (e-ink), ESP32-P4 devkit (microcontroller) and
Mac (Apple silicon). The Nintendo 3DS runs several of the applications in
the next section.
The authoritative host and target inventory is
contracts/spec/platforms.ts; each entry
records what has been verified and how. See
Platform contracts and
the Native contract.
Made with PocketJS
A music player for a PSP, a workspace on a 3DS, a game to take along. Each row
links to the project, and most to the story of how it was built.
| OpenStrike | PSP, PS Vita | A Counter-Strike-shaped shooter on 2004 hardware: BSP maps, bots and a HUD written in Solid JSX, at 60 fps with 2.2 ms of JavaScript per frame. Story |
| Pocket Voxel | PSP, PS Vita, web | A creature-RPG town rebuilt as a walking voxel diorama. Game state lives in the JS guest; logic runs at 60 Hz while presentation holds a locked 30 fps beat. Story |
| Pocket Figma | PSP, PS Vita | A 14,430-node design file, cooked into streamed tile pyramids and panned with the analog nub at 60 fps on a handheld with 32 MB of RAM. Story |
| Pocket YouTube | 3DS, PSP, PS Vita | Watch on the upper screen while searching and browsing on the touch screen. A Mac companion streams video to the 3DS over Wi-Fi. Story |
| Pocket Shell | 3DS | A tiling interface: windows on the upper screen, a workspace and control deck below |
| Pocket Doc | 3DS | A Markdown library on two screens: read above, edit and navigate below |
| Pocket Map | 3DS, PSP | OpenStreetMap or Hyrule on a handheld: pan with the touchpad, zoom, search and save places through a paired Mac |
| Pocket Term | 3DS | Mac shell sessions above a touch keyboard |
| PSPMAN | PSP | A Walkman-inspired music player by ObsoleteSony: local FLAC and MP3, album art and cassette mode |
| Pocket Character | Mac | A rigged VRM companion in a transparent always-on-top window, rendering skinned 3D at 60 fps in one process and 118 MB, against 8 processes and 2184 MB for an Electron build of the same idea. Story |
| Pocket DevTools | every backend | Time-travel debugging over a USB cable at 2 bytes per frame. The inspector highlight is emitted by the core into the draw list, so it renders on the device |
| Pocket Launcher | PSP, PS Vita | Whole-application lifecycle, target admission, frozen shots and guest switching |
| Pocket Pi | QuickJS guest | A coding agent running inside the QuickJS guest environment, with no Node underneath |
Pocket Voxel, captured on a PSP-2000: the flat Game Boy world standing up as geometry. The making-of story.
Get started
The zero-install path is the online
Playground. Local browser
development requires Bun and
Rust via rustup:
git clone https://github.com/pocket-nexus/pocketjs
cd pocketjs
bun install
rustup target add wasm32-unknown-unknown
bun run dev
The CLI operates inside a PocketJS checkout:
npm install -g @pocketjs/cli
pocket doctor
pocket setup
pocket create my-app
pocket check --target psp --manifest apps/my-app/pocket.json
pocket build --target psp --manifest apps/my-app/pocket.json -- --release
Vita packaging additionally requires VitaSDK and the pinned Rust toolchain
documented in hosts/vita/README.md. Guest builds can
be packaged as inspectable, target-thinnable
.pocket files instead of per-port directories. The
Getting started guide
walks through a first application.
Ahead-of-time compilation
The TypeScript support reference
compares ordinary application code, AOT views and compiled model bodies,
including their restrictions and current implementation limits.
MicroTS compiles
Solid TSX and Vue SFC views to Rust through a shared typed View IR.
app.model: "compiled" also compiles the supported TypeScript model subset
to Rust. The default "rust" mode uses an application-provided Rust model
implementing the same generated trait. Native AOT calls the retained UI core
without a guest engine; the host still owns input, services and presentation.
bun microts/compiler/cli.ts build solid-aot-lab --strict
cargo check --locked --manifest-path apps/solid-aot-lab/Cargo.toml
The guest build lowers that compiled model's TypeScript source to a bundle
with the same frame-synchronous reaction and task semantics. JavaScript is an
execution format; the application source contract remains TypeScript. Rust
AOT uses alloc, with capacity tags for bounded strings and arrays. Demo
gen/ directories are ignored and must be regenerated before Cargo builds.
The earlier C/cartridge experiment
lives in a separate repository with its own TypeScript subset, target profiles,
examples and toolchains. Those sources and scripts are not part of this
repository or the Rust AOT build.
Working on PocketJS
Repository layout
framework/ | Public framework APIs, renderers, components, input, lifecycle and build-time styling |
engine/ | no_std UI core, render backends, native modules, Pocket3D and platform-native crates |
devices/ | Pocket3D device kernels for GXM, GE and PICA200 |
pocket3d/ | The Pocket3D entry point: its design and a map of where its code lives |
microts/ | The MicroTS ahead-of-time compiler |
contracts/ | Generated wire specs, capability registry, manifests, build plans and package formats |
hosts/ | PSP, Vita, 3DS, web, desktop, e-reader, phone and MCU host integrations |
hosts/esp-idf/ | Composable package, QuickJS, UI, RGB565, PPA and runner components for P4/S3 firmware |
apps/ | Framework demos and system applications used by the launcher and acceptance suites |
tools/ | Build, package, launcher, device, DevTools, benchmark and release commands |
tests/ | Contract, compiler, simulation, emulator, package and golden verification |
docs/ | Platform, runtime, determinism, DevTools, backend and benchmark records |
site/ | This website (site/nexus/ holds the pocket.nexus homepage, site/pocket3d/ the 3d.pocket.nexus homepage) |
Building and testing
Emulator journeys require their external toolchains:
bun run test
bun run golden
bun run e2e
bun run e2e:vita
bun run site:build
bun run site:preview
Documentation
Sponsors and Pocket Nexus
PocketJS is developed full-time with the support of its
sponsors, who are listed on
the website.
PocketJS is built by Pocket Nexus, an independent,
non-VC-backed lab that starts from this runtime to explore new possibilities in
computing, interaction and creation, so that the joy of creating belongs to
everyone. Its other projects are listed on the
GitHub organization.
Attribution
The original motion studies are by yui540. PocketJS
accepts yui540's two stated conditions for continued use: Motion Lab carries the
requested (yui540) on-screen credit, and any other yui540 animation requires
separate permission before it is ported. The accepted scope and
capture-maintenance rules are recorded in
apps/motions/ATTRIBUTION.md.
License
PocketJS and MicroTS are MIT licensed. Inter is vendored under the
OFL in assets/fonts/.
Pocket3D, the files under pocket3d/, devices/
and engine/pocket3d/, is under the
Pocket3D License. It grants what the MIT License grants,
with one more condition: a distributed product that draws 3D scenes with
Pocket3D shows the Pocket3D title card when it starts. PocketJS's own hosts
and the applications built on them are exempt. A separate written license
removes the condition: write to support@pocket.nexus.