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

literaljs

v8.1.8

Published

A small JavaScript library for building reactive user interfaces.

Readme


Quick Start

Install

npm install literaljs

Hello World

/** @jsx h */
import { h, component, App } from 'literaljs';

const Counter = component({
  name: 'Counter',
  state: { count: 0 },
  methods() {
    return {
      increment() {
        this.setState({ count: this.getState().count + 1 });
      }
    };
  },
  render() {
    return (
      <button events={{ click: () => this.increment() }}>
        Count: {this.getState().count}
      </button>
    );
  }
});

new App(Counter, {}).mount('app');

JSX setup: Configure your build tool to use h as the pragma:

{ "plugins": [["@babel/plugin-transform-react-jsx", { "pragma": "h" }]] }

CDN (UMD)

<script src="https://unpkg.com/literaljs@8/build/index.umd.js"></script>
<script>
  const { h, component, App } = LiteralJS;
  // same as above
</script>

Public API

| Export | Purpose | |--------|---------| | h(tag, attrs, ...children) | Create a VNode (use as JSX pragma, Hyperscript, or plain object) | | component(config) | Define a component with state, methods, and lifecycle | | new App(rootComponent, initialStore) | Create an isolated app instance | | app.mount(domId) | Mount to a DOM element by ID | | app.unmount() | Remove from DOM, clean up events and state | | app.update() | Force a re-render | | app.setStore(updater) | Update the global store | | app.getStore() | Read the global store | | render(rootComponent, domId, store) | Legacy one-liner (backward-compatible) | | StateManager | Standalone state manager (for testing) | | generateUUID | UUID generator | | advanceRenderCycle | Advance the render cycle counter (testing) |


Component API

const MyComponent = component({
  name: 'MyComponent',           // Component name (for debugging and state keys)
  state: { count: 0 },          // Initial component state
  methods() {                   // Bound methods available as this.methodName
    return {
      increment() {
        this.setState({ count: this.getState().count + 1 });
      }
    };
  },
  mounted() {                   // Called after first render
    console.log('Mounted!');
  },
  updated() {                   // Called after re-render
    console.log('Updated!');
  },
  unmounted() {                  // Called when removed from DOM
    console.log('Cleaned up!');
  },
  render() {                    // Returns a VNode tree
    const { count } = this.getState();
    return (
      <div>
        <p>Count: {count}</p>
        <button events={{ click: () => this.increment() }}>
          Increment
        </button>
      </div>
    );
  }
});

Props

Pass data down from parent to child:

// Parent
<MyComponent key="unique" title="Hello" count={5} />

// Child
render() {
  return <h1>{this.props.title} — {this.props.count}</h1>;
}

Sibling Components (Auto-Keyed)

You don't need explicit key props for sibling components. LiteralJS automatically assigns stable, position-based instance keys so each sibling gets isolated state:

const Counter = component({
  name: 'Counter',
  state: { count: 0 },
  methods() {
    return {
      increment() { this.setState({ count: this.getState().count + 1 }); }
    };
  },
  render() {
    return <span>{this.getState().count}</span>;
  }
});

const App = component({
  name: 'App',
  render() {
    // Each Counter has its own independent state — no key prop needed
    return (
      <div>
        <Counter />   {/* auto-key: __auto_0__ */}
        <Counter />   {/* auto-key: __auto_1__ */}
        <Counter />   {/* auto-key: __auto_2__ */}
      </div>
    );
  }
});

How it works: Each render cycle, component factories reset a position counter. The first keyless sibling gets __auto_0__, the second __auto_1__, etc. Because the counter resets each render, the same position always maps to the same key — so state persists across re-renders while remaining isolated between siblings.

You can still use explicit key props when you need semantic identity (e.g., for list items that reorder):

{items.map(item => (
  <ListItem key={item.id} item={item} />
))}

Lifecycle Hooks

| Hook | Called When | Use Case | |------|------------|----------| | mounted() | After first DOM render | Initialize third-party libs, DOM queries | | updated() | After re-render | React to prop changes, DOM measurements | | unmounted() | Before DOM removal | Clean up timers, event listeners, subscriptions |

Component Instance Methods

| Method | Description | |--------|-------------| | this.getState() | Shallow copy of component state | | this.setState(updater) | Update state and trigger re-render | | this.getStore() | Shallow copy of global store | | this.setStore(updater) | Update global store and trigger re-render | | this.props | Current component props |


State Management

Component State (private, isolated)

Each component instance has its own private state. Sibling instances do not share state:

this.getState();              // Read current state (shallow copy)
this.setState({ count: 5 }); // Update and re-render

Global Store (shared across components)

// Write
this.setStore({ theme: 'dark' });

// Read
const theme = this.getStore().theme;

Initialize the store when creating the app:

new App(RootComponent, { theme: 'light', user: null }).mount('app');

Keys and Reconciliation

Auto-Keys (v8.0.4+)

LiteralJS automatically assigns position-based keys to sibling components. You don't need to add key props unless you need semantic identity for reordering.

Explicit Keys

Use explicit key props when:

  • List items can be reordered (keyed reconciliation tracks DOM identity)
  • You want to force a component to remount when data changes
{items.map(item => (
  <TodoItem key={item.id} item={item} />
))}

