fase 0 sigwork
This commit is contained in:
171
sigwork_original.js
Normal file
171
sigwork_original.js
Normal file
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Sigwork - Optimized & Leak-free
|
||||
*/
|
||||
|
||||
const isFn = (v) => typeof v === 'function';
|
||||
const isNode = (v) => v instanceof Node;
|
||||
|
||||
// --- Sistema de Programación ---
|
||||
let isScheduled = false;
|
||||
const queue = new Set();
|
||||
const tick = () => {
|
||||
queue.forEach(fn => fn());
|
||||
queue.clear();
|
||||
isScheduled = false;
|
||||
}
|
||||
|
||||
// --- Efectos y Alcance ---
|
||||
let activeEffect = null;
|
||||
export const effect = (fn, is_scope = false) => {
|
||||
let cleanup;
|
||||
const run = () => {
|
||||
stop(); // Limpia suscripciones y ejecuciones previas
|
||||
const prev = activeEffect;
|
||||
activeEffect = run;
|
||||
try { cleanup = fn(); } finally { activeEffect = prev; }
|
||||
}
|
||||
const stop = () => {
|
||||
run.e.forEach(subs => subs.delete(run));
|
||||
run.e.clear();
|
||||
if (isFn(cleanup)) cleanup();
|
||||
if (run.c) {
|
||||
run.c.forEach(s => s());
|
||||
run.c.length = 0;
|
||||
}
|
||||
}
|
||||
run.e = new Set(); // Suscripciones (Signals)
|
||||
if (is_scope) run.c = []; // Efectos hijos
|
||||
run();
|
||||
|
||||
// Registro en el padre para evitar fugas
|
||||
if (activeEffect?.c) activeEffect.c.push(stop);
|
||||
return stop;
|
||||
}
|
||||
|
||||
export const scope = f => effect(f, true);
|
||||
|
||||
const track = (subs) => {
|
||||
if (activeEffect) {
|
||||
subs.add(activeEffect);
|
||||
activeEffect.e.add(subs);
|
||||
}
|
||||
};
|
||||
|
||||
// --- Signals ---
|
||||
export const signal = (value) => {
|
||||
const subs = new Set();
|
||||
return {
|
||||
get value() { track(subs); return value; },
|
||||
set value(nv) {
|
||||
if (nv === value) return;
|
||||
value = nv;
|
||||
subs.forEach(fn => queue.add(fn));
|
||||
if (!isScheduled) { isScheduled = true; queueMicrotask(tick); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const computed = (fn) => {
|
||||
const sig = signal();
|
||||
effect(() => sig.value = fn());
|
||||
return { get value() { return sig.value; } };
|
||||
}
|
||||
|
||||
// --- Rendering Core ---
|
||||
let context = null;
|
||||
export const onMount = (fn) => context?.m.push(fn);
|
||||
export const onUnmount = (fn) => context?.u.push(fn);
|
||||
|
||||
const remove = (node) => {
|
||||
if (Array.isArray(node)) return node.forEach(remove);
|
||||
node.$s?.(); // Detener efectos del nodo
|
||||
if (node.$c) node.$c.u.forEach(f => f()); // Ejecutar onUnmount
|
||||
const done = () => node.remove();
|
||||
node.$l ? node.$l(done) : done();
|
||||
}
|
||||
|
||||
export const h = (tag, props = {}, ...children) => {
|
||||
children = children.flat(Infinity);
|
||||
|
||||
if (isFn(tag)) {
|
||||
const prev = context;
|
||||
const ctx = context = { m: [], u: [], p: { ...(prev?.p || {}) } };
|
||||
let res;
|
||||
const stop = effect(() => {
|
||||
res = tag(props, { children, emit: (e, ...a) => props[`on${e[0].toUpperCase()}${e.slice(1)}`]?.(...a) });
|
||||
return () => ctx.u.forEach(f => f());
|
||||
}, true);
|
||||
|
||||
// Si el componente devuelve un array, necesitamos un ancla para colgar el ciclo de vida
|
||||
const out = isNode(res) ? res : document.createTextNode(String(res));
|
||||
out.$c = ctx;
|
||||
out.$s = stop;
|
||||
context = prev;
|
||||
return out;
|
||||
}
|
||||
|
||||
if (!tag) return children; // Fragment (manejo simple)
|
||||
|
||||
const isSvg = tag === 'svg' || tag === 'path' || tag === 'circle'; // Simplificado
|
||||
const el = isSvg
|
||||
? document.createElementNS("http://www.w3.org/2000/svg", tag)
|
||||
: document.createElement(tag);
|
||||
|
||||
for (const key in props) {
|
||||
const val = props[key];
|
||||
if (key.startsWith('on')) el.addEventListener(key.slice(2).toLowerCase(), val);
|
||||
else if (key === "ref") isFn(val) ? val(el) : (val.value = el);
|
||||
else if (isFn(val)) effect(() => el[key] = val()); // Atribución reactiva
|
||||
else el[key] = val;
|
||||
}
|
||||
|
||||
children.forEach(child => append(el, child));
|
||||
return el;
|
||||
}
|
||||
|
||||
const append = (parent, child) => {
|
||||
if (child == null) return;
|
||||
if (isFn(child)) {
|
||||
const anchor = document.createTextNode('');
|
||||
parent.appendChild(anchor);
|
||||
let nodes = [];
|
||||
effect(() => {
|
||||
const raw = [child()].flat(Infinity).filter(v => v != null);
|
||||
const newNodes = raw.map(n => isNode(n) ? n : document.createTextNode(String(n)));
|
||||
|
||||
// Cleanup de nodos antiguos que ya no están
|
||||
nodes.forEach(n => { if (!newNodes.includes(n)) remove(n); });
|
||||
|
||||
// Reconciliación simple
|
||||
newNodes.forEach((n, i) => {
|
||||
if (!nodes.includes(n)) {
|
||||
parent.insertBefore(n, newNodes[i+1] || anchor);
|
||||
if (n.$c) n.$c.m.forEach(f => f());
|
||||
}
|
||||
});
|
||||
nodes = newNodes;
|
||||
}, true);
|
||||
} else {
|
||||
parent.appendChild(isNode(child) ? child : document.createTextNode(String(child)));
|
||||
}
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
export const For = (list, key, renderFn) => {
|
||||
let cache = new Map();
|
||||
return () => {
|
||||
const next = new Map();
|
||||
const items = isFn(list) ? list() : list.value || list;
|
||||
const res = items.map((item, i) => {
|
||||
const id = isFn(key) ? key(item, i) : (key ? item[key] : item);
|
||||
let node = cache.get(id);
|
||||
if (!node) node = h(() => renderFn(item, i));
|
||||
next.set(id, node);
|
||||
return node;
|
||||
});
|
||||
// Limpiar nodos que salen de la lista
|
||||
cache.forEach((node, id) => { if (!next.has(id)) remove(node); });
|
||||
cache = next;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user