Signals
Signals library based on EventTarget and AsyncIterators
Install / Use
/learn @heyMP/SignalsREADME
IN DEVELOPMENT
@heymp/signals 🚦
Minimal signals like library built with powerful browser features; EventTarget and AsyncIterators.
Usage example
import { Signal } from '@heymp/signals';
const count = new Signal.State(0);
const isDone = new Signal.Computed(() => count.value > 3, [count]);
setInterval(() => {
count.value++;
}, 1000)
for await (const value of isDone) {
console.log('Counter is done:', value);
}
See the Signals Todo MVC example.
EventTarget
Signals are based on the EventTarget
interface. This means you can listen to the update event emitted from that signal.
const count = new Signal.State(0);
count.addEventListener('update', () => {
console.log('Count updated:', count.value);
});
AsyncIterator
Signals also contain a @@asyncIterator.
Async iterables are objects with a [Symbol.asyncIterator] method, which
returns an iterator who's next() method returns a Promise. This allows you
to use the for await...of syntax to listen to the signal.
NOTE: Using an async iterator in a for await...of loop will block the event loop
until the signal is done emitting values.
const count = new Signal.State(0);
for await (const value of count) {
console.log('Count updated:', value);
}
The cool thing about using AsyncIterators is that frameworks can expose methods for re-rendering components when new values are yielded.
For instance, Lit exposes asyncReplace and asyncAppend directives
to easily render new values from an async iterable.
import { LitElement, html } from 'lit'
import { asyncReplace } from 'lit/directives/async-replace.js'
export class MyElement extends LitElement {
render() {
return html`
<button @click=${() => store.counter.value++}>
count is ${asyncReplace(store.counter)}
</button>
`;
}
}
Adapters
This library exposes framework specific adapters to make it easy to sync changes of the Signals to trigger the component render lifecycle.
React
import { Signal } from '@heymp/signals';
import { useSignal } from '@heymp/signals/react';
const counter = new Signal.State(0);
function App() {
const [count] = useSignal(counter);
return (
<>
<button onClick={() => counter.value++}>
count is {count}
</button>
</>
);
}
Lit
import { LitElement, html } from 'lit'
import { State } from '@heymp/signals';
import { watchSignal } from '@heymp/signals/lit';
export class MyCounter extends LitElement {
@watchSignal
private count?: State<number>;
@watchSignal
private message = new State('hello, world');
render() {
return html`
${this.message.value} ${this.count?.value}
`
}
}
Signals Standard
There has been some awesome work done by Rob Eisenberg and library authors to propose Signals as a new TC39 standard. I highly recommend reading this article to learn more about this initiative.
Should I use this library?
This is mostly a proof of concept but we do use this pattern for some of our internal projects. There are known issues with EventTarget if there are thousands of EventTarget instances.
I would recommend looking at the Signal Polyfill as an alternative.
Related Skills
node-connect
351.8kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
110.9kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
351.8kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
351.8kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
