alien-signals

The lightest signal library.

MIT 81 个版本
安装
npm install alien-signals
yarn add alien-signals
pnpm add alien-signals
bun add alien-signals
README


npm package Ask DeepWiki

alien-signals

This project explores a push-pull based signal algorithm. The implementation is related to the following frontend projects:

We impose some constraints (such as not using Array/Set/Map and disallowing function recursion in the algorithmic core) to ensure performance. We found that under these conditions, maintaining algorithmic simplicity offers more significant improvements than complex scheduling strategies.

I wrote the reactivity code for both Vue and alien-signals. Below is a benchmark comparison against Vue 3.4 and other frameworks. The core algorithm has since been ported back to Vue 3.6.

Image

Benchmark repo: https://github.com/transitive-bullshit/js-reactivity-benchmark

Background

I spent considerable time optimizing Vue 3.4’s reactivity system, gaining experience along the way. Since Vue 3.5 switched to a pull-based algorithm similar to Preact, I decided to continue researching a push-pull based implementation in a separate project. The algorithm is used in Vue language tools for incremental AST parsing and virtual code generation.

Other Language Implementations

Derived Projects

Adoption

Usage

Basic APIs

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

const count = signal(1);
const doubleCount = computed(() => count() * 2);

effect(() => {
  console.log(`Count is: ${count()}`);
}); // Console: Count is: 1

console.log(doubleCount()); // 2

count(2); // Console: Count is: 2

console.log(doubleCount()); // 4

Effect Scope

import { signal, effect, effectScope } from 'alien-signals';

const count = signal(1);

const stopScope = effectScope(() => {
  effect(() => {
    console.log(`Count in scope: ${count()}`);
  }); // Console: Count in scope: 1
});

count(2); // Console: Count in scope: 2

stopScope();

count(3); // No console output

Nested Effects

Effects can be nested inside other effects. When the outer effect re-runs, inner effects from the previous run are automatically cleaned up, and new inner effects are created if needed. The system ensures proper execution order — outer effects always run before their inner effects:

import { signal, effect } from 'alien-signals';

const show = signal(true);
const count = signal(1);

effect(() => {
  if (show()) {
    // This inner effect is created when show() is true
    effect(() => {
      console.log(`Count is: ${count()}`);
    });
  }
}); // Console: Count is: 1

count(2); // Console: Count is: 2

// When show becomes false, the inner effect is cleaned up
show(false); // No output

count(3); // No output (inner effect no longer exists)

Manual Triggering

The trigger() function allows you to manually trigger updates for downstream dependencies when you've directly mutated a signal's value without using the signal setter:

import { signal, computed, trigger } from 'alien-signals';

const arr = signal<number[]>([]);
const length = computed(() => arr().length);

console.log(length()); // 0

// Direct mutation doesn't automatically trigger updates
arr().push(1);
console.log(length()); // Still 0

// Manually trigger updates
trigger(arr);
console.log(length()); // 1

You can also trigger multiple signals at once:

import { signal, computed, trigger } from 'alien-signals';

const src1 = signal<number[]>([]);
const src2 = signal<number[]>([]);
const total = computed(() => src1().length + src2().length);

src1().push(1);
src2().push(2);

trigger(() => {
  src1();
  src2();
});

console.log(total()); // 2

Creating Your Own Surface API

You can reuse alien-signals’ core algorithm via createReactiveSystem() to build your own signal API. For implementation examples, see:

About propagate and checkDirty functions

The actual implementations of propagate and checkDirty in system.ts replace recursive calls with iterative stack-based traversal for performance. The recursive versions below are equivalent and easier to follow — useful as a reference when porting to other languages where the iterative optimization may not help.

