first commit
This commit is contained in:
21
web-app/node_modules/alien-signals/LICENSE
generated
vendored
Normal file
21
web-app/node_modules/alien-signals/LICENSE
generated
vendored
Normal file
@@ -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.
|
||||
196
web-app/node_modules/alien-signals/README.md
generated
vendored
Normal file
196
web-app/node_modules/alien-signals/README.md
generated
vendored
Normal file
@@ -0,0 +1,196 @@
|
||||
<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>
|
||||
</p>
|
||||
|
||||
# alien-signals
|
||||
|
||||
This project explores a push-pull based signal algorithm. Its current implementation is similar to or related to certain other 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) to ensure performance. We found that under these conditions, maintaining algorithmic simplicity offers more significant improvements than complex scheduling strategies.
|
||||
|
||||
Even though Vue 3.4 is already optimized, alien-signals is still noticeably faster. (I wrote code for both, and since they share similar algorithms, they’re quite comparable.)
|
||||
|
||||
<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. Our end goal is to implement fully incremental AST parsing and virtual code generation in Vue language tools, based on alien-signals.
|
||||
|
||||
## Derived Projects
|
||||
|
||||
- [YanqingXu/alien-signals-in-lua](https://github.com/YanqingXu/alien-signals-in-lua): Lua implementation of alien-signals
|
||||
- [medz/alien-signals-dart](https://github.com/medz/alien-signals-dart): Dart implementation of alien-signals
|
||||
- [delaneyj/alien-signals-go](https://github.com/delaneyj/alien-signals-go): Go implementation of alien-signals
|
||||
- [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
|
||||
|
||||
## Adoption
|
||||
|
||||
- [vuejs/core](https://github.com/vuejs/core): The core algorithm has been ported to 3.6 or higher (PR:https://github.com/vuejs/core/pull/12349)
|
||||
- [vuejs/language-tools](https://github.com/vuejs/language-tools): Used in the language-core package for virtual code generation
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
#### 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
|
||||
|
||||
In order to eliminate recursive calls and improve performance, we record the last link node of the previous loop in `propagate` and `checkDirty` functions, and implement the rollback logic to return to this node.
|
||||
|
||||
This results in code that is difficult to understand, and you don't necessarily get the same performance improvements in other languages, so we record the original implementation without eliminating recursive calls here for reference.
|
||||
|
||||
#### `propagate`
|
||||
|
||||
```ts
|
||||
function propagate(link: Link, targetFlag = SubscriberFlags.Dirty): void {
|
||||
do {
|
||||
const sub = link.sub;
|
||||
const subFlags = sub.flags;
|
||||
|
||||
let shouldNotify = false;
|
||||
|
||||
if (!(subFlags & (SubscriberFlags.Tracking | SubscriberFlags.Recursed | SubscriberFlags.Propagated))) {
|
||||
sub.flags = subFlags | targetFlag | SubscriberFlags.Notified;
|
||||
shouldNotify = true;
|
||||
} else if ((subFlags & SubscriberFlags.Recursed) && !(subFlags & SubscriberFlags.Tracking)) {
|
||||
sub.flags = (subFlags & ~SubscriberFlags.Recursed) | targetFlag | SubscriberFlags.Notified;
|
||||
shouldNotify = true;
|
||||
} else if (!(subFlags & SubscriberFlags.Propagated) && isValidLink(current, sub)) {
|
||||
sub.flags = subFlags | SubscriberFlags.Recursed | targetFlag | SubscriberFlags.Notified;
|
||||
shouldNotify = (sub as Dependency).subs !== undefined;
|
||||
}
|
||||
|
||||
if (shouldNotify) {
|
||||
const subSubs = (sub as Dependency).subs;
|
||||
if (subSubs !== undefined) {
|
||||
propagate(
|
||||
subSubs,
|
||||
subFlags & SubscriberFlags.Effect
|
||||
? SubscriberFlags.PendingEffect
|
||||
: SubscriberFlags.PendingComputed
|
||||
);
|
||||
}
|
||||
if (subFlags & SubscriberFlags.Effect) {
|
||||
if (queuedEffectsTail !== undefined) {
|
||||
queuedEffectsTail = queuedEffectsTail.linked = { target: sub, linked: undefined };
|
||||
} else {
|
||||
queuedEffectsTail = queuedEffects = { target: sub, linked: undefined };
|
||||
}
|
||||
}
|
||||
} else if (!(subFlags & (SubscriberFlags.Tracking | targetFlag))) {
|
||||
sub.flags = subFlags | targetFlag | SubscriberFlags.Notified;
|
||||
if ((subFlags & (SubscriberFlags.Effect | SubscriberFlags.Notified)) === SubscriberFlags.Effect) {
|
||||
if (queuedEffectsTail !== undefined) {
|
||||
queuedEffectsTail = queuedEffectsTail.linked = { target: sub, linked: undefined };
|
||||
} else {
|
||||
queuedEffectsTail = queuedEffects = { target: sub, linked: undefined };
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
!(subFlags & targetFlag)
|
||||
&& (subFlags & SubscriberFlags.Propagated)
|
||||
&& isValidLink(link, sub)
|
||||
) {
|
||||
sub.flags = subFlags | targetFlag;
|
||||
}
|
||||
|
||||
link = link.nextSub!;
|
||||
} while (link !== undefined);
|
||||
}
|
||||
```
|
||||
|
||||
#### `checkDirty`
|
||||
|
||||
```ts
|
||||
function checkDirty(link: Link): boolean {
|
||||
do {
|
||||
const dep = link.dep;
|
||||
if ('flags' in dep) {
|
||||
const depFlags = dep.flags;
|
||||
if ((depFlags & (SubscriberFlags.Computed | SubscriberFlags.Dirty)) === (SubscriberFlags.Computed | SubscriberFlags.Dirty)) {
|
||||
if (updateComputed(dep)) {
|
||||
const subs = dep.subs!;
|
||||
if (subs.nextSub !== undefined) {
|
||||
shallowPropagate(subs);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else if ((depFlags & (SubscriberFlags.Computed | SubscriberFlags.PendingComputed)) === (SubscriberFlags.Computed | SubscriberFlags.PendingComputed)) {
|
||||
if (checkDirty(dep.deps!)) {
|
||||
if (updateComputed(dep)) {
|
||||
const subs = dep.subs!;
|
||||
if (subs.nextSub !== undefined) {
|
||||
shallowPropagate(subs);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
dep.flags = depFlags & ~SubscriberFlags.PendingComputed;
|
||||
}
|
||||
}
|
||||
}
|
||||
link = link.nextDep!;
|
||||
} while (link !== undefined);
|
||||
|
||||
return false;
|
||||
}
|
||||
```
|
||||
1
web-app/node_modules/alien-signals/cjs/index.cjs
generated
vendored
Normal file
1
web-app/node_modules/alien-signals/cjs/index.cjs
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
1
web-app/node_modules/alien-signals/esm/index.mjs
generated
vendored
Normal file
1
web-app/node_modules/alien-signals/esm/index.mjs
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
54
web-app/node_modules/alien-signals/package.json
generated
vendored
Normal file
54
web-app/node_modules/alien-signals/package.json
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"name": "alien-signals",
|
||||
"version": "1.0.13",
|
||||
"sideEffects": false,
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"cjs/index.cjs",
|
||||
"esm/index.mjs",
|
||||
"types/*.d.ts"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/johnsoncodehk/signals.git"
|
||||
},
|
||||
"scripts": {
|
||||
"prepublishOnly": "npm run build",
|
||||
"build": "tsc && npm run build:esm && npm run build:cjs",
|
||||
"build:esm": "esbuild src/index.ts --bundle --minify-whitespace --format=esm --outfile=esm/index.mjs",
|
||||
"build:cjs": "esbuild src/index.ts --bundle --minify-whitespace --format=cjs --outfile=cjs/index.cjs",
|
||||
"test": "vitest run",
|
||||
"lint": "tsslint --project tsconfig.json",
|
||||
"bench": "npm run build:esm && node --jitless --expose-gc benchs/propagate.mjs",
|
||||
"bench:memory": "npm run build:esm && node --expose-gc benchs/memoryUsage.mjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsslint/cli": "latest",
|
||||
"@tsslint/config": "latest",
|
||||
"esbuild": "latest",
|
||||
"mitata": "latest",
|
||||
"typescript": "latest",
|
||||
"vitest": "latest",
|
||||
"jest-extended": "latest"
|
||||
}
|
||||
}
|
||||
17
web-app/node_modules/alien-signals/types/index.d.ts
generated
vendored
Normal file
17
web-app/node_modules/alien-signals/types/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
export * from './system.js';
|
||||
export declare let batchDepth: number;
|
||||
export declare function startBatch(): void;
|
||||
export declare function endBatch(): void;
|
||||
export declare function pauseTracking(): void;
|
||||
export declare function resumeTracking(): void;
|
||||
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<T>(fn: () => T): () => void;
|
||||
export declare function effectScope<T>(fn: () => T): () => void;
|
||||
61
web-app/node_modules/alien-signals/types/system.d.ts
generated
vendored
Normal file
61
web-app/node_modules/alien-signals/types/system.d.ts
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
export interface Dependency {
|
||||
subs: Link | undefined;
|
||||
subsTail: Link | undefined;
|
||||
}
|
||||
export interface Subscriber {
|
||||
flags: SubscriberFlags;
|
||||
deps: Link | undefined;
|
||||
depsTail: Link | undefined;
|
||||
}
|
||||
export interface Link {
|
||||
dep: Dependency | (Dependency & Subscriber);
|
||||
sub: Subscriber | (Dependency & Subscriber);
|
||||
prevSub: Link | undefined;
|
||||
nextSub: Link | undefined;
|
||||
nextDep: Link | undefined;
|
||||
}
|
||||
export declare const enum SubscriberFlags {
|
||||
Computed = 1,
|
||||
Effect = 2,
|
||||
Tracking = 4,
|
||||
Notified = 8,
|
||||
Recursed = 16,
|
||||
Dirty = 32,
|
||||
PendingComputed = 64,
|
||||
PendingEffect = 128,
|
||||
Propagated = 224
|
||||
}
|
||||
export declare function createReactiveSystem({ updateComputed, notifyEffect, }: {
|
||||
/**
|
||||
* Updates the computed subscriber's value and returns whether it changed.
|
||||
*
|
||||
* This function should be called when a computed subscriber is marked as Dirty.
|
||||
* The computed subscriber's getter function is invoked, and its value is updated.
|
||||
* If the value changes, the new value is stored, and the function returns `true`.
|
||||
*
|
||||
* @param computed - The computed subscriber to update.
|
||||
* @returns `true` if the computed subscriber's value changed; otherwise `false`.
|
||||
*/
|
||||
updateComputed(computed: Dependency & Subscriber): boolean;
|
||||
/**
|
||||
* Handles effect notifications by processing the specified `effect`.
|
||||
*
|
||||
* When an `effect` first receives any of the following flags:
|
||||
* - `Dirty`
|
||||
* - `PendingComputed`
|
||||
* - `PendingEffect`
|
||||
* this method will process them and return `true` if the flags are successfully handled.
|
||||
* If not fully handled, future changes to these flags will trigger additional calls
|
||||
* until the method eventually returns `true`.
|
||||
*/
|
||||
notifyEffect(effect: Subscriber): boolean;
|
||||
}): {
|
||||
link: (dep: Dependency, sub: Subscriber) => Link | undefined;
|
||||
propagate: (current: Link) => void;
|
||||
updateDirtyFlag: (sub: Subscriber, flags: SubscriberFlags) => boolean;
|
||||
startTracking: (sub: Subscriber) => void;
|
||||
endTracking: (sub: Subscriber) => void;
|
||||
processEffectNotifications: () => void;
|
||||
processComputedUpdate: (computed: Dependency & Subscriber, flags: SubscriberFlags) => void;
|
||||
processPendingInnerEffects: (sub: Subscriber, flags: SubscriberFlags) => void;
|
||||
};
|
||||
Reference in New Issue
Block a user