nude-reactivity
v0.0.1
Published
A minimal, zero dependency signal library, intended to be used in Web Components abstractions.
Maintainers
Readme
Nude Reactivity
A minimal, zero dependency signal library, intended to be used in Web Components abstractions.
Seriously, another one?
Most signals libraries were designed for frameworks and made it hard to use them to develop custom elements that work like native ones.
Design principles:
- DX as a priority
- Flexibility for consumers over encapsulation
- Lightweight core with abstractions layered on top
Resulting design:
- Sync & eager by default; you only opt into async or lazy where it can't be observed otherwise
- Cycle detection with smart handling of self-writes to avoid false positives
- Glitch-free: no intermediate states, no "stale" values
- Flexible computed write policy (override, ignore, throw)
- Pluggable equality, so you control what counts as a change
- No build step: pure ESM you can import straight from source
Install
npm install nude-reactivityimport { signal, computed, effect, watch, reactive } from "nude-reactivity";Sync by default
No microtasks, no promises to juggle, no ticks.
Meaning, this just works:
import { signal, computed } from "nude-reactivity";
const a = signal(1);
const b = computed(() => a.value + 1);
a.value++;
console.log(b.value); // 3Reactive objects
reactive() makes a plain object reactive in place: data properties become signals, getters become computeds. Consumers just touch plain properties — no .value, no framework.
import { reactive } from "nude-reactivity";
const state = reactive({
count: 1,
get double () {
return this.count * 2;
},
});
state.count++;
console.log(state.double); // 4Writing a getter-only property is governed by the computedWrites option ("ignore" by default, or "override" / "throw").
Effects and watchers
effect() runs a function and re-runs it whenever its reactive dependencies change. It returns a teardown function that stops it:
const stop = effect(() => console.log(a.value));
a.value++; // logs
stop(); // no more re-runswatch() is like effect(), but pausable: the function it returns pauses the effect and itself returns a resume function, so you can toggle it on and off.
const pause = watch(() => console.log(a.value));
const resume = pause(); // detached: changes are ignored
resume(); // re-runs and re-tracks dependenciesAPI
| Export | Description |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| signal(value) | A writable reactive value, read/written via .value. |
| computed(fn, options?) | A derived value. options.lazy opts into pull-based (recompute on read); options.equals customizes change detection. |
| effect(fn) | Runs fn, re-running on dependency changes. Returns a teardown function. |
| watch(fn) | Like effect(), but the returned function pauses and resumes. |
| reactive(obj, options?) | Makes an object's properties reactive in place. |
| ReactiveNode (default) | The low-level node all of the above are built on. |
The library is layered, and each layer is independently importable: nude-reactivity/node (core), nude-reactivity/reactive (objects), nude-reactivity/shortcuts (signal/computed/effect/watch).
Roadmap
- [ ] Track
oldValue - [ ] Better self-write handling
- [ ] Batched microtask updates
- [ ] Async computeds/effects
- [ ] Reactive class
