PocketJS · JavaScript UI runtime

UI runtime for
every kind of
computer

PocketJS is a portable application runtime that turns modern component code into native pixels across radically different hardware.

Nintendo 3DS

Loading Nintendo 3DS

Drag the shell to rotate. Drag the lower screen to scroll, or use the arrow keys.

PS Vita

Loading PS Vita

Drag to inspect the shell, rear touch pad, cameras and ports.

Modern DX solid · vue vapor · octane

Write components as you already do, in Solid, Vue Vapor or Octane. Each one compiles to the same native tree and runs on QuickJS, so the framework you pick changes your code and nothing below it.

import { createSignal, Show } from "solid-js";
import { Text, View } from "@pocketjs/framework/solid/components";

export default function App() {
  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>
  );
}
import { ref } from "vue";
import { Text, View } from "@pocketjs/framework/vue-vapor/components";

export default function App() {
  const count = ref(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.value}</Text>
      <View class="px-4 py-2 rounded-xl shadow-md bg-blue-600 focus:bg-blue-500"
        focusable onPress={() => { count.value++; }}>
        <Text class="text-base text-white font-bold">Press Circle</Text>
      </View>
      {count.value > 3 ? (
        <Text class="text-sm text-emerald-600">Reactive on real hardware.</Text>
      ) : null}
    </View>
  );
}
import { useState } from "octane";
import { Text, View } from "@pocketjs/framework/octane/components";

export default function App() {
  const [count, setCount] = useState(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>
      {count > 3 ? (
        <Text class="text-sm text-emerald-600">Reactive on real hardware.</Text>
      ) : null}
    </View>
  );
}
$ pocket create my-app
$ pocket check --target psp
ok 480x272 · text.glyphs.baked · input.buttons
$ pocket check --target vita
ok same bundle, density 2, no component edited
from your component to a pixel
pocketjs1 thread, 1 process
  1. your componentguest
  2. renderer adapterguest
  3. native treecore
  4. flexbox layout, baked style tablecore
  5. drawlistcore
  6. backend drawcore
pixels
browser or webview4 threads, 2 processes
  1. your componentmain
  2. framework runtime, vdom diffmain
  3. dom mutationmain
  4. cssom, cascade, specificitymain
  5. style recalculationmain
  6. layout, reflowmain
  7. paint recordsmain
  8. commit the layer tree across threads
  9. layer tree, tilingcompositor
  10. queue raster tasks, track invalidations
  11. rasterizationraster pool
  12. ipc to the gpu process, sync fences
  13. draw quadsgpu
  14. presentgpu
pixels
Motion compiled at build time

Animation is compiled. Keyframe timelines and spring curves are baked into the style table at build time and advanced by the Rust core on its own clock, so a page can animate with no per-frame JavaScript at all. The panel beside this runs the yui540 motion studies in WebAssembly, inside the handheld they were written for.

Loading the motion studies
Press left or right on the d-pad, or the L and R shoulders, to switch studies.
Performance quickjs guest, rust core

Your app runs as a QuickJS guest on a Rust core that draws every pixel itself. With no browser engine in the middle, the cost of a screen is close to what the bare metal can do, whether the metal is a 333 MHz handheld or a Mac.

how much machine this asks for
Memory, against a current phone
iPhone 17 Pro Max 12 GB
Sony PSP, 2004 32 MB
The same left edge, magnified 384 times
Sony PSP RAM 32 MB
A PocketJS app 8 MB
Clock, one core
A19 Pro, performance core 4.26 GHz
Sony PSP, one MIPS core 333 MHz

A whole app, drawing a smooth interface, lives in 8 MB on a single 333 MHz core. That is a quarter of what the handheld has, one part in 1536 of the phone's memory, and a core clocked 13 times slower than the one in your pocket.

Benchmarked on a Sony PSP

one MIPS core at 333 MHz · 32 MB RAM · 2004
Frame budget at 60 fps (ms)
016.67 ms budget
javascript 2.2 ms rest of cpu work, to 8.4 ms headroom

That budget holds OpenStrike: a bot-populated BSP map with a Solid JSX HUD, running at 60 fps on hardware from 2004.

Why not just ship a virtual DOM · hero demo (ms)
solid15.15
vue vapor16.74
vue, vdom90.75

Classic Vue does run here, and on this screen its virtual DOM costs about six times the frame work of the same UI without one. Compiled reactivity is why the other two fit in the budget at all.

The three shipped frameworks · hero demo (ms)
solid3.66
vue vapor3.61
octane6.53

Seven samples per app, same 16.67 ms budget. These two charts come from separate runs and the toolchain got faster in between, so read each on its own terms rather than across them.

A markdown editor, built three ways

Apple M3 Max · medians of committed runs
Cold start to first painted frame (ms)
pocket149
tauri v2380
electron301
Idle resident memory (MB)
pocket83
tauri v2193
electron382

On disk the same three are 10, 9 and 242 MB, and only the pocket build runs in a single process. Left alone with a document open, it redraws about twice a second: the caret blinking, and nothing else. The published report also says where the pocket build loses, and why.

Architecture the frame contract

