SolidJS custom renderer for Phaser 3. Declarative 2D game development with progressive disclosure.
Solidion bridges SolidJS's fine-grained reactivity with Phaser 3's game engine. Write game objects declaratively in JSX, bind properties to Signals, and let the framework handle the rest.
function Pet() {
const [level, setLevel] = createSignal(1);
const scale = () => 1 + level() * 0.2;
return (
<container x={400} y={300}>
<sprite texture={`/assets/pet-${level()}.png`} scale={scale()} onClick={() => setLevel(l => l + 1)} />
<text text={`Lv.${level()}`} y={50} fontSize={16} color="#ffffff" />
</container>
);
}No preload. No create. No update. No scene.add. No setInteractive. No destroy.
- Zero cognitive overhead — Phaser's preload/create/update lifecycle, manual object management, and state-to-display synchronization are handled automatically
- Progressive disclosure — Start simple (L0), add control only where needed. Each component independently chooses its abstraction level
- No virtual nodes — Follows SolidJS's philosophy. Phaser GameObjects are created and updated directly, with only lightweight metadata attached
- Declarative behaviors — Not just "what exists" but "how it moves" is declarative. Springs, tweens, state machines, oscillations — all as Signals or JSX children
| Level | What | How |
|---|---|---|
| L0 | Existence | JSX elements, props binding |
| L1a | Discrete behavior | useTween, useStateMachine, useSequence |
| L1b | Continuous behavior | useSpring, useFollow, useOscillation, useVelocity |
| L1c | Behavior composition | <SpringBehavior>, <OscillateBehavior> as JSX children |
| L2 | Resource management | <Preload>, <Scene> |
| L3 | Frame control | useFrame |
| L4 | Raw Phaser access | useScene, ref |
Most games stay in L0–L1. useFrame (L3) is the escape hatch, not the default.
Solidion includes RECS (solidion/recs), a Reactive Entity Component System for managing many entities of the same type (10+ fish, 30+ bullets).
Traditional ECS runs every system over every entity every frame — O(N) per system regardless of whether anything changed. RECS is different:
- Reactive data → display:
createStorechanges propagate automatically to Phaser GameObjects via SolidJS's fine-grained reactivity. No manual sync loop. - Reactive index:
createIndextracks which entities match a condition (e.g. "hungry fish") with O(1) updates per state change, so systems iterate only relevant entities. - Phased execution: Systems declare when they run within a frame —
"pre"(react to previous frame's state changes),"main"(physics/timers), or"post"(react to physics results) — separating discrete logic from continuous integration. - Shared step functions: The same pure algorithms (
springStep,fsmStep, etc.) power both RECS systems and L1 hooks, extracted as stateless functions for bulk processing.
RECS and hooks coexist: use hooks for few unique entities (player, UI), RECS for many uniform entities (enemies, particles).
npm install solidion solid-js phasersolidion/
src/
core/ # Renderer internals: meta, props, events, texture, scene-stack, frame, sync
hooks/ # L1a/L1b hooks: useTween, useSpring, useStateMachine, etc.
behaviors/ # L1c composition components: SpringBehavior, OscillateBehavior, etc.
components/ # Game, Scene, Preload, Overlay, GameLoop, Show, For
recs/ # RECS (Reactive ECS): pure step functions + phased Systems + reactive index
debug/ # Dev-only utilities: inspectBindings, profiling, expose
renderer.ts # solid-js/universal createRenderer implementation
contexts.ts # Solid contexts (Game, Scene, FrameManager, ParentNode)
types.ts # JSX IntrinsicElements type definitions
index.ts # Public API (L0–L1c)
tests/ # 443 tests across 15 suites
docs/ # Design specification
examples/ # Runnable demos (breakout, null-pow, floppy-heads, nadion-defense, aquarium)
npm install
npm test452 tests across 15 suites:
| Suite | Tests | Scope |
|---|---|---|
| renderer | 110 | Renderer logic (mock Phaser) |
| hooks | 107 | State machine, spring, tween, sequence, etc. |
| props | 48 | Property application & composition |
| recs | 44 | Pure step functions, phased Systems & reactive index |
| texture | 29 | Texture auto-loading |
| components | 18 | Sync, reapplyProp, preload |
| visibility | 16 | Recursive visibility toggling |
| debug | 14 | Debug utilities & profiling |
| integration | 13 | Real Solid reactivity + renderer |
| contexts | 12 | Context providers & accessors |
| meta | 11 | Metadata & delta system |
| events | 9 | Event name resolution |
| store-compat | 8 | SolidJS store compatibility |
| scene-stack | 5 | Scene stack management |
| frame | 8 | Frame callback lifecycle & phase ordering |
Solidion follows SolidJS's convention of splitting entry points by paradigm boundary.
// solidion — Daily API (L0–L1c). Most games only need this.
import { Game, Scene, Preload, Overlay, GameLoop } from "solidion";
import { Show, For, Index } from "solidion";
import { useGame, useScene, useParentNode } from "solidion";
import { useTween, useStateMachine, useSequence, useOverlap } from "solidion";
import { useSpring, useFollow, useOscillation, useVelocity } from "solidion";
import { SpringBehavior, OscillateBehavior, FollowBehavior, VelocityBehavior } from "solidion";
// solidion/recs — RECS: Reactive ECS (createStore + pure step functions + phased Systems + reactive index)
import { System, createSystemFactory, forActive, createIndex } from "solidion/recs";
import { springStep, velocityStep, followStep, fsmStep, fsmSend, tweenStep, tweenLerp } from "solidion/recs";
// solidion/core — Frame-aware escape hatch (L3–L4)
import { useFrame, useTime, render, effect, memo } from "solidion/core";
import { addDelta, removeDelta, getMeta, composeProp } from "solidion/core";
import { pushScene, popScene, getCurrentScene } from "solidion/core";
// solidion/debug — Dev-only utilities
import * as debug from "solidion/debug";See docs/solidion-spec.md for the full design specification.
See examples/ for runnable demos.
- Breakout — Block breaker game demonstrating reactive signals, frame-loop physics, batch updates, and conditional state
- Null Pow! — Pac-Man-style maze game with ghost AI, useOverlap collision, and useScene keyboard input
- Floppy Heads — Flappy Bird-style game with procedural pipe generation and score tracking
- Nadion Defense — Space Invaders-style tower defense with 40 reactive enemies and projectile pools
- Aquarium — Hybrid RECS + hooks demo with fish, food, bubbles, jellyfish, and seaweed
The name is a portmanteau of Solid (SolidJS) and nadion — a fictional subatomic particle from the Star Trek universe.
In Star Trek lore, a phaser fires nadion particles through a superconducting crystal to produce a directed energy beam. Solidion mirrors this metaphor: SolidJS's Signal system acts as the crystal, channeling fine-grained reactivity to drive Phaser GameObjects.
The -ion suffix also follows the naming convention of particles (photon, electron, nadion), giving the name a natural fit in Phaser's sci-fi lineage — which itself originated as a blend of "photon" and "maser" at Photon Storm, the studio behind the engine.
v0.1 — Architecture validated. Core renderer, hooks, behavior system, and frame synchronization are implemented and tested. Components (<Game>, <Scene>, <Preload>, <Overlay>) are implemented but require browser integration testing.
MIT