Skip to content

Instantly share code, notes, and snippets.

@scil
Last active March 31, 2024 23:48
Show Gist options
  • Save scil/ec4438db24fbbbc642701aa95c76d631 to your computer and use it in GitHub Desktop.
Save scil/ec4438db24fbbbc642701aa95c76d631 to your computer and use it in GitHub Desktop.
the compile-free/bundless version of maverick-js/signals 5.11.4
// https://github.com/maverick-js/signals
var Signals = (function (exports) {
'use strict';
const SCOPE = Symbol(0);
let scheduledEffects = false, runningEffects = false, currentScope = null, currentObserver = null, currentObservers = null, currentObserversIndex = 0, effects = [], defaultContext = {};
const NOOP = () => {
}, STATE_CLEAN = 0, STATE_CHECK = 1, STATE_DIRTY = 2, STATE_DISPOSED = 3;
function flushEffects() {
scheduledEffects = true;
queueMicrotask(runEffects);
}
function runEffects() {
if (!effects.length) {
scheduledEffects = false;
return;
}
runningEffects = true;
for (let i = 0; i < effects.length; i++) {
if (effects[i].$st !== STATE_CLEAN)
runTop(effects[i]);
}
effects = [];
scheduledEffects = false;
runningEffects = false;
}
function runTop(node) {
let ancestors = [node];
while (node = node[SCOPE]) {
if (node.$e && node.$st !== STATE_CLEAN)
ancestors.push(node);
}
for (let i = ancestors.length - 1; i >= 0; i--) {
updateCheck(ancestors[i]);
}
}
function root(init) {
const scope = createScope();
return compute(scope, !init.length ? init : init.bind(null, dispose.bind(scope)), null);
}
function peek(fn) {
return compute(currentScope, fn, null);
}
function untrack(fn) {
return compute(null, fn, null);
}
function tick() {
if (!runningEffects)
runEffects();
}
function getScope() {
return currentScope;
}
function scoped(run, scope) {
try {
return compute(scope, run, null);
} catch (error) {
handleError(scope, error);
return;
}
}
function getContext(key, scope = currentScope) {
return scope?.$cx[key];
}
function setContext(key, value, scope = currentScope) {
if (scope)
scope.$cx = { ...scope.$cx, [key]: value };
}
function onError(handler) {
if (!currentScope)
return;
currentScope.$eh = currentScope.$eh ? [handler, ...currentScope.$eh] : [handler];
}
function onDispose(disposable) {
if (!disposable || !currentScope)
return disposable || NOOP;
const node = currentScope;
if (!node.$d) {
node.$d = disposable;
} else if (Array.isArray(node.$d)) {
node.$d.push(disposable);
} else {
node.$d = [node.$d, disposable];
}
return function removeDispose() {
if (node.$st === STATE_DISPOSED)
return;
disposable.call(null);
if (isFunction(node.$d)) {
node.$d = null;
} else if (Array.isArray(node.$d)) {
node.$d.splice(node.$d.indexOf(disposable), 1);
}
};
}
function dispose(self = true) {
if (this.$st === STATE_DISPOSED)
return;
let head = self ? this.$ps || this[SCOPE] : this, current = this.$ns, next = null;
while (current && current[SCOPE] === this) {
dispose.call(current, true);
disposeNode(current);
next = current.$ns;
current.$ns = null;
current = next;
}
if (self)
disposeNode(this);
if (current)
current.$ps = !self ? this : this.$ps;
if (head)
head.$ns = current;
}
function disposeNode(node) {
node.$st = STATE_DISPOSED;
if (node.$d)
emptyDisposal(node);
if (node.$s)
removeSourceObservers(node, 0);
if (node.$ps)
node.$ps.$ns = null;
node[SCOPE] = null;
node.$s = null;
node.$o = null;
node.$ps = null;
node.$cx = defaultContext;
node.$eh = null;
}
function emptyDisposal(scope) {
try {
if (Array.isArray(scope.$d)) {
for (let i = scope.$d.length - 1; i >= 0; i--) {
const callable = scope.$d[i];
callable.call(callable);
}
} else {
scope.$d.call(scope.$d);
}
scope.$d = null;
} catch (error) {
handleError(scope, error);
}
}
function compute(scope, compute2, observer) {
const prevScope = currentScope, prevObserver = currentObserver;
currentScope = scope;
currentObserver = observer;
try {
return compute2.call(scope);
} finally {
currentScope = prevScope;
currentObserver = prevObserver;
}
}
function handleError(scope, error) {
if (!scope || !scope.$eh)
throw error;
let i = 0, len = scope.$eh.length, coercedError = coerceError(error);
for (i = 0; i < len; i++) {
try {
scope.$eh[i](coercedError);
break;
} catch (error2) {
coercedError = coerceError(error2);
}
}
if (i === len)
throw coercedError;
}
function coerceError(error) {
return error instanceof Error ? error : Error(JSON.stringify(error));
}
function read() {
if (this.$st === STATE_DISPOSED)
return this.$v;
if (currentObserver && !this.$e) {
if (!currentObservers && currentObserver.$s && currentObserver.$s[currentObserversIndex] == this) {
currentObserversIndex++;
} else if (!currentObservers)
currentObservers = [this];
else
currentObservers.push(this);
}
if (this.$c)
updateCheck(this);
return this.$v;
}
function write(newValue) {
const value = isFunction(newValue) ? newValue(this.$v) : newValue;
if (this.$ch(this.$v, value)) {
this.$v = value;
if (this.$o) {
for (let i = 0; i < this.$o.length; i++) {
notify(this.$o[i], STATE_DIRTY);
}
}
}
return this.$v;
}
const ScopeNode = function Scope() {
this[SCOPE] = null;
this.$ns = null;
this.$ps = null;
if (currentScope)
currentScope.append(this);
};
const ScopeProto = ScopeNode.prototype;
ScopeProto.$cx = defaultContext;
ScopeProto.$eh = null;
ScopeProto.$c = null;
ScopeProto.$d = null;
ScopeProto.append = function(child) {
child[SCOPE] = this;
child.$ps = this;
if (this.$ns) {
if (child.$ns) {
let tail = child.$ns;
while (tail.$ns)
tail = tail.$ns;
tail.$ns = this.$ns;
this.$ns.$ps = tail;
} else {
child.$ns = this.$ns;
this.$ns.$ps = child;
}
}
this.$ns = child;
child.$cx = child.$cx === defaultContext ? this.$cx : { ...this.$cx, ...child.$cx };
if (this.$eh) {
child.$eh = !child.$eh ? this.$eh : [...child.$eh, ...this.$eh];
}
};
ScopeProto.dispose = function() {
dispose.call(this);
};
function createScope() {
return new ScopeNode();
}
const ComputeNode = function Computation(initialValue, compute2, options) {
ScopeNode.call(this);
this.$st = compute2 ? STATE_DIRTY : STATE_CLEAN;
this.$i = false;
this.$e = false;
this.$s = null;
this.$o = null;
this.$v = initialValue;
if (compute2)
this.$c = compute2;
if (options && options.dirty)
this.$ch = options.dirty;
};
const ComputeProto = ComputeNode.prototype;
Object.setPrototypeOf(ComputeProto, ScopeProto);
ComputeProto.$ch = isNotEqual;
ComputeProto.call = read;
function createComputation(initialValue, compute2, options) {
return new ComputeNode(initialValue, compute2, options);
}
function isNotEqual(a, b) {
return a !== b;
}
function isFunction(value) {
return typeof value === "function";
}
function updateCheck(node) {
if (node.$st === STATE_CHECK) {
for (let i = 0; i < node.$s.length; i++) {
updateCheck(node.$s[i]);
if (node.$st === STATE_DIRTY) {
break;
}
}
}
if (node.$st === STATE_DIRTY)
update(node);
else
node.$st = STATE_CLEAN;
}
function cleanup(node) {
if (node.$ns && node.$ns[SCOPE] === node)
dispose.call(node, false);
if (node.$d)
emptyDisposal(node);
node.$eh = node[SCOPE] ? node[SCOPE].$eh : null;
}
function update(node) {
let prevObservers = currentObservers, prevObserversIndex = currentObserversIndex;
currentObservers = null;
currentObserversIndex = 0;
try {
cleanup(node);
const result = compute(node, node.$c, node);
if (currentObservers) {
if (node.$s)
removeSourceObservers(node, currentObserversIndex);
if (node.$s && currentObserversIndex > 0) {
node.$s.length = currentObserversIndex + currentObservers.length;
for (let i = 0; i < currentObservers.length; i++) {
node.$s[currentObserversIndex + i] = currentObservers[i];
}
} else {
node.$s = currentObservers;
}
let source;
for (let i = currentObserversIndex; i < node.$s.length; i++) {
source = node.$s[i];
if (!source.$o)
source.$o = [node];
else
source.$o.push(node);
}
} else if (node.$s && currentObserversIndex < node.$s.length) {
removeSourceObservers(node, currentObserversIndex);
node.$s.length = currentObserversIndex;
}
if (!node.$e && node.$i) {
write.call(node, result);
} else {
node.$v = result;
node.$i = true;
}
} catch (error) {
handleError(node, error);
if (node.$st === STATE_DIRTY) {
cleanup(node);
if (node.$s)
removeSourceObservers(node, 0);
}
return;
}
currentObservers = prevObservers;
currentObserversIndex = prevObserversIndex;
node.$st = STATE_CLEAN;
}
function notify(node, state) {
if (node.$st >= state)
return;
if (node.$e && node.$st === STATE_CLEAN) {
effects.push(node);
if (!scheduledEffects)
flushEffects();
}
node.$st = state;
if (node.$o) {
for (let i = 0; i < node.$o.length; i++) {
notify(node.$o[i], STATE_CHECK);
}
}
}
function removeSourceObservers(node, index) {
let source, swap;
for (let i = index; i < node.$s.length; i++) {
source = node.$s[i];
if (source.$o) {
swap = source.$o.indexOf(node);
source.$o[swap] = source.$o[source.$o.length - 1];
source.$o.pop();
}
}
}
function signal(initialValue, options) {
const node = createComputation(initialValue, null, options), signal2 = read.bind(node);
signal2[SCOPE] = true;
signal2.set = write.bind(node);
return signal2;
}
function isReadSignal(fn) {
return isFunction(fn) && SCOPE in fn;
}
function computed(compute, options) {
const node = createComputation(
options?.initial,
compute,
options
), signal2 = read.bind(node);
signal2[SCOPE] = true;
return signal2;
}
function effect(effect2, options) {
const signal2 = createComputation(
null,
function runEffect() {
let effectResult = effect2();
isFunction(effectResult) && onDispose(effectResult);
return null;
},
void 0
);
signal2.$e = true;
update(signal2);
return dispose.bind(signal2, true);
}
function readonly(signal2) {
const readonly2 = () => signal2();
readonly2[SCOPE] = true;
return readonly2;
}
function isWriteSignal(fn) {
return isReadSignal(fn) && "set" in fn;
}
exports.SCOPE = SCOPE;
exports.computed = computed;
exports.createComputation = createComputation;
exports.createScope = createScope;
exports.effect = effect;
exports.getContext = getContext;
exports.getScope = getScope;
exports.isFunction = isFunction;
exports.isNotEqual = isNotEqual;
exports.isReadSignal = isReadSignal;
exports.isWriteSignal = isWriteSignal;
exports.onDispose = onDispose;
exports.onError = onError;
exports.peek = peek;
exports.readonly = readonly;
exports.root = root;
exports.scoped = scoped;
exports.setContext = setContext;
exports.signal = signal;
exports.tick = tick;
exports.untrack = untrack;
return exports;
})({});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment