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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@pithyjs/core

v0.1.0-beta.0

Published

**Main runtime package** for PithyJS - a next-generation web framework centered on fine-grained reactivity and direct DOM updates.

Downloads

39

Readme

@pithyjs/core

Main runtime package for PithyJS - a next-generation web framework centered on fine-grained reactivity and direct DOM updates.

Overview

@pithyjs/core combines reactive signals, template compilation, and directive binding into a unified runtime. It provides:

  • Fine-grained reactivity via @pithyjs/signals
  • Template compilation with HTML parsing and directive binding
  • Structural directives (@if, @for, @switch)
  • Event handling with automatic cleanup
  • Component system with scoped styles
  • Zero virtual DOM - only signal-bound nodes update

Key Principle: Components never re-render. When a signal changes, only the specific DOM nodes bound to that signal update (O(1) operation).

Installation

npm install @pithyjs/core
pnpm add @pithyjs/core
yarn add @pithyjs/core

Quick Start

import { html, signal } from '@pithyjs/core';

const count = signal(0);
const increment = () => count.set(count() + 1);

const app = html`
  <div>
    <h1>Counter: {{ count() }}</h1>
    <button @click="increment()">Increment</button>
  </div>
`;

document.body.appendChild(app);

Core Features

Reactive Signals

import { signal, computed, effect } from '@pithyjs/core';

const name = signal('Alice');
const greeting = computed(() => `Hello, ${name()}!`);

effect(() => {
  console.log(greeting()); // Logs: "Hello, Alice!"
});

name.set('Bob'); // Logs: "Hello, Bob!"

Template Interpolations

const user = signal({ name: 'Alice', age: 30 });

html`
  <div>
    <h1>{{ user().name }}</h1>
    <p>Age: {{ user().age }}</p>
  </div>
`;

Event Handlers

const handleClick = () => console.log('Clicked!');
const handleInput = (e) => console.log(e.target.value);

html`
  <button @click="handleClick()">Click me</button>
  <input @input="handleInput($event)" />
`;

Conditionals

const isLoggedIn = signal(false);

html`
  <div @if="isLoggedIn()">
    <h1>Welcome back!</h1>
  </div>
  <div @else>
    <h1>Please log in</h1>
  </div>
`;

For Loops

const todos = signal([
  { id: 1, text: 'Buy milk', done: false },
  { id: 2, text: 'Walk dog', done: true }
]);

html`
  <ul>
    <li @for="(todo, i) of todos()" track="todo.id">
      {{ i() + 1 }}. {{ todo().text }}
      <span @if="todo().done">✓</span>
    </li>
  </ul>
`;

Complete documentation: src/compile/forLoops/README.md

Switch/Case

const status = signal('loading');

html`
  <div @switch="status()">
    <div @case="'loading'">Loading...</div>
    <div @case="'success'">Success!</div>
    <div @case="'error'">Error occurred</div>
    <div @default>Unknown status</div>
  </div>
`;

Lazy Loading

Load components on-demand to improve initial page load performance.

import { lazy, preloadLazy } from '@pithyjs/core';

// Create a lazy-loaded component
const HeavyChart = lazy(() => import('./HeavyChart.pithy'));

// Use in template with @defer directive
html`
  <div @defer="visible" @load="HeavyChart">
    <div slot="placeholder">Loading chart...</div>
    <div slot="error">Failed to load</div>
  </div>
`;

// Preload on hover for faster perceived load
button.addEventListener('mouseenter', () => preloadLazy(HeavyChart));

Trigger modes:

  • @defer="visible" - Load when element scrolls into viewport
  • @defer="when" @when="condition()" - Load when condition becomes true

Complete documentation: src/runtime/README.md

Class Directives

const isActive = signal(true);
const isDisabled = signal(false);

html`
  <button
    [class.active]="isActive()"
    [class.disabled]="isDisabled()"
  >
    Toggle
  </button>
`;

Style Directives

const color = signal('red');
const size = signal(16);