propagate
function propagate(link: Link, innerWrite: boolean): void {
	do {
		const sub = link.sub;

		let flags = sub.flags;

		if (!(flags & (ReactiveFlags.RecursedCheck | ReactiveFlags.Recursed | ReactiveFlags.Dirty | ReactiveFlags.Pending))) {
			sub.flags = flags | ReactiveFlags.Pending;
			if (innerWrite) {
				sub.flags |= ReactiveFlags.Recursed;
			}
		} else if (!(flags & (ReactiveFlags.RecursedCheck | ReactiveFlags.Recursed))) {
			flags = ReactiveFlags.None;
		} else if (!(flags & ReactiveFlags.RecursedCheck)) {
			sub.flags = (flags & ~ReactiveFlags.Recursed) | ReactiveFlags.Pending;
		} else if (!(flags & (ReactiveFlags.Dirty | ReactiveFlags.Pending)) && isValidLink(link, sub)) {
			sub.flags = flags | ReactiveFlags.Recursed | ReactiveFlags.Pending;
			flags &= ReactiveFlags.Mutable;
		} else {
			flags = ReactiveFlags.None;
		}

		if (flags & ReactiveFlags.Watching) {
			notify(sub);
		}

		if (flags & ReactiveFlags.Mutable) {
			const subSubs = sub.subs;
			if (subSubs !== undefined) {
				propagate(subSubs, innerWrite);
			}
		}

		link = link.nextSub!;
	} while (link !== undefined);
}
checkDirty
function checkDirty(link: Link, sub: ReactiveNode): boolean {
	do {
		const dep = link.dep;
		const depFlags = dep.flags;

		if (sub.flags & ReactiveFlags.Dirty) {
			return true;
		} else if ((depFlags & (ReactiveFlags.Mutable | ReactiveFlags.Dirty)) === (ReactiveFlags.Mutable | ReactiveFlags.Dirty)) {
			if (update(dep)) {
				const subs = dep.subs!;
				if (subs.nextSub !== undefined) {
					shallowPropagate(subs);
				}
				return true;
			}
		} else if ((depFlags & (ReactiveFlags.Mutable | ReactiveFlags.Pending)) === (ReactiveFlags.Mutable | ReactiveFlags.Pending)) {
			if (checkDirty(dep.deps!, dep)) {
				if (update(dep)) {
					const subs = dep.subs!;
					if (subs.nextSub !== undefined) {
						shallowPropagate(subs);
					}
					return true;
				}
			} else {
				dep.flags = depFlags & ~ReactiveFlags.Pending;
			}
		}

		link = link.nextDep!;
	} while (link !== undefined);

	return false;
}
版本列表
3.2.1 2026-05-14
3.2.0 2026-05-12
3.1.2 2025-12-24
3.1.1 2025-11-25
3.1.0 2025-11-08
3.0.6 2025-11-05
3.0.5 2025-11-01
3.0.4 2025-11-01
3.0.3 2025-10-20
3.0.2 2025-10-20
3.0.1 2025-10-16
3.0.0 2025-09-27
2.0.8 2025-09-26
2.0.7 2025-08-19
2.0.6 2025-08-01
2.0.5 2025-05-15
2.0.4 2025-05-07
2.0.3 2025-05-07
2.0.2 2025-05-07
2.0.1 2025-04-30
2.0.0-alpha.2 2025-04-28
2.0.0-alpha.1 2025-04-28
2.0.0-alpha.0 2025-01-24
2.0.0 2025-04-30
1.1.0-alpha.3 2025-01-24
1.1.0-alpha.2 2025-01-20
1.1.0-alpha.1 2025-01-20
1.1.0-alpha.0 2025-01-20
1.0.13 2025-03-31
1.0.12 2025-03-31
1.0.11 2025-03-31
1.0.10 2025-03-28
1.0.9 2025-03-27
1.0.8 2025-03-25
1.0.7 2025-03-23
1.0.6 2025-03-21
1.0.5 2025-03-20
1.0.4 2025-02-20
1.0.3 2025-01-30
1.0.2 2025-01-30
1.0.1 2025-01-20
1.0.0-alpha.3 2025-01-12
1.0.0-alpha.2 2025-01-12
1.0.0-alpha.1 2025-01-11
1.0.0-alpha.0 2025-01-11
1.0.0 2025-01-14
0.6.0 2025-01-10
0.5.0 2024-11-26
0.4.14 2025-01-08
0.4.13 2025-01-07
0.4.12 2024-12-30
0.4.11 2024-12-25
0.4.10 2024-12-24
0.4.9 2024-12-23
0.4.8 2024-12-22
0.4.7 2024-12-22
0.4.6 2024-12-22
0.4.5 2024-12-17
0.4.4 2024-11-26
0.4.3 2024-11-25
0.4.2 2024-11-24
0.4.1 2024-11-22
0.4.0 2024-11-22
0.3.2 2024-11-21
0.3.1 2024-11-20
0.3.0-alpha.2 2024-11-16
0.3.0-alpha.1 2024-11-15
0.3.0-alpha.0 2024-11-15
0.3.0 2024-11-17
0.2.2-alpha.0 2024-11-11
0.2.2 2024-11-13
0.2.1 2024-11-10
0.2.0 2024-10-20
0.1.4 2024-10-18
0.1.3 2024-10-16
0.1.2 2024-10-16
0.1.1 2024-10-15
0.1.0 2024-10-14
0.0.6 2024-10-13
0.0.5 2024-10-12
0.0.4 2024-10-09