0.1.0 โ€ข Published 4 months ago

@ray-d-song/reactive-html v0.1.0

Weekly downloads
-
License
ISC
Repository
github
Last release
4 months ago

reactive-html

License: ISC Blazing Fast Reactive

๐Ÿš€ What is reactive-html?

reactive-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 reactive-html

Basic Usage

import { html, createReactiveRender } from 'reactive-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 'reactive-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 'reactive-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 'reactive-html'

// Cleanup effects for an element
cleanupReactive(myElement)

๐Ÿ“‹ Complete Example: Todo App

import { html, createReactiveRender } from 'reactive-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

reactive-html includes all the powerful features from lighterhtml:

Template Literals

import { html, svg } from 'reactive-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

reactive-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

PackageSize (gzipped)
reactive-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

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


๐Ÿ“„ License

ISC License - same as lighterhtml