html`
  <div
    [style.color]="color()"
    [style.font-size]="size() + 'px'"
  >
    Styled text
  </div>
`;

Architecture

@pithyjs/core
├── runtime/
│   ├── html.ts              # html`` template tag
│   ├── applyBindings.ts     # AOT binding applicator (Phase 5 stub)
│   ├── lazy.ts              # Lazy loading (lazy(), preloadLazy())
│   ├── module-cache.ts      # Module caching for lazy components
│   └── README.md            # Runtime documentation
├── compile/
│   ├── compileTemplate.ts   # Main compilation entry
│   ├── parseHtml.ts         # HTML parser
│   ├── forLoops/            # For loop system
│   │   └── README.md        # Complete @for documentation
│   └── binds/               # Directive implementations
│       ├── bindTextInterpolations.ts
│       ├── bindEventHandlers.ts
│       ├── bindClassDirectives.ts
│       └── bindConditionals.ts
└── index.ts                 # Public API

Template Compilation System

The compilation system transforms templates into reactive DOM:

  1. Parse HTML into AST
  2. Bind structural directives (creates contexts):
    • @if/@else-if/@else chains
    • @switch/@case statements
    • @for loops
  3. Bind non-structural directives (uses contexts):
    • Text interpolations {{ }}
    • Event listeners @event
    • Class/style directives

Complete documentation: src/compile/README.md

For Loops System

The @for directive provides keyed, reactive list rendering with:

  • O(1) signal updates when items change
  • Minimal DOM moves using LIS algorithm for reordering
  • Range-based architecture with comment anchors
  • Deep nesting support (loops in loops, loops in conditionals)
  • Automatic cleanup on unmount

Complete documentation: src/compile/forLoops/README.md

Performance Characteristics

| Operation | Complexity | Notes | |-----------|------------|-------| | Signal read | O(1) | Direct value access | | Signal write | O(1) | Updates only subscribers | | Text interpolation update | O(1) | Only bound text node updates | | Event binding | O(1) | Direct listener attachment | | Loop item add | O(1) | Keyed insertion | | Loop item remove | O(1) | Range detachment | | Loop item update | O(1) | Signal update, no re-render | | Loop reorder | O(n log n) | LIS algorithm | | Conditional toggle | O(m) | m = branch nodes |

Target: Sub-1KB core runtime (min+gzip)

API Reference

Runtime APIs

Note: This table is auto-generated from codex annotations. APIs marked internal are not exported from the @pithyjs/core public barrel — they are used internally or via direct subpath imports (e.g., @pithyjs/core/runtime/applyBindings).