PocketJS makes time the frame counter: one frame(buttons) call is a transaction nothing outside it can interrupt. Nothing waits on a wall clock, so tests run 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.

where an effect actually lands
wall clock
network reply arrives mid-frame
input tape
0000000400040000 2000000000010000
frames
n+1+2+3 +4+5+6+7
pixels

A 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 app hook runs. Nothing lands halfway through a transaction: no wall clock, no microtask races, no mid-frame callbacks, and after() replaces setTimeout with a deadline measured in frames.

staten+1 = F(staten, inputn)
pixelsn = G(staten)
every app is a pure fold over its inputs
Which frame the same async task lands on, 60 runs each
wall clock, 60 runs 22 outcomes · assertion 9/60 140 145 150 155 160 165 tallest bar: 6 of 60 runs frame clock, 60 runs 1 outcome · assertion 60/60 140 145 150 155 160 165 frame 144, every run, forever

Same app, same awaited confirmation, same assertion. Driven by requestAnimationFrame against a wall clock it lands on 22 different frames, and the timing assertion passes 9 times out of 60. On the frame clock it lands on frame 144 in every run. That spread on the left is where a flaky test lives: the assertion is really a question about a race.

Thirteen-frame filmstrip of a whole session captured at 2 Hz

SUBSAMPLED · a whole session in 13 frames at 2 Hz, byte-identical to its counterpart in the 390-frame 60 Hz run.

Because history is a data structure, hypotheticals are cheap: fork the tape at frame 9, splice in a different press, re-run the fold, and a counterfactual world exists in 22 ms. Chaos mode proves the floor holds, injecting real sleeps, allocation churn and forced GC between frames without moving the trace by one bit.

Beyond 2D UI mount what the content needs

PocketJS is an application runtime with a game engine's architecture, so a game and an app are built the same way.

one guest, different cores Pocket TalkOpenStrikePocket Voxel
guest programyour JavaScript, one frame at a time
uitree, layout, draw, input, focus
netpoll batches audiopcm mixer strikebsp, bots, hits voxelchunks, meshing

Cores are independent native modules, loaded the way a kernel loads drivers. An app takes the ones its content needs and the rest never enters the build. Adding one widens what your program is allowed to ask for; it never changes how your program is written.

Compatibility run on the metal, not emulated

PocketJS has booted on every operating system below, on the real machine, and what changes between them is one native submission layer, never the application. Each entry links to the post or pull request that brought it up. Keeping the hardware bootable is its own work, so the Pocket Museum project repairs these machines and keeps them running.

Ecosystem shipped on the runtime

PocketJS carries complete applications on the machines this runtime was built for: music, video, maps, documents, shells, games, and developer tools. Each card links to the project or its setup guide.

11 cases
Pocket YouTube's 3DS interface replay: Big Buck Bunny above touch-screen search results
Nintendo 3DSPSPPS Vita

Pocket YouTubeclient

Watch on the upper screen while searching and browsing on the touch screen. A Mac companion streams video to the 3DS over Wi-Fi; PSP and PS Vita builds are available too.

Read the story →How to try Pocket YouTube
Pocket Voxel's town with voxel houses, trees and a player, captured on PSP
PSPPS Vita

Pocket Voxelworld

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, two ticks per presented frame.

Read the story →How to try Pocket Voxel
The VRM character widget beside Activity Monitor

Pocket Characterdesktop

A rigged VRM companion in a transparent always-on-top window, rendering skinned 3D at 60 fps in one process and 118 MB. The Electron build of the same idea takes 8 processes and 2184 MB.

Read the story →
DevTools panel highlighting the same node as the device screen

Pocket DevToolstooling

Time-travel debugging over a USB cable at 2 bytes per frame. The inspector highlight is emitted by the core into the DrawList, so it renders on the real device, on every backend.

Read the story →
OpenStrike's Dust2 courtyard and game HUD, captured on PSP
PSPPS Vita

OpenStrikefps

A Counter-Strike-shaped shooter on 2004 hardware: BSP maps, bots, and a HUD written in Solid JSX. 60 fps, with 2.2 ms of JavaScript per frame and a worst observed frame of 9.7 ms.

Read the story →How to try OpenStrike
who builds this

Pocket Lab is an independent, non-VC-backed organization built on this runtime, so that the joy of creating belongs to everyone.

Sponsors thank you

We can only develop PocketJS full-time thanks to your support.

Become a sponsor

Pocket Stack · Games

OpenStrike

A CS-like FPS. Classic maps, bots, and a JSX heads-up display on a handheld.

PSPPS Vita
Build from sourceHomebrew setup + game map assets

How to try it

  1. Choose the PSP or PS Vita build in the project guide.
  2. Prepare the map assets required by the game's cooker and build the app.
  3. Install the PSP EBOOT or PS Vita VPK using the platform instructions.
OpenStrike's Dust2 courtyard and game HUD, captured on PSP
PSP capture · Pocket Stack ↗

Pocket Stack · Productivity

Pocket Doc

Your Markdown library on two screens. Read above, edit and navigate below.

Nintendo 3DS
Build from source3DS Homebrew Launcher + paired Mac over Wi-Fi

