van-hmr/lib/van/van.js
2025-02-03 14:13:41 -05:00

140 lines
4.8 KiB
JavaScript

// 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()
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
}
export default {
tags: new Proxy(ns => new Proxy(tag, handler(ns)), handler()),
hydrate: (dom, f) => update(dom, bind(f, dom)),
add, state, derive,
}