| API | Component | Signature | Stability | Description | | --- | --- | --- | --- | --- | | compileNodes | compile-nodes | (nodes: PithyNode[], context: Record<string, unknown>) => { el: DocumentFragment; destroy: () => void } | stable | - | | compileTemplate | renderer | (html: string, context: Record<string, any>, css?: string, scopeId?: string, importedIdentifiers?: Set<string>) => { el: HTMLElement | DocumentFragment; destroy: () => void } | stable | - | | bindTemplateDirectives | renderer | (fragment: DocumentFragment, context: Record<string, any>, disposers: (() => void)[]) => void | stable | - | | isSSRMode | ssr-mode | () => boolean | stable | - | | toTextValue | dom-safety | (input: unknown) => string | internal | - | | __applyBindings | apply-bindings | (fragment: DocumentFragment, bindings: Desc[], context: Record<string, unknown>) => { el: DocumentFragment; destroy: () => void } | experimental | - | | bootstrap | bootstrap | (options: BootstrapOptions) => Promise<void> | stable | - | | createContextProxy | context | (contexts: Record<string, unknown>[]) => Record<string, unknown> | stable | - | | loadWithTimeout | defer-helpers | <T>(handle: LazyHandle<T>, timeoutMs: number) => Promise<T> | internal | - | | scheduleByPriority | defer-helpers | (callback: () => void, priority: LoadingPriority | undefined) => number | void | internal | - | | html | html | (template: TemplateStringsArray | string, contextOrCss?: Record<string, unknown> | string, cssIfContextPassed?: string) => { el: HTMLElement | DocumentFragment; destroy: () => void } | stable | - | | onDestroy | onDestroy | (fn: () => void) => void | stable | - | | bindBoundAttributes | attributes | (root: Element | DocumentFragment, context: Record<string, unknown>, disposers: (() => void)[]) => void | stable | - | | bindClassDirectives | classes | (root: Element | DocumentFragment, ctx: Record<string, unknown>, disposers: (() => void)[]) => void | stable | - | | bindComponentTags | components | (root: Element | DocumentFragment, rootCtx: Record<string, unknown>, disposers: (() => void)[]) => void | stable | - | | bindDefaultTwoWay | two-way-binding | (fragment: Element | DocumentFragment, context: Record<string, unknown>, disposers: (() => void)[]) => void | stable | - | | bindEventHandlers | events | (root: Element | DocumentFragment, rootCtx: Record<string, unknown>, disposers: (() => void)[]) => void | stable | - | | bindTextInterpolations | interpolations | (root: Element | DocumentFragment, ctx: Record<string, unknown>, disposers: (() => void)[]) => void | stable | - | | buildPidMap | binding-helpers | (fragment: DocumentFragment) => Map<string, Element> | internal | - | | buildCommentMap | binding-helpers | (fragment: DocumentFragment) => Map<string, Comment> | internal | - | | nearestCtx | binding-helpers | (el: Node, rootCtx: BindingContext) => BindingContext | internal | - | | mergeContexts | binding-helpers | (context: BindingContext) => BindingContext | internal | - | | compileWithEvent | binding-helpers | (expr: string, ctx: BindingContext) => (ev: Event, el: Element) => unknown | internal | - |

Directive APIs

| API | Component | Signature | Stability | Description | | --- | --- | --- | --- | --- | | bindSwitchCases | switch | (nodes: PithyNode[], rootEl: HTMLElement | DocumentFragment, ctx: Record<string, unknown>, processedNodeIds?: Set<string>, disposers?: (() => void)[]) => void | stable | - | | handleConditionalChain | conditional-chain | (domChain: HTMLElement[], astChain: PithyNode[], ctx: Record<string, unknown>, processedNodeIds?: Set<string>, disposers?: (() => void)[]) => Set<string> | stable | - | | bindConditionals | if | (nodes: PithyNode[], rootEl: HTMLElement | DocumentFragment, ctx: Record<string, unknown>, processedNodeIds?: Set<string>, disposers?: (() => void)[]) => Set<string> | stable | - | | bindForLoops | for | (nodes: PithyNode[], rootEl: HTMLElement | DocumentFragment, ctx: Record<string, unknown>, disposers?: (() => void)[], processedNodeIds?: Set<string>) => void | stable | - | | createLoopManager | loop-manager | <T>(options: LoopManagerOptions<T>) => LoopManager<T> | stable | - | | safeInsertBefore | loop-helpers | (parent: Node, newNode: Node, referenceNode: Node | null) => void | stable | Safely inserts a node before a reference node, with fallback to appendChild | | markElementsAsLoopGenerated | loop-helpers | (fragment: DocumentFragment, node: { nodeId?: string }) => void | stable | Marks all elements in a fragment as loop-generated for safe cleanup Optimized to use targeted selectors when possible | | createItemRange | loop-helpers | (key: string) => { start: Comment; end: Comment } | stable | - | | wrapInRange | loop-helpers | (start: Comment, content: DocumentFragment | HTMLElement, end: Comment) => DocumentFragment | stable | - | | detachRange | loop-helpers | (start: Comment, end: Comment) => void | stable | - | | createLoopSignals | loop-signals | <T>(item: T, idx: number, total: number) => LoopSignals | stable | Creates all the reactive signals needed for a loop item | | updateLoopSignals | loop-signals | (signals: LoopSignals, idx: number, total: number) => void | stable | Updates all positional signals for a loop item (idx, first, last, even, odd, count) Uses batch() for optimal performance | | createLoopContext | loop-signals | (parentCtx: Record<string, unknown>, signals: LoopSignals, itemVar: string, indexVar?: string, countVar?: string, firstVar?: string, lastVar?: string, evenVar?: string, oddVar?: string) => Record<string, unknown> | stable | Creates the context object for a loop item with all necessary signals. Helper signal names (count, first, last, even, odd) can be aliased to avoid shadowing in nested loops. | | findAllNestedLoopNodes | loop-traversal | (node: PithyNode) => PithyNode[] | stable | Recursively finds all nested loop nodes within a given node Used to process nested loops before binding to preserve data-pid references |