How to try it

  1. Clone the project with its runtime and install the 3DS build prerequisites.
  2. Build the app, then deploy its .3dsx and pairing key with ftpd.
  3. Start the Mac companion and open Pocket Doc from Homebrew Launcher.
Pocket Doc's 3DS interface: a file list and rendered Markdown above two scrolling touchpads
WASM interface capture · Pocket Stack ↗

Pocket Stack · Games

Pocket Voxel

A Game Boy world rebuilt in voxels. Play in your browser or take it to a handheld.

PSPPS Vita
Web player + console exportBring your own supported US Pokémon Red ROM

How to try it

  1. Open the Web Player and select your own supported ROM.
  2. The browser processes the ROM locally; you can play in the page.
  3. Choose PSP ZIP or PS Vita VPK to generate a console package. Console installation requires homebrew support.
Pocket Voxel's town with voxel houses, trees and a player, captured on PSP
PSP capture · Pocket Stack ↗

Community / ObsoleteSony · Music

PSPMAN

A Walkman-inspired music player. Local FLAC, MP3, album art, and cassette mode.

PSP
Public alpha · source privateSupported PSP + custom firmware; PSP-1000 unsupported

How to try it

  1. Check the official compatibility list: PSP-2000, PSP-3000, PSP Street, or PSP Go with an M2 card. PSP Go internal storage is unsupported.
  2. Download the alpha and copy the complete PSPMAN folder to /PSP/GAME/ on the Memory Stick.
  3. Add your music to /MUSIC/ or /PSP/MUSIC/, then launch from Game → Memory Stick.
PSPMAN's Now Playing interface, from the official ObsoleteSony site
Official product image · ObsoleteSony ↗

Pocket Stack · Desktop

Pocket Shell

A tiling interface on the 3DS. Windows on top, a workspace and control deck below.

Nintendo 3DS
Build from source3DS Homebrew Launcher; Bun + Docker to build

How to try it

  1. Clone the project recursively and run its setup and 3DS build.
  2. Copy the generated .3dsx under /3DS/ on the SD card.
  3. Open it in Homebrew Launcher. The separate iPod touch companion is also documented in this repository.
Pocket Shell's tiled windows and lower-screen touch deck, captured on 3DS
3DS capture · Pocket Stack ↗

Pocket Stack · Design

Pocket Figma

Explore a Figma file with a thumbstick. Pan and zoom through a baked design canvas.

PSPPS Vita
Build from sourcePSP or PS Vita homebrew setup

How to try it

  1. Set up the project and its pinned PocketJS toolchain.
  2. Use the PSP or Vita build command in the README.
  3. Install the EBOOT or VPK. The device browses baked design tiles; this is a file viewer, not a live Figma editor.
Pocket Figma's design component canvas and zoom controls in a PSP-sized framebuffer
Emulator capture · Pocket Stack ↗

Pocket Stack · Video

Pocket YouTube

Watch above, search and browse below. A Mac companion streams video to the 3DS over Wi-Fi, with PSP and PS Vita builds too.

Nintendo 3DSPSPPS Vita
Build from sourceNew 3DS with homebrew and DSP firmware + Mac companion on the same LAN

How to try it

  1. Follow the 3DS guide to install the toolchain and prepare DSP firmware on a New 3DS.
  2. Build the app and deploy its .3dsx and pairing key with ftpd.
  3. Exit ftpd, open Pocket YouTube in Homebrew Launcher, and start the Mac companion with the console's IP address. The README also covers PSP over USB and PS Vita over Wi-Fi.
Pocket YouTube's 3DS interface replay: Big Buck Bunny above touch-screen search results
WASM UI replay · Pocket Stack · Big Buck Bunny © 2008 Blender Foundation, CC BY 3.0 ↗

Pocket Stack · Maps

Pocket Map

Browse OpenStreetMap or Hyrule on a handheld. Pan with the touchpad, zoom, search and save places through a paired Mac.

Nintendo 3DSPSP
Build from source3DS Homebrew Launcher + paired Mac on the same LAN

How to try it

  1. Clone the project with its runtime and install the 3DS build prerequisites.
  2. Follow the setup guide to prepare the map assets, build the app and deploy it through ftpd.
  3. Open Pocket Map in Homebrew Launcher and start the Mac companion with the console's IP address. The project also documents a PSP build over USB.
Pocket Map's native 3DS build in Azahar: Union Square above the map touchpad and zoom controls
Native 3DS capture in Azahar · Pocket Stack · © OpenStreetMap contributors ↗

Pocket Stack · Tools

Pocket Term

A terminal in your hands. Mac shell sessions above a touch keyboard on the 3DS.

Nintendo 3DS
Build from source3DS homebrew setup + Mac companion

How to try it

  1. Follow the project's requirements for the Mac companion and 3DS build.
  2. Start the companion that hosts the terminal sessions on your Mac.
  3. Connect the 3DS app to browse sessions and send input with its touch keyboard.
Pocket Term's native 3DS interface in Azahar: an 80-column terminal above session tabs, a touchpad and a keyboard
Native 3DS capture in Azahar · Pocket Stack ↗