first commit
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024-present Johnson Chu
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+266
@@ -0,0 +1,266 @@
|
||||
<p align="center">
|
||||
<img src="assets/logo.png" width="250"><br>
|
||||
<p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://npmjs.com/package/alien-signals"><img src="https://badgen.net/npm/v/alien-signals" alt="npm package"></a>
|
||||
<a href="https://deepwiki.com/stackblitz/alien-signals"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
|
||||
</p>
|
||||
|
||||
# alien-signals
|
||||
|
||||
This project explores a push-pull based signal algorithm. The implementation is related to the following frontend projects:
|
||||
|
||||
- Propagation algorithm of Vue 3
|
||||
- Preact’s double-linked-list approach (https://preactjs.com/blog/signal-boosting/)
|
||||
- Inner effects scheduling of Svelte
|
||||
- Graph-coloring approach of Reactively (https://milomg.dev/2022-12-01/reactivity)
|
||||
|
||||
We impose some constraints (such as not using Array/Set/Map and disallowing function recursion in [the algorithmic core](https://github.com/stackblitz/alien-signals/blob/master/src/system.ts)) 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](https://github.com/vuejs/core/pull/12349).
|
||||
|
||||
<img width="1210" alt="Image" src="https://github.com/user-attachments/assets/88448f6d-4034-4389-89aa-9edf3da77254" />
|
||||
|
||||
> Benchmark repo: https://github.com/transitive-bullshit/js-reactivity-benchmark
|
||||
|
||||
## Background
|
||||
|
||||
I spent considerable time [optimizing Vue 3.4’s reactivity system](https://github.com/vuejs/core/pull/5912), gaining experience along the way. Since Vue 3.5 [switched to a pull-based algorithm similar to Preact](https://github.com/vuejs/core/pull/10397), 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
|
||||
|
||||
- **Dart:** [medz/alien-signals-dart](https://github.com/medz/alien-signals-dart)
|
||||
- **Dart:** [void-signals/void_signals](https://github.com/void-signals/void_signals)
|
||||
- **Lua:** [YanqingXu/alien-signals-in-lua](https://github.com/YanqingXu/alien-signals-in-lua)
|
||||
- **Lua 5.4:** [xuhuanzy/alien-signals-lua](https://github.com/xuhuanzy/alien-signals-lua)
|
||||
- **Luau:** [Nicell/alien-signals-luau](https://github.com/Nicell/alien-signals-luau)
|
||||
- **Java:** [CTRL-Neo-Studios/java-alien-signals](https://github.com/CTRL-Neo-Studios/java-alien-signals)
|
||||
- **C#:** [CTRL-Neo-Studios/csharp-alien-signals](https://github.com/CTRL-Neo-Studios/csharp-alien-signals)
|
||||
- **Go:** [delaneyj/alien-signals-go](https://github.com/delaneyj/alien-signals-go)
|
||||
- **Rust:** [wuzekang/samara-signals](https://github.com/wuzekang/samara/tree/main/crates/signals)
|
||||
- **Rust:** [ohkami-rs/alien-signals-rs](https://github.com/ohkami-rs/alien-signals-rs)
|
||||
|
||||
## Derived Projects
|
||||
|
||||
- [Rajaniraiyn/react-alien-signals](https://github.com/Rajaniraiyn/react-alien-signals): React bindings for the alien-signals API
|
||||
- [CCherry07/alien-deepsignals](https://github.com/CCherry07/alien-deepsignals): Use alien-signals with the interface of a plain JavaScript object
|
||||
- [hunghg255/reactjs-signal](https://github.com/hunghg255/reactjs-signal): Share Store State with Signal Pattern
|
||||
- [gn8-ai/universe-alien-signals](https://github.com/gn8-ai/universe-alien-signals): Enables simple use of the Alien Signals state management system in modern frontend frameworks
|
||||
- [WebReflection/alien-signals](https://github.com/WebReflection/alien-signals): Preact signals like API and a class based approach for easy brand check
|
||||
- [@lift-html/alien](https://github.com/JLarky/lift-html/tree/main/packages/alien): Integrating alien-signals into lift-html
|
||||
- [ilha](https://github.com/ilhajs/ilha): A tiny web UI library built around the islands architecture
|
||||
|
||||
## Adoption
|
||||
|
||||
- [vuejs/core](https://github.com/vuejs/core): The core algorithm has been ported to v3.6 (PR: https://github.com/vuejs/core/pull/12349)
|
||||
- [statelyai/xstate](https://github.com/statelyai/xstate): The core algorithm has been ported to implement the atom architecture (PR: https://github.com/statelyai/xstate/pull/5250)
|
||||
- [flamrdevs/xignal](https://github.com/flamrdevs/xignal): Infrastructure for the reactive system
|
||||
- [vuejs/language-tools](https://github.com/vuejs/language-tools): Used in the language-core package for virtual code generation
|
||||
- [unuse](https://github.com/un-ts/unuse): A framework-agnostic `use` library inspired by `VueUse`
|
||||
|
||||
## Usage
|
||||
|
||||
#### Basic APIs
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```ts
|
||||
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:
|
||||
|
||||
```ts
|
||||
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:
|
||||
|
||||
```ts
|
||||
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:
|
||||
|
||||
```ts
|
||||
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:
|
||||
|
||||
- [Starter template](https://github.com/johnsoncodehk/alien-signals-starter) (implements `.get()` & `.set()` methods like the [Signals proposal](https://github.com/tc39/proposal-signals))
|
||||
- [stackblitz/alien-signals/src/index.ts](https://github.com/stackblitz/alien-signals/blob/master/src/index.ts)
|
||||
- [proposal-signals/signal-polyfill#44](https://github.com/proposal-signals/signal-polyfill/pull/44)
|
||||
|
||||
|
||||
## About `propagate` and `checkDirty` functions
|
||||
|
||||
The actual implementations of `propagate` and `checkDirty` in [system.ts](https://github.com/stackblitz/alien-signals/blob/master/src/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.
|
||||
|
||||
<details>
|
||||
<summary><code>propagate</code></summary>
|
||||
|
||||
```ts
|
||||
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);
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><code>checkDirty</code></summary>
|
||||
|
||||
```ts
|
||||
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;
|
||||
}
|
||||
```
|
||||
</details>
|
||||
+386
@@ -0,0 +1,386 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getActiveSub = getActiveSub;
|
||||
exports.setActiveSub = setActiveSub;
|
||||
exports.getBatchDepth = getBatchDepth;
|
||||
exports.startBatch = startBatch;
|
||||
exports.endBatch = endBatch;
|
||||
exports.isSignal = isSignal;
|
||||
exports.isComputed = isComputed;
|
||||
exports.isEffect = isEffect;
|
||||
exports.isEffectScope = isEffectScope;
|
||||
exports.signal = signal;
|
||||
exports.computed = computed;
|
||||
exports.effect = effect;
|
||||
exports.effectScope = effectScope;
|
||||
exports.trigger = trigger;
|
||||
const system_js_1 = require("./system.cjs");
|
||||
const HasChildEffect = 64;
|
||||
let cycle = 0;
|
||||
let runDepth = 0;
|
||||
let batchDepth = 0;
|
||||
let notifyIndex = 0;
|
||||
let queuedLength = 0;
|
||||
let activeSub;
|
||||
const queued = [];
|
||||
const { link, unlink, propagate, checkDirty, shallowPropagate, } = (0, system_js_1.createReactiveSystem)({
|
||||
update(node) {
|
||||
if ('getter' in node) {
|
||||
return updateComputed(node);
|
||||
}
|
||||
if ('currentValue' in node) {
|
||||
return updateSignal(node);
|
||||
}
|
||||
node.flags = 1;
|
||||
return true;
|
||||
},
|
||||
notify(effect) {
|
||||
let insertIndex = queuedLength;
|
||||
let firstInsertedIndex = insertIndex;
|
||||
do {
|
||||
queued[insertIndex++] = effect;
|
||||
effect.flags &= ~2;
|
||||
effect = effect.subs?.sub;
|
||||
if (effect === undefined || !(effect.flags & 2)) {
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
queuedLength = insertIndex;
|
||||
while (firstInsertedIndex < --insertIndex) {
|
||||
const left = queued[firstInsertedIndex];
|
||||
queued[firstInsertedIndex++] = queued[insertIndex];
|
||||
queued[insertIndex] = left;
|
||||
}
|
||||
},
|
||||
unwatched(node) {
|
||||
if ('getter' in node) {
|
||||
if (node.depsTail !== undefined) {
|
||||
node.flags = 1 | 16;
|
||||
disposeAllDepsInReverse(node);
|
||||
}
|
||||
}
|
||||
else if ('currentValue' in node) {
|
||||
}
|
||||
else if ('fn' in node) {
|
||||
effectOper.call(node);
|
||||
}
|
||||
else {
|
||||
effectScopeOper.call(node);
|
||||
}
|
||||
},
|
||||
});
|
||||
function getActiveSub() {
|
||||
return activeSub;
|
||||
}
|
||||
function setActiveSub(sub) {
|
||||
const prevSub = activeSub;
|
||||
activeSub = sub;
|
||||
return prevSub;
|
||||
}
|
||||
function getBatchDepth() {
|
||||
return batchDepth;
|
||||
}
|
||||
function startBatch() {
|
||||
++batchDepth;
|
||||
}
|
||||
function endBatch() {
|
||||
if (!--batchDepth) {
|
||||
flush();
|
||||
}
|
||||
}
|
||||
function isSignal(fn) {
|
||||
return fn.name === 'bound ' + signalOper.name;
|
||||
}
|
||||
function isComputed(fn) {
|
||||
return fn.name === 'bound ' + computedOper.name;
|
||||
}
|
||||
function isEffect(fn) {
|
||||
return fn.name === 'bound ' + effectOper.name;
|
||||
}
|
||||
function isEffectScope(fn) {
|
||||
return fn.name === 'bound ' + effectScopeOper.name;
|
||||
}
|
||||
function signal(initialValue) {
|
||||
return signalOper.bind({
|
||||
currentValue: initialValue,
|
||||
pendingValue: initialValue,
|
||||
subs: undefined,
|
||||
subsTail: undefined,
|
||||
flags: 1,
|
||||
});
|
||||
}
|
||||
function computed(getter) {
|
||||
return computedOper.bind({
|
||||
value: undefined,
|
||||
subs: undefined,
|
||||
subsTail: undefined,
|
||||
deps: undefined,
|
||||
depsTail: undefined,
|
||||
flags: 0,
|
||||
getter: getter,
|
||||
});
|
||||
}
|
||||
function effect(fn) {
|
||||
const e = {
|
||||
fn,
|
||||
cleanup: undefined,
|
||||
subs: undefined,
|
||||
subsTail: undefined,
|
||||
deps: undefined,
|
||||
depsTail: undefined,
|
||||
flags: 2 | 4,
|
||||
};
|
||||
const prevSub = setActiveSub(e);
|
||||
if (prevSub !== undefined) {
|
||||
link(e, prevSub, 0);
|
||||
prevSub.flags |= HasChildEffect;
|
||||
}
|
||||
try {
|
||||
++runDepth;
|
||||
e.cleanup = e.fn();
|
||||
}
|
||||
finally {
|
||||
--runDepth;
|
||||
activeSub = prevSub;
|
||||
e.flags &= ~4;
|
||||
}
|
||||
return effectOper.bind(e);
|
||||
}
|
||||
function effectScope(fn) {
|
||||
const e = {
|
||||
deps: undefined,
|
||||
depsTail: undefined,
|
||||
subs: undefined,
|
||||
subsTail: undefined,
|
||||
flags: 1,
|
||||
};
|
||||
const prevSub = setActiveSub(e);
|
||||
if (prevSub !== undefined) {
|
||||
link(e, prevSub, 0);
|
||||
prevSub.flags |= HasChildEffect;
|
||||
}
|
||||
try {
|
||||
fn();
|
||||
}
|
||||
finally {
|
||||
activeSub = prevSub;
|
||||
}
|
||||
return effectScopeOper.bind(e);
|
||||
}
|
||||
function trigger(fn) {
|
||||
const sub = {
|
||||
deps: undefined,
|
||||
depsTail: undefined,
|
||||
flags: 2,
|
||||
};
|
||||
const prevSub = setActiveSub(sub);
|
||||
try {
|
||||
fn();
|
||||
}
|
||||
finally {
|
||||
activeSub = prevSub;
|
||||
sub.flags = 0;
|
||||
let link = sub.deps;
|
||||
while (link !== undefined) {
|
||||
const dep = link.dep;
|
||||
link = unlink(link, sub);
|
||||
const subs = dep.subs;
|
||||
if (subs !== undefined) {
|
||||
propagate(subs, !!runDepth);
|
||||
shallowPropagate(subs);
|
||||
}
|
||||
}
|
||||
if (!batchDepth) {
|
||||
flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
function updateComputed(c) {
|
||||
if (c.flags & HasChildEffect) {
|
||||
let link = c.depsTail;
|
||||
while (link !== undefined) {
|
||||
const prev = link.prevDep;
|
||||
const dep = link.dep;
|
||||
if (!('getter' in dep) && !('currentValue' in dep)) {
|
||||
unlink(link, c);
|
||||
}
|
||||
link = prev;
|
||||
}
|
||||
}
|
||||
c.depsTail = undefined;
|
||||
c.flags = 1 | 4;
|
||||
const prevSub = setActiveSub(c);
|
||||
try {
|
||||
++cycle;
|
||||
const oldValue = c.value;
|
||||
return oldValue !== (c.value = c.getter(oldValue));
|
||||
}
|
||||
finally {
|
||||
activeSub = prevSub;
|
||||
c.flags &= ~4;
|
||||
purgeDeps(c);
|
||||
}
|
||||
}
|
||||
function updateSignal(s) {
|
||||
s.flags = 1;
|
||||
return s.currentValue !== (s.currentValue = s.pendingValue);
|
||||
}
|
||||
function run(e) {
|
||||
const flags = e.flags;
|
||||
if (flags & 16
|
||||
|| (flags & 32
|
||||
&& checkDirty(e.deps, e))) {
|
||||
if (flags & HasChildEffect) {
|
||||
let link = e.depsTail;
|
||||
while (link !== undefined) {
|
||||
const prev = link.prevDep;
|
||||
const dep = link.dep;
|
||||
if (!('getter' in dep) && !('currentValue' in dep)) {
|
||||
unlink(link, e);
|
||||
}
|
||||
link = prev;
|
||||
}
|
||||
}
|
||||
if (e.cleanup) {
|
||||
runCleanup(e);
|
||||
if (!e.flags) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
e.depsTail = undefined;
|
||||
e.flags = 2 | 4;
|
||||
const prevSub = setActiveSub(e);
|
||||
try {
|
||||
++cycle;
|
||||
++runDepth;
|
||||
e.cleanup = e.fn();
|
||||
}
|
||||
finally {
|
||||
--runDepth;
|
||||
activeSub = prevSub;
|
||||
e.flags &= ~4;
|
||||
purgeDeps(e);
|
||||
}
|
||||
}
|
||||
else if (e.deps !== undefined) {
|
||||
e.flags = 2 | (flags & HasChildEffect);
|
||||
}
|
||||
}
|
||||
function flush() {
|
||||
try {
|
||||
while (notifyIndex < queuedLength) {
|
||||
const effect = queued[notifyIndex];
|
||||
queued[notifyIndex++] = undefined;
|
||||
run(effect);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
while (notifyIndex < queuedLength) {
|
||||
const effect = queued[notifyIndex];
|
||||
queued[notifyIndex++] = undefined;
|
||||
effect.flags |= 2 | 8;
|
||||
}
|
||||
notifyIndex = 0;
|
||||
queuedLength = 0;
|
||||
}
|
||||
}
|
||||
function computedOper() {
|
||||
const flags = this.flags;
|
||||
if (flags & 16
|
||||
|| (flags & 32
|
||||
&& (checkDirty(this.deps, this)
|
||||
|| (this.flags = flags & ~32, false)))) {
|
||||
if (updateComputed(this)) {
|
||||
const subs = this.subs;
|
||||
if (subs !== undefined) {
|
||||
shallowPropagate(subs);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!flags) {
|
||||
this.flags = 1 | 4;
|
||||
const prevSub = setActiveSub(this);
|
||||
try {
|
||||
this.value = this.getter();
|
||||
}
|
||||
finally {
|
||||
activeSub = prevSub;
|
||||
this.flags &= ~4;
|
||||
}
|
||||
}
|
||||
const sub = activeSub;
|
||||
if (sub !== undefined) {
|
||||
link(this, sub, cycle);
|
||||
}
|
||||
return this.value;
|
||||
}
|
||||
function signalOper(...value) {
|
||||
if (value.length) {
|
||||
if (this.pendingValue !== (this.pendingValue = value[0])) {
|
||||
this.flags = 1 | 16;
|
||||
const subs = this.subs;
|
||||
if (subs !== undefined) {
|
||||
propagate(subs, !!runDepth);
|
||||
if (!batchDepth) {
|
||||
flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (this.flags & 16) {
|
||||
if (updateSignal(this)) {
|
||||
const subs = this.subs;
|
||||
if (subs !== undefined) {
|
||||
shallowPropagate(subs);
|
||||
}
|
||||
}
|
||||
}
|
||||
const sub = activeSub;
|
||||
if (sub !== undefined) {
|
||||
link(this, sub, cycle);
|
||||
}
|
||||
return this.currentValue;
|
||||
}
|
||||
}
|
||||
function runCleanup(e) {
|
||||
const cleanup = e.cleanup;
|
||||
e.cleanup = undefined;
|
||||
const prevSub = activeSub;
|
||||
activeSub = undefined;
|
||||
try {
|
||||
cleanup();
|
||||
}
|
||||
finally {
|
||||
activeSub = prevSub;
|
||||
}
|
||||
}
|
||||
function effectOper() {
|
||||
effectScopeOper.call(this);
|
||||
if (this.cleanup) {
|
||||
runCleanup(this);
|
||||
}
|
||||
}
|
||||
function effectScopeOper() {
|
||||
this.flags = 0;
|
||||
disposeAllDepsInReverse(this);
|
||||
const sub = this.subs;
|
||||
if (sub !== undefined) {
|
||||
unlink(sub);
|
||||
}
|
||||
}
|
||||
function disposeAllDepsInReverse(sub) {
|
||||
let link = sub.depsTail;
|
||||
while (link !== undefined) {
|
||||
const prev = link.prevDep;
|
||||
unlink(link, sub);
|
||||
link = prev;
|
||||
}
|
||||
}
|
||||
function purgeDeps(sub) {
|
||||
const depsTail = sub.depsTail;
|
||||
let dep = depsTail !== undefined ? depsTail.nextDep : sub.deps;
|
||||
while (dep !== undefined) {
|
||||
dep = unlink(dep, sub);
|
||||
}
|
||||
}
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ReactiveFlags = void 0;
|
||||
exports.createReactiveSystem = createReactiveSystem;
|
||||
exports.ReactiveFlags = {
|
||||
None: 0,
|
||||
Mutable: 1,
|
||||
Watching: 2,
|
||||
RecursedCheck: 4,
|
||||
Recursed: 8,
|
||||
Dirty: 16,
|
||||
Pending: 32,
|
||||
};
|
||||
function createReactiveSystem({ update, notify, unwatched, }) {
|
||||
return {
|
||||
link,
|
||||
unlink,
|
||||
propagate,
|
||||
checkDirty,
|
||||
shallowPropagate,
|
||||
};
|
||||
function link(dep, sub, version) {
|
||||
const prevDep = sub.depsTail;
|
||||
if (prevDep !== undefined && prevDep.dep === dep) {
|
||||
return;
|
||||
}
|
||||
const nextDep = prevDep !== undefined ? prevDep.nextDep : sub.deps;
|
||||
if (nextDep !== undefined && nextDep.dep === dep) {
|
||||
nextDep.version = version;
|
||||
sub.depsTail = nextDep;
|
||||
return;
|
||||
}
|
||||
const prevSub = dep.subsTail;
|
||||
if (prevSub !== undefined && prevSub.version === version && prevSub.sub === sub) {
|
||||
return;
|
||||
}
|
||||
const newLink = sub.depsTail
|
||||
= dep.subsTail
|
||||
= {
|
||||
version,
|
||||
dep,
|
||||
sub,
|
||||
prevDep,
|
||||
nextDep,
|
||||
prevSub,
|
||||
nextSub: undefined,
|
||||
};
|
||||
if (nextDep !== undefined) {
|
||||
nextDep.prevDep = newLink;
|
||||
}
|
||||
if (prevDep !== undefined) {
|
||||
prevDep.nextDep = newLink;
|
||||
}
|
||||
else {
|
||||
sub.deps = newLink;
|
||||
}
|
||||
if (prevSub !== undefined) {
|
||||
prevSub.nextSub = newLink;
|
||||
}
|
||||
else {
|
||||
dep.subs = newLink;
|
||||
}
|
||||
}
|
||||
function unlink(link, sub = link.sub) {
|
||||
const { dep, prevDep, nextDep, nextSub, prevSub } = link;
|
||||
if (nextDep !== undefined) {
|
||||
nextDep.prevDep = prevDep;
|
||||
}
|
||||
else {
|
||||
sub.depsTail = prevDep;
|
||||
}
|
||||
if (prevDep !== undefined) {
|
||||
prevDep.nextDep = nextDep;
|
||||
}
|
||||
else {
|
||||
sub.deps = nextDep;
|
||||
}
|
||||
if (nextSub !== undefined) {
|
||||
nextSub.prevSub = prevSub;
|
||||
}
|
||||
else {
|
||||
dep.subsTail = prevSub;
|
||||
}
|
||||
if (prevSub !== undefined) {
|
||||
prevSub.nextSub = nextSub;
|
||||
}
|
||||
else if ((dep.subs = nextSub) === undefined) {
|
||||
unwatched(dep);
|
||||
}
|
||||
return nextDep;
|
||||
}
|
||||
function propagate(link, innerWrite) {
|
||||
let next = link.nextSub;
|
||||
let stack;
|
||||
top: do {
|
||||
const sub = link.sub;
|
||||
let flags = sub.flags;
|
||||
if (!(flags & (4 | 8 | 16 | 32))) {
|
||||
sub.flags = flags | 32;
|
||||
if (innerWrite) {
|
||||
sub.flags |= 8;
|
||||
}
|
||||
}
|
||||
else if (!(flags & (4 | 8))) {
|
||||
flags = 0;
|
||||
}
|
||||
else if (!(flags & 4)) {
|
||||
sub.flags = (flags & ~8) | 32;
|
||||
}
|
||||
else if (!(flags & (16 | 32)) && isValidLink(link, sub)) {
|
||||
sub.flags = flags | (8 | 32);
|
||||
flags &= 1;
|
||||
}
|
||||
else {
|
||||
flags = 0;
|
||||
}
|
||||
if (flags & 2) {
|
||||
notify(sub);
|
||||
}
|
||||
if (flags & 1) {
|
||||
const subSubs = sub.subs;
|
||||
if (subSubs !== undefined) {
|
||||
const nextSub = (link = subSubs).nextSub;
|
||||
if (nextSub !== undefined) {
|
||||
stack = { value: next, prev: stack };
|
||||
next = nextSub;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((link = next) !== undefined) {
|
||||
next = link.nextSub;
|
||||
continue;
|
||||
}
|
||||
while (stack !== undefined) {
|
||||
link = stack.value;
|
||||
stack = stack.prev;
|
||||
if (link !== undefined) {
|
||||
next = link.nextSub;
|
||||
continue top;
|
||||
}
|
||||
}
|
||||
break;
|
||||
} while (true);
|
||||
}
|
||||
function checkDirty(link, sub) {
|
||||
let stack;
|
||||
let checkDepth = 0;
|
||||
let dirty = false;
|
||||
top: do {
|
||||
const dep = link.dep;
|
||||
const flags = dep.flags;
|
||||
if (sub.flags & 16) {
|
||||
dirty = true;
|
||||
}
|
||||
else if ((flags & (1 | 16)) === (1 | 16)) {
|
||||
const subs = dep.subs;
|
||||
if (update(dep)) {
|
||||
if (subs.nextSub !== undefined) {
|
||||
shallowPropagate(subs);
|
||||
}
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
else if ((flags & (1 | 32)) === (1 | 32)) {
|
||||
stack = { value: link, prev: stack };
|
||||
link = dep.deps;
|
||||
sub = dep;
|
||||
++checkDepth;
|
||||
continue;
|
||||
}
|
||||
if (!dirty) {
|
||||
const nextDep = link.nextDep;
|
||||
if (nextDep !== undefined) {
|
||||
link = nextDep;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
while (checkDepth--) {
|
||||
link = stack.value;
|
||||
stack = stack.prev;
|
||||
if (dirty) {
|
||||
const subs = sub.subs;
|
||||
if (update(sub)) {
|
||||
if (subs.nextSub !== undefined) {
|
||||
shallowPropagate(subs);
|
||||
}
|
||||
sub = link.sub;
|
||||
continue;
|
||||
}
|
||||
dirty = false;
|
||||
}
|
||||
else {
|
||||
sub.flags &= ~32;
|
||||
}
|
||||
sub = link.sub;
|
||||
const nextDep = link.nextDep;
|
||||
if (nextDep !== undefined) {
|
||||
link = nextDep;
|
||||
continue top;
|
||||
}
|
||||
}
|
||||
return dirty && !!sub.flags;
|
||||
} while (true);
|
||||
}
|
||||
function shallowPropagate(link) {
|
||||
do {
|
||||
const sub = link.sub;
|
||||
const flags = sub.flags;
|
||||
if ((flags & (32 | 16)) === 32) {
|
||||
sub.flags = flags | 16;
|
||||
if ((flags & (2 | 4)) === 2) {
|
||||
notify(sub);
|
||||
}
|
||||
}
|
||||
} while ((link = link.nextSub) !== undefined);
|
||||
}
|
||||
function isValidLink(checkLink, sub) {
|
||||
let link = sub.depsTail;
|
||||
while (link !== undefined) {
|
||||
if (link === checkLink) {
|
||||
return true;
|
||||
}
|
||||
link = link.prevDep;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+370
@@ -0,0 +1,370 @@
|
||||
import { createReactiveSystem } from './system.mjs';
|
||||
const HasChildEffect = 64;
|
||||
let cycle = 0;
|
||||
let runDepth = 0;
|
||||
let batchDepth = 0;
|
||||
let notifyIndex = 0;
|
||||
let queuedLength = 0;
|
||||
let activeSub;
|
||||
const queued = [];
|
||||
const { link, unlink, propagate, checkDirty, shallowPropagate, } = createReactiveSystem({
|
||||
update(node) {
|
||||
if ('getter' in node) {
|
||||
return updateComputed(node);
|
||||
}
|
||||
if ('currentValue' in node) {
|
||||
return updateSignal(node);
|
||||
}
|
||||
node.flags = 1;
|
||||
return true;
|
||||
},
|
||||
notify(effect) {
|
||||
let insertIndex = queuedLength;
|
||||
let firstInsertedIndex = insertIndex;
|
||||
do {
|
||||
queued[insertIndex++] = effect;
|
||||
effect.flags &= ~2;
|
||||
effect = effect.subs?.sub;
|
||||
if (effect === undefined || !(effect.flags & 2)) {
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
queuedLength = insertIndex;
|
||||
while (firstInsertedIndex < --insertIndex) {
|
||||
const left = queued[firstInsertedIndex];
|
||||
queued[firstInsertedIndex++] = queued[insertIndex];
|
||||
queued[insertIndex] = left;
|
||||
}
|
||||
},
|
||||
unwatched(node) {
|
||||
if ('getter' in node) {
|
||||
if (node.depsTail !== undefined) {
|
||||
node.flags = 1 | 16;
|
||||
disposeAllDepsInReverse(node);
|
||||
}
|
||||
}
|
||||
else if ('currentValue' in node) {
|
||||
}
|
||||
else if ('fn' in node) {
|
||||
effectOper.call(node);
|
||||
}
|
||||
else {
|
||||
effectScopeOper.call(node);
|
||||
}
|
||||
},
|
||||
});
|
||||
export function getActiveSub() {
|
||||
return activeSub;
|
||||
}
|
||||
export function setActiveSub(sub) {
|
||||
const prevSub = activeSub;
|
||||
activeSub = sub;
|
||||
return prevSub;
|
||||
}
|
||||
export function getBatchDepth() {
|
||||
return batchDepth;
|
||||
}
|
||||
export function startBatch() {
|
||||
++batchDepth;
|
||||
}
|
||||
export function endBatch() {
|
||||
if (!--batchDepth) {
|
||||
flush();
|
||||
}
|
||||
}
|
||||
export function isSignal(fn) {
|
||||
return fn.name === 'bound ' + signalOper.name;
|
||||
}
|
||||
export function isComputed(fn) {
|
||||
return fn.name === 'bound ' + computedOper.name;
|
||||
}
|
||||
export function isEffect(fn) {
|
||||
return fn.name === 'bound ' + effectOper.name;
|
||||
}
|
||||
export function isEffectScope(fn) {
|
||||
return fn.name === 'bound ' + effectScopeOper.name;
|
||||
}
|
||||
export function signal(initialValue) {
|
||||
return signalOper.bind({
|
||||
currentValue: initialValue,
|
||||
pendingValue: initialValue,
|
||||
subs: undefined,
|
||||
subsTail: undefined,
|
||||
flags: 1,
|
||||
});
|
||||
}
|
||||
export function computed(getter) {
|
||||
return computedOper.bind({
|
||||
value: undefined,
|
||||
subs: undefined,
|
||||
subsTail: undefined,
|
||||
deps: undefined,
|
||||
depsTail: undefined,
|
||||
flags: 0,
|
||||
getter: getter,
|
||||
});
|
||||
}
|
||||
export function effect(fn) {
|
||||
const e = {
|
||||
fn,
|
||||
cleanup: undefined,
|
||||
subs: undefined,
|
||||
subsTail: undefined,
|
||||
deps: undefined,
|
||||
depsTail: undefined,
|
||||
flags: 2 | 4,
|
||||
};
|
||||
const prevSub = setActiveSub(e);
|
||||
if (prevSub !== undefined) {
|
||||
link(e, prevSub, 0);
|
||||
prevSub.flags |= HasChildEffect;
|
||||
}
|
||||
try {
|
||||
++runDepth;
|
||||
e.cleanup = e.fn();
|
||||
}
|
||||
finally {
|
||||
--runDepth;
|
||||
activeSub = prevSub;
|
||||
e.flags &= ~4;
|
||||
}
|
||||
return effectOper.bind(e);
|
||||
}
|
||||
export function effectScope(fn) {
|
||||
const e = {
|
||||
deps: undefined,
|
||||
depsTail: undefined,
|
||||
subs: undefined,
|
||||
subsTail: undefined,
|
||||
flags: 1,
|
||||
};
|
||||
const prevSub = setActiveSub(e);
|
||||
if (prevSub !== undefined) {
|
||||
link(e, prevSub, 0);
|
||||
prevSub.flags |= HasChildEffect;
|
||||
}
|
||||
try {
|
||||
fn();
|
||||
}
|
||||
finally {
|
||||
activeSub = prevSub;
|
||||
}
|
||||
return effectScopeOper.bind(e);
|
||||
}
|
||||
export function trigger(fn) {
|
||||
const sub = {
|
||||
deps: undefined,
|
||||
depsTail: undefined,
|
||||
flags: 2,
|
||||
};
|
||||
const prevSub = setActiveSub(sub);
|
||||
try {
|
||||
fn();
|
||||
}
|
||||
finally {
|
||||
activeSub = prevSub;
|
||||
sub.flags = 0;
|
||||
let link = sub.deps;
|
||||
while (link !== undefined) {
|
||||
const dep = link.dep;
|
||||
link = unlink(link, sub);
|
||||
const subs = dep.subs;
|
||||
if (subs !== undefined) {
|
||||
propagate(subs, !!runDepth);
|
||||
shallowPropagate(subs);
|
||||
}
|
||||
}
|
||||
if (!batchDepth) {
|
||||
flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
function updateComputed(c) {
|
||||
if (c.flags & HasChildEffect) {
|
||||
let link = c.depsTail;
|
||||
while (link !== undefined) {
|
||||
const prev = link.prevDep;
|
||||
const dep = link.dep;
|
||||
if (!('getter' in dep) && !('currentValue' in dep)) {
|
||||
unlink(link, c);
|
||||
}
|
||||
link = prev;
|
||||
}
|
||||
}
|
||||
c.depsTail = undefined;
|
||||
c.flags = 1 | 4;
|
||||
const prevSub = setActiveSub(c);
|
||||
try {
|
||||
++cycle;
|
||||
const oldValue = c.value;
|
||||
return oldValue !== (c.value = c.getter(oldValue));
|
||||
}
|
||||
finally {
|
||||
activeSub = prevSub;
|
||||
c.flags &= ~4;
|
||||
purgeDeps(c);
|
||||
}
|
||||
}
|
||||
function updateSignal(s) {
|
||||
s.flags = 1;
|
||||
return s.currentValue !== (s.currentValue = s.pendingValue);
|
||||
}
|
||||
function run(e) {
|
||||
const flags = e.flags;
|
||||
if (flags & 16
|
||||
|| (flags & 32
|
||||
&& checkDirty(e.deps, e))) {
|
||||
if (flags & HasChildEffect) {
|
||||
let link = e.depsTail;
|
||||
while (link !== undefined) {
|
||||
const prev = link.prevDep;
|
||||
const dep = link.dep;
|
||||
if (!('getter' in dep) && !('currentValue' in dep)) {
|
||||
unlink(link, e);
|
||||
}
|
||||
link = prev;
|
||||
}
|
||||
}
|
||||
if (e.cleanup) {
|
||||
runCleanup(e);
|
||||
if (!e.flags) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
e.depsTail = undefined;
|
||||
e.flags = 2 | 4;
|
||||
const prevSub = setActiveSub(e);
|
||||
try {
|
||||
++cycle;
|
||||
++runDepth;
|
||||
e.cleanup = e.fn();
|
||||
}
|
||||
finally {
|
||||
--runDepth;
|
||||
activeSub = prevSub;
|
||||
e.flags &= ~4;
|
||||
purgeDeps(e);
|
||||
}
|
||||
}
|
||||
else if (e.deps !== undefined) {
|
||||
e.flags = 2 | (flags & HasChildEffect);
|
||||
}
|
||||
}
|
||||
function flush() {
|
||||
try {
|
||||
while (notifyIndex < queuedLength) {
|
||||
const effect = queued[notifyIndex];
|
||||
queued[notifyIndex++] = undefined;
|
||||
run(effect);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
while (notifyIndex < queuedLength) {
|
||||
const effect = queued[notifyIndex];
|
||||
queued[notifyIndex++] = undefined;
|
||||
effect.flags |= 2 | 8;
|
||||
}
|
||||
notifyIndex = 0;
|
||||
queuedLength = 0;
|
||||
}
|
||||
}
|
||||
function computedOper() {
|
||||
const flags = this.flags;
|
||||
if (flags & 16
|
||||
|| (flags & 32
|
||||
&& (checkDirty(this.deps, this)
|
||||
|| (this.flags = flags & ~32, false)))) {
|
||||
if (updateComputed(this)) {
|
||||
const subs = this.subs;
|
||||
if (subs !== undefined) {
|
||||
shallowPropagate(subs);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!flags) {
|
||||
this.flags = 1 | 4;
|
||||
const prevSub = setActiveSub(this);
|
||||
try {
|
||||
this.value = this.getter();
|
||||
}
|
||||
finally {
|
||||
activeSub = prevSub;
|
||||
this.flags &= ~4;
|
||||
}
|
||||
}
|
||||
const sub = activeSub;
|
||||
if (sub !== undefined) {
|
||||
link(this, sub, cycle);
|
||||
}
|
||||
return this.value;
|
||||
}
|
||||
function signalOper(...value) {
|
||||
if (value.length) {
|
||||
if (this.pendingValue !== (this.pendingValue = value[0])) {
|
||||
this.flags = 1 | 16;
|
||||
const subs = this.subs;
|
||||
if (subs !== undefined) {
|
||||
propagate(subs, !!runDepth);
|
||||
if (!batchDepth) {
|
||||
flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (this.flags & 16) {
|
||||
if (updateSignal(this)) {
|
||||
const subs = this.subs;
|
||||
if (subs !== undefined) {
|
||||
shallowPropagate(subs);
|
||||
}
|
||||
}
|
||||
}
|
||||
const sub = activeSub;
|
||||
if (sub !== undefined) {
|
||||
link(this, sub, cycle);
|
||||
}
|
||||
return this.currentValue;
|
||||
}
|
||||
}
|
||||
function runCleanup(e) {
|
||||
const cleanup = e.cleanup;
|
||||
e.cleanup = undefined;
|
||||
const prevSub = activeSub;
|
||||
activeSub = undefined;
|
||||
try {
|
||||
cleanup();
|
||||
}
|
||||
finally {
|
||||
activeSub = prevSub;
|
||||
}
|
||||
}
|
||||
function effectOper() {
|
||||
effectScopeOper.call(this);
|
||||
if (this.cleanup) {
|
||||
runCleanup(this);
|
||||
}
|
||||
}
|
||||
function effectScopeOper() {
|
||||
this.flags = 0;
|
||||
disposeAllDepsInReverse(this);
|
||||
const sub = this.subs;
|
||||
if (sub !== undefined) {
|
||||
unlink(sub);
|
||||
}
|
||||
}
|
||||
function disposeAllDepsInReverse(sub) {
|
||||
let link = sub.depsTail;
|
||||
while (link !== undefined) {
|
||||
const prev = link.prevDep;
|
||||
unlink(link, sub);
|
||||
link = prev;
|
||||
}
|
||||
}
|
||||
function purgeDeps(sub) {
|
||||
const depsTail = sub.depsTail;
|
||||
let dep = depsTail !== undefined ? depsTail.nextDep : sub.deps;
|
||||
while (dep !== undefined) {
|
||||
dep = unlink(dep, sub);
|
||||
}
|
||||
}
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
export const ReactiveFlags = {
|
||||
None: 0,
|
||||
Mutable: 1,
|
||||
Watching: 2,
|
||||
RecursedCheck: 4,
|
||||
Recursed: 8,
|
||||
Dirty: 16,
|
||||
Pending: 32,
|
||||
};
|
||||
export function createReactiveSystem({ update, notify, unwatched, }) {
|
||||
return {
|
||||
link,
|
||||
unlink,
|
||||
propagate,
|
||||
checkDirty,
|
||||
shallowPropagate,
|
||||
};
|
||||
function link(dep, sub, version) {
|
||||
const prevDep = sub.depsTail;
|
||||
if (prevDep !== undefined && prevDep.dep === dep) {
|
||||
return;
|
||||
}
|
||||
const nextDep = prevDep !== undefined ? prevDep.nextDep : sub.deps;
|
||||
if (nextDep !== undefined && nextDep.dep === dep) {
|
||||
nextDep.version = version;
|
||||
sub.depsTail = nextDep;
|
||||
return;
|
||||
}
|
||||
const prevSub = dep.subsTail;
|
||||
if (prevSub !== undefined && prevSub.version === version && prevSub.sub === sub) {
|
||||
return;
|
||||
}
|
||||
const newLink = sub.depsTail
|
||||
= dep.subsTail
|
||||
= {
|
||||
version,
|
||||
dep,
|
||||
sub,
|
||||
prevDep,
|
||||
nextDep,
|
||||
prevSub,
|
||||
nextSub: undefined,
|
||||
};
|
||||
if (nextDep !== undefined) {
|
||||
nextDep.prevDep = newLink;
|
||||
}
|
||||
if (prevDep !== undefined) {
|
||||
prevDep.nextDep = newLink;
|
||||
}
|
||||
else {
|
||||
sub.deps = newLink;
|
||||
}
|
||||
if (prevSub !== undefined) {
|
||||
prevSub.nextSub = newLink;
|
||||
}
|
||||
else {
|
||||
dep.subs = newLink;
|
||||
}
|
||||
}
|
||||
function unlink(link, sub = link.sub) {
|
||||
const { dep, prevDep, nextDep, nextSub, prevSub } = link;
|
||||
if (nextDep !== undefined) {
|
||||
nextDep.prevDep = prevDep;
|
||||
}
|
||||
else {
|
||||
sub.depsTail = prevDep;
|
||||
}
|
||||
if (prevDep !== undefined) {
|
||||
prevDep.nextDep = nextDep;
|
||||
}
|
||||
else {
|
||||
sub.deps = nextDep;
|
||||
}
|
||||
if (nextSub !== undefined) {
|
||||
nextSub.prevSub = prevSub;
|
||||
}
|
||||
else {
|
||||
dep.subsTail = prevSub;
|
||||
}
|
||||
if (prevSub !== undefined) {
|
||||
prevSub.nextSub = nextSub;
|
||||
}
|
||||
else if ((dep.subs = nextSub) === undefined) {
|
||||
unwatched(dep);
|
||||
}
|
||||
return nextDep;
|
||||
}
|
||||
function propagate(link, innerWrite) {
|
||||
let next = link.nextSub;
|
||||
let stack;
|
||||
top: do {
|
||||
const sub = link.sub;
|
||||
let flags = sub.flags;
|
||||
if (!(flags & (4 | 8 | 16 | 32))) {
|
||||
sub.flags = flags | 32;
|
||||
if (innerWrite) {
|
||||
sub.flags |= 8;
|
||||
}
|
||||
}
|
||||
else if (!(flags & (4 | 8))) {
|
||||
flags = 0;
|
||||
}
|
||||
else if (!(flags & 4)) {
|
||||
sub.flags = (flags & ~8) | 32;
|
||||
}
|
||||
else if (!(flags & (16 | 32)) && isValidLink(link, sub)) {
|
||||
sub.flags = flags | (8 | 32);
|
||||
flags &= 1;
|
||||
}
|
||||
else {
|
||||
flags = 0;
|
||||
}
|
||||
if (flags & 2) {
|
||||
notify(sub);
|
||||
}
|
||||
if (flags & 1) {
|
||||
const subSubs = sub.subs;
|
||||
if (subSubs !== undefined) {
|
||||
const nextSub = (link = subSubs).nextSub;
|
||||
if (nextSub !== undefined) {
|
||||
stack = { value: next, prev: stack };
|
||||
next = nextSub;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((link = next) !== undefined) {
|
||||
next = link.nextSub;
|
||||
continue;
|
||||
}
|
||||
while (stack !== undefined) {
|
||||
link = stack.value;
|
||||
stack = stack.prev;
|
||||
if (link !== undefined) {
|
||||
next = link.nextSub;
|
||||
continue top;
|
||||
}
|
||||
}
|
||||
break;
|
||||
} while (true);
|
||||
}
|
||||
function checkDirty(link, sub) {
|
||||
let stack;
|
||||
let checkDepth = 0;
|
||||
let dirty = false;
|
||||
top: do {
|
||||
const dep = link.dep;
|
||||
const flags = dep.flags;
|
||||
if (sub.flags & 16) {
|
||||
dirty = true;
|
||||
}
|
||||
else if ((flags & (1 | 16)) === (1 | 16)) {
|
||||
const subs = dep.subs;
|
||||
if (update(dep)) {
|
||||
if (subs.nextSub !== undefined) {
|
||||
shallowPropagate(subs);
|
||||
}
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
else if ((flags & (1 | 32)) === (1 | 32)) {
|
||||
stack = { value: link, prev: stack };
|
||||
link = dep.deps;
|
||||
sub = dep;
|
||||
++checkDepth;
|
||||
continue;
|
||||
}
|
||||
if (!dirty) {
|
||||
const nextDep = link.nextDep;
|
||||
if (nextDep !== undefined) {
|
||||
link = nextDep;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
while (checkDepth--) {
|
||||
link = stack.value;
|
||||
stack = stack.prev;
|
||||
if (dirty) {
|
||||
const subs = sub.subs;
|
||||
if (update(sub)) {
|
||||
if (subs.nextSub !== undefined) {
|
||||
shallowPropagate(subs);
|
||||
}
|
||||
sub = link.sub;
|
||||
continue;
|
||||
}
|
||||
dirty = false;
|
||||
}
|
||||
else {
|
||||
sub.flags &= ~32;
|
||||
}
|
||||
sub = link.sub;
|
||||
const nextDep = link.nextDep;
|
||||
if (nextDep !== undefined) {
|
||||
link = nextDep;
|
||||
continue top;
|
||||
}
|
||||
}
|
||||
return dirty && !!sub.flags;
|
||||
} while (true);
|
||||
}
|
||||
function shallowPropagate(link) {
|
||||
do {
|
||||
const sub = link.sub;
|
||||
const flags = sub.flags;
|
||||
if ((flags & (32 | 16)) === 32) {
|
||||
sub.flags = flags | 16;
|
||||
if ((flags & (2 | 4)) === 2) {
|
||||
notify(sub);
|
||||
}
|
||||
}
|
||||
} while ((link = link.nextSub) !== undefined);
|
||||
}
|
||||
function isValidLink(checkLink, sub) {
|
||||
let link = sub.depsTail;
|
||||
while (link !== undefined) {
|
||||
if (link === checkLink) {
|
||||
return true;
|
||||
}
|
||||
link = link.prevDep;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"name": "alien-signals",
|
||||
"version": "3.2.1",
|
||||
"license": "MIT",
|
||||
"description": "The lightest signal library.",
|
||||
"packageManager": "pnpm@9.12.0",
|
||||
"types": "./types/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./types/index.d.ts",
|
||||
"import": "./esm/index.mjs",
|
||||
"require": "./cjs/index.cjs"
|
||||
},
|
||||
"./cjs": {
|
||||
"types": "./types/index.d.ts",
|
||||
"import": "./cjs/index.cjs",
|
||||
"require": "./cjs/index.cjs"
|
||||
},
|
||||
"./esm": {
|
||||
"types": "./types/index.d.ts",
|
||||
"import": "./esm/index.mjs",
|
||||
"require": "./esm/index.mjs"
|
||||
},
|
||||
"./system": {
|
||||
"types": "./types/system.d.ts",
|
||||
"import": "./esm/system.mjs",
|
||||
"require": "./cjs/system.cjs"
|
||||
},
|
||||
"./cjs/system": {
|
||||
"types": "./types/system.d.ts",
|
||||
"import": "./cjs/system.cjs",
|
||||
"require": "./cjs/system.cjs"
|
||||
},
|
||||
"./esm/system": {
|
||||
"types": "./types/system.d.ts",
|
||||
"import": "./esm/system.mjs",
|
||||
"require": "./esm/system.mjs"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"cjs/*.cjs",
|
||||
"esm/*.mjs",
|
||||
"types/*.d.ts"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/stackblitz/alien-signals.git"
|
||||
},
|
||||
"scripts": {
|
||||
"prepublishOnly": "npm run check && npm run test",
|
||||
"check": "tsslint --project tsconfig.json",
|
||||
"size": "node ./size.js",
|
||||
"build": "node ./build.js",
|
||||
"test": "npm run build && vitest run",
|
||||
"bench": "npm run build && node --jitless --expose-gc benchs/propagate.mjs",
|
||||
"memory": "npm run build && node --expose-gc benchs/memoryUsage.mjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsslint/cli": "latest",
|
||||
"@tsslint/config": "latest",
|
||||
"mitata": "latest",
|
||||
"reactive-framework-test-suite": "0.0.2",
|
||||
"rolldown": "latest",
|
||||
"typescript": "latest",
|
||||
"vitest": "latest"
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import { type ReactiveNode } from './system.js';
|
||||
export declare function getActiveSub(): ReactiveNode | undefined;
|
||||
export declare function setActiveSub(sub?: ReactiveNode): ReactiveNode | undefined;
|
||||
export declare function getBatchDepth(): number;
|
||||
export declare function startBatch(): void;
|
||||
export declare function endBatch(): void;
|
||||
export declare function isSignal(fn: () => void): boolean;
|
||||
export declare function isComputed(fn: () => void): boolean;
|
||||
export declare function isEffect(fn: () => void): boolean;
|
||||
export declare function isEffectScope(fn: () => void): boolean;
|
||||
export declare function signal<T>(): {
|
||||
(): T | undefined;
|
||||
(value: T | undefined): void;
|
||||
};
|
||||
export declare function signal<T>(initialValue: T): {
|
||||
(): T;
|
||||
(value: T): void;
|
||||
};
|
||||
export declare function computed<T>(getter: (previousValue?: T) => T): () => T;
|
||||
export declare function effect(fn: () => void | (() => void)): () => void;
|
||||
export declare function effectScope(fn: () => void): () => void;
|
||||
export declare function trigger(fn: () => void): void;
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
export interface ReactiveNode {
|
||||
deps?: Link;
|
||||
depsTail?: Link;
|
||||
subs?: Link;
|
||||
subsTail?: Link;
|
||||
flags: ReactiveFlags;
|
||||
}
|
||||
export interface Link {
|
||||
version: number;
|
||||
dep: ReactiveNode;
|
||||
sub: ReactiveNode;
|
||||
prevSub: Link | undefined;
|
||||
nextSub: Link | undefined;
|
||||
prevDep: Link | undefined;
|
||||
nextDep: Link | undefined;
|
||||
}
|
||||
export declare const enum ReactiveFlags {
|
||||
None = 0,
|
||||
Mutable = 1,
|
||||
Watching = 2,
|
||||
RecursedCheck = 4,
|
||||
Recursed = 8,
|
||||
Dirty = 16,
|
||||
Pending = 32
|
||||
}
|
||||
export declare function createReactiveSystem({ update, notify, unwatched, }: {
|
||||
update(sub: ReactiveNode): boolean;
|
||||
notify(sub: ReactiveNode): void;
|
||||
unwatched(sub: ReactiveNode): void;
|
||||
}): {
|
||||
link: (dep: ReactiveNode, sub: ReactiveNode, version: number) => void;
|
||||
unlink: (link: Link, sub?: ReactiveNode) => Link | undefined;
|
||||
propagate: (link: Link, innerWrite: boolean) => void;
|
||||
checkDirty: (link: Link, sub: ReactiveNode) => boolean;
|
||||
shallowPropagate: (link: Link) => void;
|
||||
};
|
||||
Reference in New Issue
Block a user