npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

sylph-jsx

v0.7.3

Published

A lightweight, SolidJS-powered runtime for building declarative PixiJS experiences

Readme

Status

The framework is fully-functional, but APIs are subject to change.

Quick Start

Sylph offers a typescript template:

npx degit dpchamps/sylph.jsx/packages/sylph-template my-sylph-app
cd my-sylph-app
npm install
npm run dev

Overview

Sylph uses the SolidJS Universal Renderer to construct a PixiJS container hierarchy.

Additionally, it provides top-level mechanisms for writing declarative components with fine-grained reactivity. Effects are synchronized to the PixiJS ticker to ensure deterministic sequencing and updating within a given frame.

What this means is that you can write PixiJS applications that leverage SolidJS reactive primitives. An example would be:

import {
  createAsset,
  Application,
  createSignal,
  render,
  onEveryFrame,
} from "sylph-jsx";

const App = () => {
  const texture = createAsset("fire.png");
  const [angle, setAngle] = createSignal(0);

  onEveryFrame((time) => {
    setAngle((last) => last - 0.05 * time.deltaTime);
  });

  return (
    <Application width={800} height={600} backgroundColor={0x101820}>
      <sprite
        texture={texture()}
        pivot={{ x: 0.5, y: 0.5 }}
        x={400}
        y={300}
        rotation={angle()}
      />
    </Application>
  );
};

The above example creates an Application component, with a single sprite as a child.

It uses the onEveryFrame lifecycle hook to execute a side effect on every frame of the PixiJS ticker. The side effect in this case is updating the angle signal. Note that we have ticker primitives available: time.deltaTime is used to ensure smoothness across frames.

We can then consume this signal in the sprite component below.

The end result is that we have updates applied to containers that trigger re-renders only when the reactive primitives they're dependent on are triggered. Read more about fine-grained reactivity here: SolidJS Docs on fine-grained reactivity.

Performance

Sylph's fine-grained reactivity enables performance characteristics that are difficult to achieve with traditional component-based rendering:

  • Frame-synchronized effects: All reactive updates are scheduled to perform in a single tick.
    • The scheduler will flush as many effect-cascades as possible within a budgeted frame window
  • Granular updates: Only the specific properties that changed are updated, not entire component trees.
    • If a sprite's x position changes, only that property is set—nothing else re-renders.

Examples:

  • BasicReactivityLoadTest demonstrates 3,000+ individually tracked sprites, each with reactive position, scale, and rotation properties updating in real-time using coroutine-based easing animations—all while maintaining 60fps with minimal performance impact.

This makes Sylph particularly well-suited for interactive visualizations, 2D games, and real-time data displays where declarative code and smooth performance are both important.

Development Features

  • TypeScript support: All JSX elements have complete type definitions—your IDE will autocomplete PixiJS properties and catch errors before runtime.
  • PixiJS devtools: The devtools overlay boots automatically in development, giving you scene graph inspection and performance monitoring.
  • Full SolidJS compatibility: All SolidJS primitives and components work with PixiJS containers. Use <Show>, <For>, <Index>, createMemo, createResource, and any other SolidJS feature—they all just work.

Motivations

Sylph is intended to be a general-purpose framework for writing canvas/webgpu PixiJS applications. The personal goals that inspired this project were to have a general suite of tools that enabled performant push-based reactivity for game development.

For example:

const PLAYER_SPEED = 5;
const DESTINATION = { x: 500, y: 500 };

const ExampleSprite = (props: PixiNodeProps<{ x: number; y: number }>) => {
  const texture = createAsset<Texture>("fire.png");

  return (
    <sprite
      texture={texture()}
      scale={1}
      x={props.x}
      y={props.y}
      tint={"white"}
      pivot={{ x: 0.5, y: 0.5 }}
    />
  );
};

export const ControlsAndMovement = () => {
  const wasdController = createWASDController();
  const [playerPosition, setPlayerPosition] = createSignal({ x: 0, y: 0 });
  const winCondition = () =>
    euclideanDistance(playerPosition(), DESTINATION) < 20;

  createSynchronizedEffect(wasdController, ({ x, y }, time) => {
    setPlayerPosition((last) => ({
      x: last.x + x * PLAYER_SPEED * time.deltaTime,
      y: last.y + y * PLAYER_SPEED * time.deltaTime,
    }));
  });

  return (
    <Show when={!winCondition()} fallback={<text>You Won!</text>}>
      <ExampleSprite x={playerPosition().x} y={playerPosition().y} />
      <ExampleSprite x={DESTINATION.x} y={DESTINATION.y} />
    </Show>
  );
};

The above example demonstrates generally how we can compose effects and signals together to write a declarative scene where parts only update when the dependencies change:

  1. The first ExampleSprite updates only when player position changes
  2. The second ExampleSprite never updates
  3. The Show block only updates when the winCondition changes

Further, the example shows how effects, signals and components are composable:

  1. winCondition is a simple function, but it preserves reactivity from its inner computation
  2. ExampleSprite is a reusable component for a sprite with a given texture
  3. wasdController (not shown in example) is similar to winCondition: derived signals bound to input