Lazy Loading APIs

| API | Signature | Stability | Description | | --- | --- | --- | --- | | LazyState | type LazyState = 'idle' | 'loading' | 'loaded' | 'error' | stable | Possible states for a lazy component | | LazyOptions | interface LazyOptions | stable | Options for configuring lazy loading behavior | | LazyHandle | interface LazyHandle<T> | stable | Handle returned by lazy() for controlling lazy component loading | | lazy | <T>(loader: () => Promise<T>, options?: LazyOptions) => LazyHandle<T> | stable | - | | isLazyHandle | (value: unknown) => value is LazyHandle<unknown> | stable | - | | preloadLazy | <T>(handle: LazyHandle<T>) => Promise<T> | stable | - |

Accessibility APIs

| API | Component | Signature | Stability | Description | | --- | --- | --- | --- | --- | | FocusTrap | focus-trap | class FocusTrap | stable | Focus Trap Instance Manages focus containment within an element for accessible modals/dialogs | | activate | focus-trap | activate(): void | stable | - | | deactivate | focus-trap | deactivate(): void | stable | - | | updateFocusableElements | focus-trap | updateFocusableElements(): void | stable | - | | isActivated | focus-trap | isActivated(): boolean | stable | - | | destroy | focus-trap | destroy(): void | stable | - | | createFocusTrap | focus-trap | (element: HTMLElement, options?: FocusTrapOptions) => FocusTrap | stable | Create a focus trap for an element Convenience function for creating and activating a focus trap | | isKey | keyboard-utils | (event: KeyboardEvent, key: string) => boolean | stable | Check if a keyboard event matches a specific key | | isActivationKey | keyboard-utils | (event: KeyboardEvent) => boolean | stable | Check if keyboard event is an activation key (Enter or Space) Used for custom interactive elements (buttons, links, etc.) | | onEscape | keyboard-utils | (handler: KeyboardHandler, options?: KeyboardHandlerOptions) => () => void | stable | Handle Escape key press Commonly used to close modals, dropdowns, tooltips, etc. | | onEnter | keyboard-utils | (element: HTMLElement, handler: KeyboardHandler, options?: KeyboardHandlerOptions) => () => void | stable | Handle Enter key press | | onSpace | keyboard-utils | (element: HTMLElement, handler: KeyboardHandler, options?: KeyboardHandlerOptions) => () => void | stable | Handle Space key press Note: Space typically triggers on keydown for consistency with Enter | | onActivation | keyboard-utils | (element: HTMLElement, handler: KeyboardHandler, options?: KeyboardHandlerOptions) => () => void | stable | Handle activation keys (Enter or Space) Used for custom buttons, links, and other interactive elements | | onArrowKeys | keyboard-utils | (element: HTMLElement, handlers: ArrowKeyHandlers, options?: KeyboardHandlerOptions) => () => void | stable | Handle arrow key navigation | | hasModifierKeys | keyboard-utils | (event: KeyboardEvent) => boolean | stable | Check if modifier keys are pressed Modifier keys: Ctrl, Alt, Shift, Meta (Command/Windows key) | | onKeyboardShortcut | keyboard-utils | (shortcut: string, handler: KeyboardHandler, options?: KeyboardHandlerOptions) => () => void | stable | Create a keyboard shortcut handler Supports combinations like "Ctrl+S", "Cmd+K", "Escape", etc. | | makeKeyboardAccessible | keyboard-utils | (element: HTMLElement, handler: KeyboardHandler) => () => void | stable | Make an element keyboard-accessible by adding Enter/Space key handlers Useful for making divs/spans behave like buttons | | RovingTabindex | roving-tabindex | class RovingTabindex | stable | Roving Tabindex Manager Implements the roving tabindex pattern for keyboard navigation in custom widgets Used for tabs, menus, toolbars, listboxes, etc. | | focusItem | roving-tabindex | focusItem(index: number): void | stable | - | | updateItems | roving-tabindex | updateItems(items: HTMLElement[] | NodeListOf<HTMLElement>): void | stable | - | | getCurrentIndex | roving-tabindex | getCurrentIndex(): number | stable | - | | getCurrentItem | roving-tabindex | getCurrentItem(): HTMLElement | null | stable | - | | destroy | roving-tabindex | destroy(): void | stable | - | | createRovingTabindex | roving-tabindex | (items: HTMLElement[] | NodeListOf<HTMLElement>, options?: RovingTabindexOptions) => RovingTabindex | stable | Create a roving tabindex manager Convenience function for creating a roving tabindex | | SkipLink | skip-link | (options?: SkipLinkOptions) => SkipLinkResult | stable | Skip Link Component Provides a keyboard-accessible way to bypass repeated content Meets WCAG 2.4.1 Level A requirement | | SkipLinks | skip-link | (links: SkipLinkOptions[]) => SkipLinkResult | stable | Create multiple skip links for different page sections |

