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

signal-html

v0.1.1-alpha.1

Published

lighterhtml, but reactive with signal

Readme

signal-html

License: ISC Blazing Fast Reactive

🚀 What is signal-html?

signal-html is a fork of lighterhtml that adds reactive programming capabilities. It works with any signal-based reactive library by accepting an effect function. This combines the simplicity and performance of lighterhtml with automatic DOM updates when your data changes.

Key Features

  • 🔥 All the power of lighterhtml - Fast template rendering, minimal bundle size
  • ⚡️ Reactive updates - Automatically re-render when signals change
  • 🎯 Fine-grained reactivity - Only updates the parts of DOM that actually changed
  • 🧹 Automatic cleanup - No memory leaks, effects are cleaned up automatically
  • 🔗 Library agnostic - Works with any signal-based reactive library

🎯 Quick Start

Installation

npm install signal-html

Basic Usage

import { html, createReactiveRender } from 'signal-html'
// Use any signal library - here we use alien-signals as an example
import { signal, effect } from 'alien-signals'

// Create a reactive render function by passing your effect function
const reactiveRender = createReactiveRender(effect)

// Create a signal
const count = signal(0)

// Reactive rendering - automatically updates when count changes
reactiveRender(document.body, () => html`
  <div>
    <h1>Count: ${count()}</h1>
    <button onclick=${() => count(count() + 1)}>
      Increment
    </button>
  </div>
`)

With Computed Values

import { html, createReactiveRender } from 'signal-html'
import { signal, computed, effect } from 'alien-signals'

const reactiveRender = createReactiveRender(effect)

const firstName = signal('John')
const lastName = signal('Doe')
const fullName = computed(() => `${firstName()} ${lastName()}`)

reactiveRender(document.body, () => html`
  <div>
    <h1>Hello, ${fullName()}!</h1>
    <input 
      value=${firstName()} 
      oninput=${e => firstName(e.target.value)}
      placeholder="First name"
    />
    <input 
      value=${lastName()} 
      oninput=${e => lastName(e.target.value)}
      placeholder="Last name"
    />
  </div>
`)

🔄 Reactive API

createReactiveRender(effect)

Creates a reactive render function that automatically updates the DOM when signals change. The effect parameter should be an effect function from your chosen reactive library.

import { createReactiveRender } from 'signal-html'

// Works with any reactive library
// alien-signals
import { effect } from 'alien-signals'
const reactiveRender = createReactiveRender(effect)

// @preact/signals
import { effect } from '@preact/signals'
const reactiveRender = createReactiveRender(effect)

// solid-js
import { createEffect } from 'solid-js'
const reactiveRender = createReactiveRender(createEffect)

cleanupReactive(element)

Manually cleanup reactive effects for a DOM element. Usually not needed as effects are cleaned up automatically when a new render is applied to the same element.

import { cleanupReactive } from 'signal-html'

// Cleanup effects for an element
cleanupReactive(myElement)

📋 Complete Example: Todo App

import { html, createReactiveRender } from 'signal-html'
import { signal, computed, effect } from 'alien-signals'

const reactiveRender = createReactiveRender(effect)

// State
const todos = signal([])
const filter = signal('all') // 'all', 'active', 'completed'
const newTodo = signal('')

// Computed values
const filteredTodos = computed(() => {
  const allTodos = todos()
  const currentFilter = filter()
  
  if (currentFilter === 'active') return allTodos.filter(t => !t.completed)
  if (currentFilter === 'completed') return allTodos.filter(t => t.completed)
  return allTodos
})

// Actions
const addTodo = () => {
  const text = newTodo().trim()
  if (text) {
    todos([...todos(), { id: Date.now(), text, completed: false }])
    newTodo('')
  }
}

const toggleTodo = (id) => {
  todos(todos().map(t => 
    t.id === id ? { ...t, completed: !t.completed } : t
  ))
}

const removeTodo = (id) => {
  todos(todos().filter(t => t.id !== id))
}

// Reactive render
reactiveRender(document.body, () => html`
  <div class="todo-app">
    <h1>Todo App</h1>
    
    <div class="input-section">
      <input 
        value=${newTodo()}
        oninput=${e => newTodo(e.target.value)}
        onkeypress=${e => e.key === 'Enter' && addTodo()}
        placeholder="What needs to be done?"
      />
      <button onclick=${addTodo}>Add</button>
    </div>

    <div class="filters">
      <button 
        class=${filter() === 'all' ? 'active' : ''}
        onclick=${() => filter('all')}
      >All</button>
      <button 
        class=${filter() === 'active' ? 'active' : ''}
        onclick=${() => filter('active')}
      >Active</button>
      <button 
        class=${filter() === 'completed' ? 'active' : ''}
        onclick=${() => filter('completed')}
      >Completed</button>
    </div>

    <ul class="todo-list">
      ${filteredTodos().map(todo => html`
        <li class=${todo.completed ? 'completed' : ''}>
          <input 
            type="checkbox" 
            checked=${todo.completed}
            onchange=${() => toggleTodo(todo.id)}
          />
          <span>${todo.text}</span>
          <button onclick=${() => removeTodo(todo.id)}>×</button>
        </li>
      `)}
    </ul>
  </div>
`)

🎨 All lighterhtml Features Included

signal-html includes all the powerful features from lighterhtml:

Template Literals

import { html, svg } from 'signal-html'

// HTML templates
const htmlTemplate = html`<div class="example">${content}</div>`

// SVG templates  
const svgTemplate = svg`<circle r="10" fill="${color}"/>`

Special Attributes

// Boolean attributes
html`<input ?disabled=${isDisabled} />`

// Property setters
html`<div .textContent=${'Direct property access'} />`

// Dataset
html`<div .dataset=${{ userId: '123', role: 'admin' }} />`

// Aria attributes
html`<div aria=${{ label: 'Close button', expanded: true }} />`

// Event listeners with options
html`<button onclick=${[handler, { once: true }]}>Click me</button>`

// Refs
html`<input ref=${inputRef} />`

Keyed Rendering

// For persistent references
const keyedTemplate = html.for(someObject, 'uniqueId')`
  <div>This content is keyed and will be reused</div>
`

// One-off nodes
const oneOffNode = html.node`<div>Standalone node</div>`

🚀 Performance

signal-html inherits lighterhtml's excellent performance characteristics:

  • Minimal DOM operations - Only updates what actually changed
  • Efficient diffing - Uses battle-tested algorithms from udomdiff
  • Template caching - Templates are parsed once and reused
  • Fine-grained updates - Reactivity system only triggers updates for affected parts

Plus reactive-specific optimizations:

  • Smart effect cleanup - Prevents memory leaks automatically
  • Batched updates - Multiple signal changes in the same tick are batched
  • Dependency tracking - Only tracks the signals actually used in templates

📦 Bundle Size

| Package | Size (gzipped) | |---------|----------------| | signal-html | ~7.2KB |


🤝 Relationship to lighterhtml

This project is a fork of lighterhtml by Andrea Giammarchi (WebReflection). We've added reactive capabilities while maintaining full compatibility with the original API.

Credits


📖 API Reference

Core Functions

All lighterhtml functions are available:

  • html - Create HTML templates
  • svg - Create SVG templates
  • render(where, what) - Render templates to DOM

Reactive Extensions

  • createReactiveRender(effect) - Create a reactive render function
  • cleanupReactive(element) - Cleanup effects for an element

Compatible Signal Libraries

signal-html works with any signal library that provides an effect function:


📄 License

ISC License - same as lighterhtml