Skip to content

Commit dfa085c

Browse files
committed
1.2.3: emergency fix of the issue
1 parent 24b5edf commit dfa085c

19 files changed

+760
-9
lines changed

demo/terminal/van-1.2.2.min.js

Lines changed: 0 additions & 1 deletion
This file was deleted.

demo/terminal/van-1.2.3.min.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "vanjs-core",
3-
"version": "1.2.2",
3+
"version": "1.2.3",
44
"description": "VanJS. A minimalist React-like UI library based on vanilla JavaScript and DOM.",
55
"files": [
66
"src/van.js",

public/van-1.2.3.d.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
export interface State<T> {
2+
val: T
3+
readonly oldVal: T
4+
}
5+
6+
// Defining readonly view of State<T> for covariance.
7+
// Basically we want StateView<string> to implement StateView<string | number>
8+
export type StateView<T> = Readonly<State<T>>
9+
10+
export type Primitive = string | number | boolean | bigint
11+
12+
export type PropValue = Primitive | ((e: any) => void) | null
13+
14+
export type Props = Record<string, PropValue | StateView<PropValue> | (() => PropValue)>
15+
16+
export type ValidChildDomValue = Primitive | Node | null | undefined
17+
18+
export type BindingFunc = ((dom?: Node) => ValidChildDomValue) | ((dom?: Element) => Element)
19+
20+
export type ChildDom = ValidChildDomValue | StateView<Primitive | null | undefined> | BindingFunc | readonly ChildDom[]
21+
22+
export type TagFunc<Result> = (first?: Props | ChildDom, ...rest: readonly ChildDom[]) => Result
23+
24+
type Tags = Readonly<Record<string, TagFunc<Element>>> & {
25+
[K in keyof HTMLElementTagNameMap]: TagFunc<HTMLElementTagNameMap[K]>
26+
}
27+
28+
export interface Van {
29+
readonly state: <T>(initVal: T) => State<T>
30+
readonly val: <T>(s: T | StateView<T>) => T
31+
readonly oldVal: <T>(s: T | StateView<T>) => T
32+
readonly derive: <T>(f: () => T) => State<T>
33+
readonly add: (dom: Element, ...children: readonly ChildDom[]) => Element
34+
readonly _: (f: () => PropValue) => () => PropValue
35+
readonly tags: Tags
36+
readonly tagsNS: (namespaceURI: string) => Readonly<Record<string, TagFunc<Element>>>
37+
readonly hydrate: <T extends Node>(dom: T, f: (dom: T) => T | null | undefined) => T
38+
}
39+
40+
declare const van: Van
41+
42+
export default van

public/van-1.2.3.debug.d.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
export interface State<T> {
2+
val: T
3+
readonly oldVal: T
4+
}
5+
6+
// Defining readonly view of State<T> for covariance.
7+
// Basically we want StateView<string> to implement StateView<string | number>
8+
export type StateView<T> = Readonly<State<T>>
9+
10+
export type Primitive = string | number | boolean | bigint
11+
12+
export type PropValue = Primitive | ((e: any) => void) | null
13+
14+
export type Props = Record<string, PropValue | StateView<PropValue> | (() => PropValue)>
15+
16+
export type ValidChildDomValue = Primitive | Node | null | undefined
17+
18+
export type BindingFunc = ((dom?: Node) => ValidChildDomValue) | ((dom?: Element) => Element)
19+
20+
export type ChildDom = ValidChildDomValue | StateView<Primitive | null | undefined> | BindingFunc | readonly ChildDom[]
21+
22+
export type TagFunc<Result> = (first?: Props | ChildDom, ...rest: readonly ChildDom[]) => Result
23+
24+
type Tags = Readonly<Record<string, TagFunc<Element>>> & {
25+
[K in keyof HTMLElementTagNameMap]: TagFunc<HTMLElementTagNameMap[K]>
26+
}
27+
28+
export interface Van {
29+
readonly state: <T>(initVal: T) => State<T>
30+
readonly val: <T>(s: T | StateView<T>) => T
31+
readonly oldVal: <T>(s: T | StateView<T>) => T
32+
readonly derive: <T>(f: () => T) => State<T>
33+
readonly add: (dom: Element, ...children: readonly ChildDom[]) => Element
34+
readonly _: (f: () => PropValue) => () => PropValue
35+
readonly tags: Tags
36+
readonly tagsNS: (namespaceURI: string) => Readonly<Record<string, TagFunc<Element>>>
37+
readonly hydrate: <T extends Node>(dom: T, f: (dom: T) => T | null | undefined) => T
38+
}
39+
40+
declare const van: Van
41+
42+
export default van

public/van-1.2.3.debug.js

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import van from "./van-1.2.3.js"
2+
3+
// If this variable is set to an Array, we will push the error message into the array instead of
4+
// throwing an error. This is useful in testing, to capture the error occurred asynchronous to the initiating
5+
// callstack. e.g.: a state change can trigger a dom update processing when idle (i.e.: dom update
6+
// processing is set via setTimeout function, which is asynchronous to the initiating callstack).
7+
let capturedErrors
8+
9+
const startCapturingErrors = () => capturedErrors = []
10+
11+
const stopCapturingErrors = () => capturedErrors = null
12+
13+
const expect = (cond, msg) => {
14+
if (!cond) {
15+
if (capturedErrors) capturedErrors.push(msg); else throw new Error(msg)
16+
return false
17+
}
18+
return true
19+
}
20+
21+
const protoOf = Object.getPrototypeOf
22+
const stateProto = protoOf(van.state())
23+
const isState = s => protoOf(s ?? 0) === stateProto
24+
25+
const checkStateValValid = v => {
26+
expect(!isState(v), "State couldn't have value to other state")
27+
expect(!(v instanceof Node), "DOM Node is not valid value for state")
28+
return v
29+
}
30+
31+
const state = initVal => new Proxy(van.state(Object.freeze(checkStateValValid(initVal))), {
32+
set: (s, prop, val) => {
33+
prop === "val" && Object.freeze(checkStateValValid(val))
34+
return Reflect.set(s, prop, val)
35+
}
36+
})
37+
38+
const derive = f => {
39+
expect(typeof(f) === "function", "Must pass-in a function to `van.derive`")
40+
return van.derive(f)
41+
}
42+
43+
const isValidPrimitive = v =>
44+
typeof(v) === "string" ||
45+
typeof(v) === "number" ||
46+
typeof(v) === "boolean" ||
47+
typeof(v) === "bigint"
48+
49+
const isDomOrPrimitive = v => v instanceof Node || isValidPrimitive(v)
50+
51+
const validateChild = child => {
52+
expect(
53+
isDomOrPrimitive(child) || child === null || child === undefined,
54+
"Only DOM Node, string, number, boolean, bigint, null, undefined are valid child of a DOM Element",
55+
)
56+
return child
57+
}
58+
59+
const withResultValidation = f => dom => {
60+
const r = validateChild(f(dom))
61+
if (r !== dom && r instanceof Node)
62+
expect(!r.isConnected,
63+
"If the result of complex binding function is not the same as previous one, it shouldn't be already connected to document")
64+
return r
65+
}
66+
67+
const checkChildren = children => children.flat(Infinity).map(c => {
68+
if (isState(c)) return withResultValidation(() => c.val)
69+
if (typeof c === "function") return withResultValidation(c)
70+
expect(!c?.isConnected, "You can't add a DOM Node that is already connected to document")
71+
return validateChild(c)
72+
})
73+
74+
const add = (dom, ...children) => {
75+
expect(dom instanceof Element, "1st argument of `van.add` function must be a DOM Element object")
76+
return van.add(dom, ...checkChildren(children))
77+
}
78+
79+
const _ = f => {
80+
expect(typeof(f) === "function", "Must pass-in a function to `van._`")
81+
return van._(f)
82+
}
83+
84+
const _tagsNS = ns => new Proxy(van.tagsNS(ns), {
85+
get: (vanTags, name) => {
86+
const vanTag = vanTags[name]
87+
return (...args) => {
88+
const [props, ...children] = protoOf(args[0] ?? 0) === Object.prototype ? args : [{}, ...args]
89+
const debugProps = {}
90+
for (const [k, v] of Object.entries(props)) {
91+
const validatePropValue = k.startsWith("on") ?
92+
(k.toLowerCase() === k ?
93+
v => (expect(typeof v === "function" || v === null,
94+
`Invalid property value for ${k}: Only functions and null are allowed for ${k} property`), v) :
95+
v => (expect(typeof v === "string",
96+
`Invalid property value for ${k}: Only strings are allowed for ${k} attribute`), v)
97+
) :
98+
v => (expect(isValidPrimitive(v) || v === null,
99+
`Invalid property value for ${k}: Only string, number, boolean, bigint and null are valid prop value types`), v)
100+
101+
if (isState(v))
102+
debugProps[k] = van._(() => validatePropValue(v.val))
103+
else if (typeof v === "function" && (!k.startsWith("on") || v._isBindingFunc))
104+
debugProps[k] = van._(() => validatePropValue(v()))
105+
else
106+
debugProps[k] = validatePropValue(v)
107+
}
108+
return vanTag(debugProps, ...checkChildren(children))
109+
}
110+
},
111+
})
112+
113+
const tagsNS = ns => {
114+
expect(typeof ns === "string", "Must provide a string for parameter `ns` in `van.tagsNS`")
115+
return _tagsNS(ns)
116+
}
117+
118+
const hydrate = (dom, f) => {
119+
expect(dom instanceof Node, "1st argument of `van.hydrate` function must be a DOM Node object")
120+
expect(typeof(f) === "function", "2nd argument of `van.hydrate` function must be a function")
121+
return van.hydrate(dom, withResultValidation(f))
122+
}
123+
124+
export default {add, _, tags: _tagsNS(), tagsNS, state, val: van.val, oldVal: van.oldVal, derive, hydrate, startCapturingErrors, stopCapturingErrors, get capturedErrors() { return capturedErrors }}

public/van-1.2.3.js

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// This file consistently uses `let` keyword instead of `const` for reducing the bundle size.
2+
3+
// Global variables - aliasing some builtin symbols to reduce the bundle size.
4+
let Obj = Object, _undefined, protoOf = Obj.getPrototypeOf, doc = document
5+
let changedStates, curDeps, curNewDerives, alwaysConnectedDom = {isConnected: 1}, gcCycleInMs = 1000, statesToGc, propSetterCache = {}
6+
let objProto = protoOf(alwaysConnectedDom), funcProto = protoOf(protoOf)
7+
8+
let addAndScheduleOnFirst = (set, s, f, waitMs) =>
9+
(set ?? (setTimeout(f, waitMs), new Set)).add(s)
10+
11+
let runAndCaptureDeps = (f, deps, arg) => {
12+
let prevDeps = curDeps
13+
curDeps = deps
14+
try {
15+
return f(arg)
16+
} catch (e) {
17+
console.error(e)
18+
return arg
19+
} finally {
20+
curDeps = prevDeps
21+
}
22+
}
23+
24+
let keepConnected = l => l.filter(b => b._dom?.isConnected)
25+
26+
let addStatesToGc = d => statesToGc = addAndScheduleOnFirst(statesToGc, d, () => {
27+
for (let s of statesToGc)
28+
s._bindings = keepConnected(s._bindings),
29+
s._listeners = keepConnected(s._listeners)
30+
statesToGc = _undefined
31+
}, gcCycleInMs)
32+
33+
let stateProto = {
34+
get val() {
35+
curDeps?.add(this)
36+
return this._val
37+
},
38+
39+
get oldVal() {
40+
curDeps?.add(this)
41+
return this._oldVal
42+
},
43+
44+
set val(v) {
45+
// Aliasing `this` to reduce the bundle size.
46+
let s = this
47+
if (v !== s._val) {
48+
s._val = v
49+
let listeners = [...s._listeners = keepConnected(s._listeners)]
50+
for (let l of listeners) derive(l.f, l.s, l._dom), l._dom = _undefined
51+
s._bindings.length ?
52+
changedStates = addAndScheduleOnFirst(changedStates, s, updateDoms) :
53+
s._oldVal = v
54+
}
55+
},
56+
}
57+
58+
let state = initVal => ({
59+
__proto__: stateProto,
60+
_val: initVal,
61+
_oldVal: initVal,
62+
_bindings: [],
63+
_listeners: [],
64+
})
65+
66+
let isState = s => protoOf(s ?? 0) === stateProto
67+
68+
let val = s => isState(s) ? s.val : s
69+
let oldVal = s => isState(s) ? s.oldVal : s
70+
71+
let bind = (f, dom) => {
72+
let deps = new Set, binding = {f}, prevNewDerives = curNewDerives
73+
curNewDerives = []
74+
let newDom = runAndCaptureDeps(f, deps, dom)
75+
newDom = (newDom ?? doc).nodeType ? newDom : new Text(newDom)
76+
for (let d of deps) addStatesToGc(d), d._bindings.push(binding)
77+
for (let l of curNewDerives) l._dom = newDom
78+
curNewDerives = prevNewDerives
79+
return binding._dom = newDom
80+
}
81+
82+
let derive = (f, s = state(), dom) => {
83+
let deps = new Set, listener = {f, s}
84+
listener._dom = dom ?? curNewDerives?.push(listener) ?? alwaysConnectedDom
85+
s.val = runAndCaptureDeps(f, deps)
86+
for (let d of deps) addStatesToGc(d), d._listeners.push(listener)
87+
return s
88+
}
89+
90+
let add = (dom, ...children) => {
91+
for (let c of children.flat(Infinity)) {
92+
let protoOfC = protoOf(c ?? 0)
93+
let child = protoOfC === stateProto ? bind(() => c.val) :
94+
protoOfC === funcProto ? bind(c) : c
95+
if (child != _undefined) dom.append(child)
96+
}
97+
return dom
98+
}
99+
100+
let _ = f => (f._isBindingFunc = 1, f)
101+
102+
let tagsNS = ns => new Proxy((name, ...args) => {
103+
let [props, ...children] = protoOf(args[0] ?? 0) === objProto ? args : [{}, ...args]
104+
let dom = ns ? doc.createElementNS(ns, name) : doc.createElement(name)
105+
for (let [k, v] of Obj.entries(props)) {
106+
let getPropDescriptor = proto => proto ?
107+
Obj.getOwnPropertyDescriptor(proto, k) ?? getPropDescriptor(protoOf(proto)) :
108+
_undefined
109+
let cacheKey = name + "," + k
110+
let propSetter = propSetterCache[cacheKey] ??
111+
(propSetterCache[cacheKey] = getPropDescriptor(protoOf(dom))?.set ?? 0)
112+
let setter = propSetter ? propSetter.bind(dom) : dom.setAttribute.bind(dom, k)
113+
let protoOfV = protoOf(v ?? 0)
114+
if (protoOfV === stateProto) bind(() => (setter(v.val), dom))
115+
else if (protoOfV === funcProto && (!k.startsWith("on") || v._isBindingFunc))
116+
bind(() => (setter(v()), dom))
117+
else setter(v)
118+
}
119+
return add(dom, ...children)
120+
}, {get: (tag, name) => tag.bind(_undefined, name)})
121+
122+
let update = (dom, newDom) => newDom ? newDom !== dom && dom.replaceWith(newDom) : dom.remove()
123+
124+
let updateDoms = () => {
125+
let changedStatesArray = [...changedStates].filter(s => s._val !== s._oldVal)
126+
changedStates = _undefined
127+
for (let b of new Set(changedStatesArray.flatMap(s => s._bindings = keepConnected(s._bindings))))
128+
update(b._dom, bind(b.f, b._dom)), b._dom = _undefined
129+
for (let s of changedStatesArray) s._oldVal = s._val
130+
}
131+
132+
let hydrate = (dom, f) => update(dom, bind(f, dom))
133+
134+
export default {add, _, tags: tagsNS(), tagsNS, state, val, oldVal, derive, hydrate}

0 commit comments

Comments
 (0)