Template Syntax

| Syntax | Purpose | Example | |--------|---------|---------| | {{ expr }} | Text interpolation | {{ count() }} | | @event | Event binding | @click="handler()" | | @if / @else-if / @else | Conditional rendering | @if="show()" | | @for | Loop rendering | @for="(item, i) of items()" track="item.id" | | @switch / @case / @default | Multi-way branch | @switch="value()" | | @defer / @load | Lazy loading | @defer="visible" @load="LazyComponent" | | [class.name] | Class toggle | [class.active]="isActive()" | | [style.prop] | Style binding | [style.color]="color()" | | @error / @fallback | Error boundary | @error="errorSig" |

Development

pnpm build              # Build package
pnpm test               # Run tests
pnpm test -- --watch    # Watch mode

Testing

| Feature | Unit | Integration | E2E | Status | | --- | --- | --- | --- | --- | | compileNodes | - | - | - | Missing unit, integration, e2e | | compileTemplate | - | - | - | Missing unit, integration, e2e | | bindTemplateDirectives | - | - | - | Missing unit, integration, e2e | | isSSRMode | - | - | - | Missing unit, integration, e2e | | toTextValue | - | - | - | Missing unit, integration, e2e | | __applyBindings | - | - | - | Missing unit, integration, e2e | | bootstrap | ✓ | - | - | Missing integration, e2e | | createContextProxy | - | - | - | Missing unit, integration, e2e | | loadWithTimeout | - | - | - | Missing unit, integration, e2e | | scheduleByPriority | - | - | - | Missing unit, integration, e2e | | html | - | - | - | Missing unit, integration, e2e | | onDestroy | ✓ | - | - | Missing integration, e2e | | bindBoundAttributes | - | - | - | Missing unit, integration, e2e | | bindClassDirectives | - | - | - | Missing unit, integration, e2e | | bindComponentTags | - | - | - | Missing unit, integration, e2e | | bindDefaultTwoWay | - | - | - | Missing unit, integration, e2e | | bindEventHandlers | - | - | - | Missing unit, integration, e2e | | bindTextInterpolations | - | - | - | Missing unit, integration, e2e | | buildPidMap | - | - | - | Missing unit, integration, e2e | | buildCommentMap | - | - | - | Missing unit, integration, e2e | | nearestCtx | - | - | - | Missing unit, integration, e2e | | mergeContexts | - | - | - | Missing unit, integration, e2e | | compileWithEvent | - | - | - | Missing unit, integration, e2e |