Architecture

Design Principles

  • Zero runtime dependencies — only dev dependencies for builds and tests
  • Small surface area — 9 exports, one component pattern, one state model
  • Plain functions — no classes, no hooks, no decorators, no magic
  • Isolated instances — multiple App instances on one page, zero globals

Module Structure

App (container)
├── StateManager      — global store + component state with subscriptions
├── EventManager      — WeakMap delegation + event cache
├── Renderer           — DOM creation + virtual DOM diff/patch
├── LifecycleQueue     — mounted/updated/unmounted lifecycle hooks
├── DiffEngine         — keyed + non-keyed child reconciliation
├── MountOrchestrator  — render cycle coordination + tree generation
├── UpdateLoop         — microtask batched scheduler
├── ComponentInstance  — per-instance state, methods, and lifecycle
└── VNode             — lightweight virtual DOM representation

v8 Performance Features

| Feature | Benefit | |---------|---------| | Keyed reconciliation | Preserves DOM identity across reordering | | WeakMap event delegation | Skips ancestor walk when no handlers exist | | Component memoization | Shallow props comparison skips re-render | | Text node fast path | textContent update instead of replaceChild | | Reference-equality shortcuts | Skips diffing for unchanged VNodes, children, attrs | | Auto-instance keys | Sibling components isolated without manual key props | | Microtask batching | Multiple setState calls collapse to one render |


Bundle Size

| Format | Minified | Gzipped | |--------|----------|---------| | ESM (modern) | 16.6 kB | 5.3 kB | | CJS | 16.6 kB | 5.3 kB | | UMD | 16.8 kB | 5.4 kB |

Target: Chrome 90+, Firefox 90+, Safari 14+ (ES2018). No Object.assign polyfill needed.


Performance

Benchmarks measured in JSDOM. Real Chrome results are significantly faster.

| Benchmark | v7 | v8.0.5 | v8.1.0 | Change (v7→v8.1) | |-----------|----|--------|--------|------------------| | create 1k rows | ~112ms | ~47ms | ~46ms | -59% | | create 10k rows | ~890ms | ~495ms | ~458ms | -49% | | replace 1k rows | ~65ms | ~10ms | ~0.3ms | -99.5% | | partial update | ~186ms | ~126ms | ~3.7ms | -98% | | select row | ~22ms | ~0.03ms | ~0.03ms | -99.8% | | swap rows | — | ~90ms | ~0.3ms | -99.7% | | remove row | ~74ms | ~8.6ms | ~0.2ms | -99.7% | | append rows | — | ~197ms | ~0.3ms | -99.8% | | clear rows | ~3ms | ~0.01ms | ~0.2ms | -93% |


Event Handling

LiteralJS uses event delegation. Define handlers via the events attribute:

<button events={{ click: () => this.increment() }}>
  Click me
</button>

Supported events: click, input, change, submit, keyup, keydown, focus, blur, mouseenter, mouseleave, touchstart, touchend, and more.


Syntax Options

JSX (recommended)

/** @jsx h */
import { h, component, App } from 'literaljs';

const View = component({
  name: 'View',
  render() {
    return <div class="app"><h1>Hello</h1></div>;
  }
});

Hyperscript

const View = component({
  name: 'View',
  render() {
    return h('div', { class: 'app' }, h('h1', {}, 'Hello'));
  }
});

Object syntax

const View = component({
  name: 'View',
  render() {
    return { e: 'div', a: { class: 'app' }, c: [{ e: 'h1', a: {}, c: ['Hello'] }] };
  }
});

Style Handling

Pass styles as objects (not strings). String styles cause a TypeError because LiteralJS iterates over style keys:

// ✅ Correct
<div style={{ position: 'fixed', top: '0', left: '0' }}>

// ❌ Wrong — causes TypeError
<div style="position:fixed;top:0;left:0">

Changelog

[8.1.8] - 2026-06-09

Fixed: State updates now correctly trigger DOM updates when component instances are returned directly from render().

  • Fixed memoization in tree.js to preserve component instances instead of flattening them into VNodes
  • Added cloneVNode() helper to properly clone VNode trees while preserving component instances
  • Component instances cached during memoization are now processed during tree recursion
  • All 475 tests pass, including new regression tests for nested App and router-mounted App patterns

[8.0.4] - 2026-05-19

Fixed: Sibling components without explicit key props now have isolated state.

  • Replaced __default__ singleton key with auto-incrementing position keys (__auto_0__, __auto_1__, etc.)
  • Added render-cycle counter (advanceRenderCycle) for stable keys across re-renders
  • MountOrchestrator calls advanceRenderCycle() before each render pass
  • Exported advanceRenderCycle for testing
  • Updated tree.js (simplified, removed _resetPosition from VNode walk)

[8.0.3] - 2026-05-18

Fixed: Null/undefined guard in generateTree before typeof check.

[8.0.2] - 2026-05-18

Fixed: Tree direct-props fix — two code paths for backward compat (newC.a.props) and modern JSX ({ ...newC.a }).

[8.0.1] - 2026-05-18

Fixed: WeakMap event delegation. Component render memoization. Keyed reconciliation.

[8.0.0] - 2026-05-18

Initial v8 release. Full architecture refactor from monolith to 12 modules.