@ray-d-song/reactive-html v0.1.0
reactive-html
๐ 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
Package | Size (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
- lighterhtml by Andrea Giammarchi - The foundation this project builds upon
๐ API Reference
Core Functions
All lighterhtml functions are available:
html
- Create HTML templatessvg
- Create SVG templatesrender(where, what)
- Render templates to DOM
Reactive Extensions
createReactiveRender(effect)
- Create a reactive render functioncleanupReactive(element)
- Cleanup effects for an element
Compatible Signal Libraries
reactive-html works with any signal library that provides an effect
function:
- alien-signals
- @preact/signals
- solid-js (use
createEffect
) - vue (use
watchEffect
) - And many more!
๐ License
ISC License - same as lighterhtml
4 months ago