van-hmr/lib/van/van.js

227 lines
7.2 KiB
JavaScript
Raw Normal View History

2025-02-03 14:13:41 -05:00
// This file consistently uses `let` keyword instead of `const` for reducing the bundle size.
// Global variables - aliasing some builtin symbols to reduce the bundle size.
let protoOf = Object.getPrototypeOf
let changedStates, derivedStates, curDeps, curNewDerives, alwaysConnectedDom = {isConnected: 1}
let gcCycleInMs = 1000, statesToGc, propSetterCache = {}
let objProto = protoOf(alwaysConnectedDom), funcProto = protoOf(protoOf), _undefined
let addAndScheduleOnFirst = (set, s, f, waitMs) =>
(set ?? (setTimeout(f, waitMs), new Set)).add(s)
let runAndCaptureDeps = (f, deps, arg) => {
let prevDeps = curDeps
curDeps = deps
try {
return f(arg)
} catch (e) {
console.error(e)
return arg
} finally {
curDeps = prevDeps
}
}
let keepConnected = l => l.filter(b => b._dom?.isConnected)
let addStatesToGc = d => statesToGc = addAndScheduleOnFirst(statesToGc, d, () => {
for (let s of statesToGc)
s._bindings = keepConnected(s._bindings),
s._listeners = keepConnected(s._listeners)
statesToGc = _undefined
}, gcCycleInMs)
let stateProto = {
get val() {
curDeps?._getters?.add(this)
return this.rawVal
},
get oldVal() {
curDeps?._getters?.add(this)
return this._oldVal
},
set val(v) {
curDeps?._setters?.add(this)
if (v !== this.rawVal) {
this.rawVal = v
this._bindings.length + this._listeners.length ?
(derivedStates?.add(this), changedStates = addAndScheduleOnFirst(changedStates, this, updateDoms)) :
this._oldVal = v
}
},
}
let state = initVal => ({
__proto__: stateProto,
rawVal: initVal,
_oldVal: initVal,
_bindings: [],
_listeners: [],
})
let bind = (f, dom) => {
let deps = {_getters: new Set, _setters: new Set}, binding = {f}, prevNewDerives = curNewDerives
curNewDerives = []
let newDom = runAndCaptureDeps(f, deps, dom)
newDom = (newDom ?? document).nodeType ? newDom : new Text(newDom)
for (let d of deps._getters)
deps._setters.has(d) || (addStatesToGc(d), d._bindings.push(binding))
for (let l of curNewDerives) l._dom = newDom
curNewDerives = prevNewDerives
return binding._dom = newDom
}
let derive = (f, s = state(), dom) => {
let deps = {_getters: new Set, _setters: new Set}, listener = {f, s}
listener._dom = dom ?? curNewDerives?.push(listener) ?? alwaysConnectedDom
s.val = runAndCaptureDeps(f, deps, s.rawVal)
for (let d of deps._getters)
deps._setters.has(d) || (addStatesToGc(d), d._listeners.push(listener))
return s
}
let add = (dom, ...children) => {
for (let c of children.flat(Infinity)) {
let protoOfC = protoOf(c ?? 0)
let child = protoOfC === stateProto ? bind(() => c.val) :
protoOfC === funcProto ? bind(c) : c
child != _undefined && dom.append(child)
}
return dom
}
let tag = (ns, name, ...args) => {
let [{is, ...props}, ...children] = protoOf(args[0] ?? 0) === objProto ? args : [{}, ...args]
let dom = ns ? document.createElementNS(ns, name, {is}) : document.createElement(name, {is})
for (let [k, v] of Object.entries(props)) {
let getPropDescriptor = proto => proto ?
Object.getOwnPropertyDescriptor(proto, k) ?? getPropDescriptor(protoOf(proto)) :
_undefined
let cacheKey = name + "," + k
let propSetter = propSetterCache[cacheKey] ??= getPropDescriptor(protoOf(dom))?.set ?? 0
let setter = k.startsWith("on") ?
(v, oldV) => {
let event = k.slice(2)
dom.removeEventListener(event, oldV)
dom.addEventListener(event, v)
} :
propSetter ? propSetter.bind(dom) : dom.setAttribute.bind(dom, k)
let protoOfV = protoOf(v ?? 0)
k.startsWith("on") || protoOfV === funcProto && (v = derive(v), protoOfV = stateProto)
protoOfV === stateProto ? bind(() => (setter(v.val, v._oldVal), dom)) : setter(v)
}
return add(dom, children)
}
let handler = ns => ({get: (_, name) => tag.bind(_undefined, ns, name)})
let update = (dom, newDom) => newDom ? newDom !== dom && dom.replaceWith(newDom) : dom.remove()
2025-02-03 14:54:44 -05:00
//let updateDoms = () => {
// let iter = 0, derivedStatesArray = [...changedStates].filter(s => s.rawVal !== s._oldVal)
// do {
// derivedStates = new Set
// for (let l of new Set(derivedStatesArray.flatMap(s => s._listeners = keepConnected(s._listeners))))
// derive(l.f, l.s, l._dom), l._dom = _undefined
// } while (++iter < 100 && (derivedStatesArray = [...derivedStates]).length)
// let changedStatesArray = [...changedStates].filter(s => s.rawVal !== s._oldVal)
// changedStates = _undefined
// for (let b of new Set(changedStatesArray.flatMap(s => s._bindings = keepConnected(s._bindings))))
// update(b._dom, bind(b.f, b._dom)), b._dom = _undefined
// for (let s of changedStatesArray) s._oldVal = s.rawVal
//}
/*********************************************** */
const Gateway = {
Time: 0,
Temp:{},
Tick()
{
for(let k in Gateway.Temp)
{
localStorage.setItem(k, Gateway.Temp[k]);
}
Gateway.Temp = {};
Gateway.Time = 0;
},
Save(key, value)
{
Gateway.Temp[key] = value;
if(!Gateway.Time)
{
Gateway.Time = setTimeout(Gateway.Tick, 500);
}
},
Load(key)
{
return localStorage.getItem(key);
}
}
function pathHash(/** @type {HTMLElement} */element)
{
2025-02-03 14:54:44 -05:00
const path = [];
while (element && element !== document.body) {
2025-02-03 14:54:44 -05:00
const parent = element.parentElement
path.unshift(Array.from(parent.children).indexOf(element)); // Push the index of the current element at the start of the path
element = parent;
2025-02-03 14:54:44 -05:00
}
return path.join('-'); // Return the path as a string, e.g., "1-0-0-2"
}
/**
* Logs a relationship between a state and a DOM element.
* @param {any} state - The state object being tracked.
* @param {Element} dom - The DOM element affected by the state.
*/
2025-02-03 17:11:51 -05:00
const trackStateDomRelationship = (state, doms) => {
doms.forEach(dom => {
if(dom)
{
const hash = pathHash(dom);
Gateway.Save(hash, JSON.stringify(state));
}
});
2025-02-03 14:54:44 -05:00
};
// Enhanced updateDoms function
2025-02-03 14:13:41 -05:00
let updateDoms = () => {
let iter = 0, derivedStatesArray = [...changedStates].filter(s => s.rawVal !== s._oldVal)
2025-02-03 14:13:41 -05:00
do {
derivedStates = new Set
for (let l of new Set(derivedStatesArray.flatMap(s => s._listeners = keepConnected(s._listeners))))
derive(l.f, l.s, l._dom), l._dom = _undefined
} while (++iter < 100 && (derivedStatesArray = [...derivedStates]).length)
let changedStatesArray = [...changedStates].filter(s => s.rawVal !== s._oldVal)
changedStates = _undefined
const _block = new Set(changedStatesArray.flatMap(s =>{
trackStateDomRelationship(s.rawVal, s._bindings.map(b=>b._dom));
s.rawVal = "FORCE";
return s._bindings = keepConnected(s._bindings);
}));
2025-02-03 14:54:44 -05:00
2025-02-03 17:11:51 -05:00
for (let b of _block) {
2025-02-03 14:54:44 -05:00
update(b._dom, bind(b.f, b._dom));
b._dom = _undefined;
}
// Update old values for changed states
for (let s of changedStatesArray) {
s._oldVal = s.rawVal;
}
};
// Debugging helper to log the state-DOM relationships
globalThis.Divulge = () => console.log(Gateway.Temp);
2025-02-03 14:54:44 -05:00
globalThis.path = pathHash;
/*********************************************** */
2025-02-03 14:13:41 -05:00
export default {
tags: new Proxy(ns => new Proxy(tag, handler(ns)), handler()),
hydrate: (dom, f) => update(dom, bind(f, dom)),
add, state, derive,
}