This kind of development may not be desirable or appropriate for parts of the application. The component PixiExternalContainer is provided to allow you to eject from the reactive runtime at any point and perform your own logic directly in a PixiJS container. See #pixiexternalcontainer below for more info.

Development

npm install
npm run test # run test suite with watch, show coverage
npm run dev -w slyph-examples # run the sandbox app

Quick start

JSX primitives

<application>

Handles PixiJS initialization, waits to mount until .initialize() runs, and accepts standard ApplicationOptions plus loadingState, appInitialize, and createTicker hooks.

<application width={800} height={600} backgroundColor={0x101820}>
  {children}
</application>

<container>

Standard scene graph grouping element. Accepts other intrinsics (including <render-layer>) and supports PixiJS container options such as sortableChildren, eventMode, and position properties.

<container x={100} y={120} sortableChildren>
  <sprite texture={texture()} />
  <text>x: {position().x}</text>
</container>

<sprite>

Leaf element for textured display objects. Provide a PixiJS Texture, positional props, interactivity settings, and transforms.

<sprite
  texture={createAsset("fire.png")()}
  x={400}
  y={300}
  pivot={{ x: 0.5, y: 0.5 }}
  eventMode="static"
  onclick={() => setAngle((angle) => angle + 0.1)}
/>

<text>

Displays dynamic copy. String children automatically concatenate into the PixiJS Text value, and updates react when signals change.

<text style={{ fontSize: 24 }} fill={0xffffff} x={32} y={32}>
  Score: {score()}
</text>

<render-layer>

Wrap a subtree in a PixiJS RenderLayer. Useful for independent z-sorting, compositing, or post-processing passes without leaving JSX.

<render-layer zIndex={100}>
  <text x={16} y={16}>
    HUD Overlay
  </text>
  <container>
    <sprite texture={hudTexture()} />
  </container>
</render-layer>

Frame-aware query functions src/engine/core/query-fns.ts

createSynchronizedEffect(query, effect, owner?)

Run when reactive state changes and commit changes during the next frame

  • Tracks reactive dependencies in the query function.
  • Queues the effect for the next PixiJS ticker frame and runs it with the most recent Ticker.
  • Preserves the caller's SolidJS owner so cleanup and disposals behave as if the effect lived in the component.

onEveryFrame(effect)

Run on every frame

  • Schedules effect on every ticker frame without reactive tracking.
  • Ideal for fixed-step simulations, counters, and other continuous work.
  • Prefer createSynchronizedEffect when you only need updates in response to state changes; reserve onEveryFrame for unavoidable per-frame work.

Core Framework Components

Application

The <Application> component constructs the intrinsic <application> tag, and adds all necessary lifecycle management and core providers required for framework functionality.

There's usually no good reason to not use this component.

  • Awaits appInitialize and shows loadingState (defaults to <text>Loading...</text>) while asynchronous setup completes.
  • Starts the ticker only after the PixiJS application is ready and exposes the instance through useApplicationState().
  • Seeds the internal game-loop context so that createSynchronizedEffect and onEveryFrame can schedule work onto the game loop.
  • Boots the PixiJS devtools overlay via initDevtools when available.

Use the intrinsic form (<application>) when authoring low-level JSX trees and the component form when you want the full runtime integration.

PixiExternalContainer

You may want to manage some or most of your logic outside the reactive render tree. For these cases, you may reach for PixiExternalContainer :

const external = new Container();

<PixiExternalContainer container={external} x={120} y={180}>
  <text>Overlay UI</text>
</PixiExternalContainer>;
  • Adds reactivity around the managed container

Working with render layers

<render-layer> mounts a PixiJS RenderLayer alongside your display objects and automatically registers every descendant with that layer. The layer itself is inserted into the parent container, while the children remain regular siblings in the display list; they are simply marked as renderLayerChildren so PixiJS sorts and composites them through the layer. This means you can freely mix layered and non-layered content inside the same container without losing control over draw order.

  • Any prop you pass to <render-layer> (such as sortableChildren or zIndex) updates the underlying layer immediately, and reactive props will resync as signals change.
  • Descendants inherit the layer recursively: nested containers, sprites, text nodes, and additional render-layer blocks all attach correctly, so complex hierarchies continue to render through the intended layer.
  • Conditional rendering, For/Index loops, and other SolidJS control flow work the same way—adding or removing nodes updates both the container's display list and the layer's child registry.
  • Removing the layer detaches the RenderLayer itself and clears the associated renderLayerChildren, leaving the rest of the scene untouched.
<container>
  <render-layer zIndex={100} sortableChildren>
    <text x={16} y={16}>
      HUD Overlay
    </text>
    <container>
      <sprite texture={hudTexture()} />
    </container>
    <For each={alerts()}>
      {(alert) => <text y={alert().y}>{alert().label}</text>}
    </For>
  </render-layer>

  <sprite texture={playerTexture()} x={player().x} y={player().y} />
</container>

The overlay subtree above always renders through the same layer, even as the alerts() array grows or shrinks, while the player sprite continues to follow normal container ordering.

Why the word "Sylph"?

Sprites, Pixi.js's... A reactive framework that's lighter than air.