| Feature | Unit | Integration | E2E | Status | | --- | --- | --- | --- | --- | | bindSwitchCases | - | - | - | Missing unit, integration | | handleConditionalChain | - | - | - | Missing unit, integration | | bindConditionals | - | - | - | Missing unit, integration | | bindForLoops | - | - | - | Missing unit, integration | | createLoopManager | - | - | - | Missing unit, integration | | safeInsertBefore | - | - | - | Missing unit, integration | | markElementsAsLoopGenerated | - | - | - | Missing unit, integration | | createItemRange | - | - | - | Missing unit, integration | | wrapInRange | - | - | - | Missing unit, integration | | detachRange | - | - | - | Missing unit, integration | | createLoopSignals | - | - | - | Missing unit, integration | | updateLoopSignals | - | - | - | Missing unit, integration | | createLoopContext | - | - | - | Missing unit, integration | | findAllNestedLoopNodes | - | - | - | Missing unit, integration |

| Feature | Unit | Integration | E2E | Status | | --- | --- | --- | --- | --- | | LazyState | - | - | - | Missing unit, integration, e2e | | LazyOptions | - | - | - | Missing unit, integration, e2e | | LazyHandle | ✓ | - | - | Missing integration, e2e | | lazy | ✓ | - | - | Missing integration, e2e | | isLazyHandle | ✓ | - | - | Missing integration, e2e | | preloadLazy | ✓ | - | - | Missing integration, e2e | | ModuleRecord | ✓ | - | - | Missing integration, e2e | | loadModule | ✓ | - | - | Missing integration, e2e | | getModuleRecord | ✓ | - | - | Missing integration, e2e | | invalidateModule | ✓ | - | - | Missing integration, e2e | | getResolvedModuleKeys | ✓ | - | - | Missing integration, e2e | | clearModuleCache | ✓ | - | - | Missing integration, e2e | | bindDeferDirectives | ✓ | - | - | Missing integration | | registerDirectiveBinder | ✓ | - | - | Missing integration, e2e | | unregisterDirectiveBinder | ✓ | - | - | Missing integration, e2e | | executeCustomDirectiveBinders | ✓ | - | - | Missing integration, e2e |

| Feature | Unit | Integration | E2E | Status | | --- | --- | --- | --- | --- | | FocusTrap | ✓ | - | - | Missing integration | | activate | ✓ | - | - | Missing integration | | deactivate | ✓ | - | - | Missing integration | | updateFocusableElements | ✓ | - | - | Missing integration | | isActivated | ✓ | - | - | Missing integration | | destroy | ✓ | - | - | Missing integration | | createFocusTrap | ✓ | - | - | Missing integration | | isKey | ✓ | - | - | Missing integration | | isActivationKey | ✓ | - | - | Missing integration | | onEscape | ✓ | - | - | Missing integration | | onEnter | ✓ | - | - | Missing integration | | onSpace | ✓ | - | - | Missing integration | | onActivation | ✓ | - | - | Missing integration | | onArrowKeys | ✓ | - | - | Missing integration | | hasModifierKeys | ✓ | - | - | Missing integration | | onKeyboardShortcut | ✓ | - | - | Missing integration | | makeKeyboardAccessible | ✓ | - | - | Missing integration | | RovingTabindex | ✓ | - | - | Missing integration | | focusItem | ✓ | - | - | Missing integration | | updateItems | ✓ | - | - | Missing integration | | getCurrentIndex | ✓ | - | - | Missing integration | | getCurrentItem | ✓ | - | - | Missing integration | | destroy | ✓ | - | - | Missing integration | | createRovingTabindex | ✓ | - | - | Missing integration | | SkipLink | ✓ | - | - | Missing integration | | SkipLinks | ✓ | - | - | Missing integration |

Related Packages

Documentation

License

MIT