PocketJS runs your JSX app through one of two framework adapters — Solid or Vue Vapor — each of which compiles your component code and drives the same retained-mode Rust UI tree. Choosing a framework changes only the JavaScript reactivity layer and the JSX transform; the Rust core, the Tailwind-subset compiler, font atlases, input model, animation API, PSP native build, andDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/pocket-stack/pocketjs/llms.txt
Use this file to discover all available pages before exploring further.
.pak asset container are all completely shared. The compiled output (a .js bundle and a .pak file) is byte-for-byte the same shape regardless of which adapter you picked.
The adapter model
Both frameworks target the same HostOps surface — theui.* op set (createNode, setStyle, insertBefore, setText, and so on) that the Rust core exposes to JavaScript. On the PSP that surface is QuickJS FFI bindings; in the browser and headless Bun it is the WebAssembly wrapper. A framework adapter is the thin renderer layer that translates JSX tree mutations into those ops. Solid does it through babel-preset-solid’s universal renderer output; Vue Vapor does it through vue-jsx-vapor plus a small DOM facade the Vapor runtime expects.
The JS mirror tree, focus manager, animation scheduler, overlay root, and every component in @pocketjs/framework/components are shared across both adapters. Switching frameworks costs nothing at the Rust boundary.
The built artifact —
dist/<app>.js plus dist/<app>.pak — is structurally identical regardless of which framework compiled it. The .pak always contains the same ui:styles, ui:font.*, and ui:img.* entries baked from the same class/charset scan. Only the JS layer differs.Solid (default)
Solid is the default adapter. Apps import reactive primitives directly fromsolid-js and mount with @pocketjs/framework.
- JSX transform:
babel-preset-solid{ generate: 'universal', moduleName: <absolute path to src/renderer-solid.ts> }— compiled into universal renderer calls that create native nodes instead of DOM elements. - Types:
@babel/preset-typescriptstrips types in the same Babel pass. - Main entry:
@pocketjs/framework(maps tosrc/index.ts) - Components:
@pocketjs/framework/components(maps tosrc/components.ts) - Lifecycle:
onMount,onCleanup,createEffect— imported directly fromsolid-js. - Control flow:
Show,For,Index,Switch,Match— imported fromsolid-jsas usual. - Output suffix: none — produces
dist/<app>.jsanddist/<app>.pak.
Vue Vapor
The Vue Vapor adapter usesvue-jsx-vapor to compile JSX into Vapor renderer calls. Components return render functions (() => JSX.Element) rather than JSX directly. Reactive state is Vue ref/reactive; lifecycle hooks come from vue.
- JSX transform:
vue-jsx-vapor— compiles JSX with the Vue Vapor codegen, bundled againstsrc/renderer-vue-vapor.tsplus the Vapor runtime facade. - Main entry:
@pocketjs/framework/vue-vapor(maps tosrc/index-vue-vapor.ts) - Components:
@pocketjs/framework/vue-vapor/components(maps tosrc/components-vue-vapor.ts) - Lifecycle:
onMounted,onScopeDispose,watchEffect— imported fromvue. - Control flow: Ternaries and
.map()instead of<Show>/<For>(standard Vue Vapor pattern). - Output suffix:
.vue-vapor— producesdist/<app>.vue-vapor.jsanddist/<app>.vue-vapor.pak.
Vue Vapor components must return a render function (
() => JSX.Element), not JSX directly. The Vapor runtime calls the render function on each reactive update. If you return JSX from the component body itself, reactivity will not track correctly.Framework selection
There are three ways to pick a framework, in order of precedence:1. pocket.config.ts (project default)
Create pocket.config.ts at the repo root to set the default for all build commands:
2. --framework CLI flag (per-invocation override)
Pass --framework=solid or --framework=vue-vapor to any build script to override the config file for a single command. The flag works across all three entry points:
3. CLI flag wins
When bothpocket.config.ts and --framework are present, --framework always wins. Use --no-config to ignore the config file entirely (defaults to Solid unless --framework is also set). Use --config=<path> to load a different config file.
Import path reference
Both frameworks export the same logical surface through different subpaths. The generic paths (withoutsolid/ or vue-vapor/ prefix) resolve to Solid in a Solid build and to Vue Vapor in a Vue Vapor build — the compiler rewrites them at bundle time.
| Purpose | Solid | Vue Vapor |
|---|---|---|
| Mount | @pocketjs/framework | @pocketjs/framework/vue-vapor |
| Components | @pocketjs/framework/components | @pocketjs/framework/vue-vapor/components |
| Lifecycle | @pocketjs/framework/lifecycle | @pocketjs/framework/vue-vapor/lifecycle |
| Animation | @pocketjs/framework/animation | @pocketjs/framework/vue-vapor/animation |
| Input | @pocketjs/framework/input | @pocketjs/framework/vue-vapor/input |
createSignal, ref) native to whichever framework they chose.
Side-by-side counter example
The same counter component written in both frameworks:- Solid
- Vue Vapor
QuickJS constraints on PSP
The PSP runs your bundle through QuickJS (Bellard 2025, ~ES2023). QuickJS supports Proxy, WeakMap, WeakRef, FinalizationRegistry, and logical assignment, but it is missingsetTimeout, MessageChannel, and performance. A polyfill provides queueMicrotask via Promise.resolve().then() for the basic microtask case.
As a result, certain Solid APIs that rely on the scheduler are off-limits on PSP and produce a hard compile error if imported:
| Banned import | Reason |
|---|---|
createResource | Requires async scheduling (no setTimeout) |
useTransition | Requires concurrent scheduling |
startTransition | Same |
createEffect for reactive data fetching, and animate() from @pocketjs/framework/animation for motion. These patterns work identically across PSP, browser, and headless Bun.
What stays the same across both frameworks
Switching from Solid to Vue Vapor (or back) does not change:- The Rust
no_stdcore, taffy layout engine, and sceGu backend - The Tailwind-subset compiler and generated
styles.generated.ts - Font atlas baking and the baked charset
- The
.pakcontainer format and itsui:styles,ui:font.*,ui:img.*entries - Host detection and the HostOps interface
- The input and focus manager
- The overlay root and portal mechanism
- The animation API (
animate(),spring()) - The PSP EBOOT build pipeline
- Every component in
@pocketjs/framework/components - The browser dev host and PPSSPP capture path