Create on
every screen
you love

Build apps and games for your favorite devices, from a PSP to your desktop. Familiar JavaScript components, brought to life by a compact native runtime.

Nintendo 3DS

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

PS Vita

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

Familiar tools

Start with the components you know. Write in Solid, Vue Vapor or Octane. All three run on QuickJS and render to the same native tree, with layout and drawing handled by Rust.

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
Smooth motion

Give your interface motion that feels at home on a handheld. Keyframe timelines and spring curves compile at build time and run on the Rust core's clock, with no per-frame JavaScript needed. Try the yui540 motion studies here, running in WebAssembly inside an interactive PSP model.

Loading the motion studies
Press left or right on the d-pad, or the L and R shoulders, to switch studies.
Small footprint

A small screen can hold a complete app. Your JavaScript runs on QuickJS, while a Rust core handles layout and drawing without a browser engine. Here is what that costs on a 333 MHz PSP and a Mac.

room for an app on a PSP
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

The PSP app shown here runs in 8 MB on a single 333 MHz core: a quarter of the handheld's RAM and 1/1536 of the iPhone's memory above. The phone's performance core is clocked about 13 times higher.

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.

Compiled reactivity vs. virtual DOM · hero demo (ms)
solid15.15
vue vapor16.74
vue, vdom90.75

In this comparison, classic Vue's virtual DOM takes about six times the frame time of Solid for the same screen. Solid stays within the 16.67 ms frame budget; Vue Vapor is close to it.

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

Seven samples per framework, with a 16.67 ms frame budget. This is a later run with an updated toolchain; compare frameworks within each chart.

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

The PocketJS, Tauri and Electron builds occupy 10, 9 and 242 MB on disk. The PocketJS build uses one process and, with an idle document open, redraws about twice a second for the blinking caret. The report covers the tradeoffs as well as the gains.

Replay every frame

Reproduce a bug, replay an interaction, or try a different input. PocketJS advances your app one frame(buttons) call at a time, with timing measured in frames. Tests can replay the same sequence as fast as the CPU allows, without waiting for real time to pass.

when an async result reaches your app
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 during frame +3 is queued and delivered at the start of frame +4, in FIFO order, before app hooks run. Callbacks enter between frames, and after() schedules work against a frame deadline.

staten+1 = F(staten, inputn)
pixelsn = G(staten)
each frame advances state and draws the result
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 in all 60 runs

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. The frame clock makes this timing assertion reproducible.

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.

Change a button press at frame 9 and replay the recording to explore a different outcome. This example takes 22 ms. In chaos mode, injected delays, allocation churn and forced garbage collection between frames leave the recorded trace unchanged.

Apps and games

Make a chat app, a desktop companion, or a world to explore. Apps and games share the same runtime, with native modules for the features each project needs.

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

Choose the native modules your project needs, such as networking, audio, or 3D. Unused modules stay out of the build. Your JavaScript uses them alongside the same UI and input APIs.

Pocket Talk, a message thread with the system keyboard OpenStrike, a muzzle flash over a crate with a Solid JSX HUD Pocket Voxel, a route of extruded voxel grass and carved trees
Choose your screen

Bring a new project to a familiar device. These platforms use a shared runtime with a native host for each target. Follow the links for the porting stories and implementation details. Pocket Museum repairs and maintains the older machines used to develop and test PocketJS.

Made with PocketJS

A music player for a PSP. A workspace on a 3DS. A game you can take with you. Explore what people are making, find something to try, or get an idea for your own project.

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

Community voices

What people are saying about PocketJS and the things being made with it.

PabloW@pablowasserman

I love this. Old hardware, new software.

Original

Me encanta esto. Hardware viejo, software nuevo.

Wang Jack@WangJack845358

This project really is a miracle!!

Original

这个项目的确是个奇迹!!

Buto@ChibiButo

Impressive!

J.R. DeJesus@Jok3r0314

Awesome!!!!

TJ (thaddeus jiang)@ThaddeusJiang

I've been following it for a while, and I'm sure PocketJS is the JS project I've been most excited about since Bun.

Original

看了有一段时间了,我确定 PocketJS 是近几年 bun 之后我最期待的 JS 项目。

Eric Xu (e/Mettā)@xleaps

Yes, I could see the potential from the first glance. The combination of technologies makes a lot of sense, too.

Original

Yes, 从第一眼看到就感受到这是个很有潜力的项目,技术栈的组合也非常合理。

Sponsors

We can build PocketJS full-time thanks to your support. Help bring more ideas to more screens.

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 ↗