Skip to content

Instantly share code, notes, and snippets.

@enpitsuLin
Created May 29, 2023 10:03
Show Gist options
  • Save enpitsuLin/cc51f3b326708a04f76caf797eaf46d6 to your computer and use it in GitHub Desktop.
Save enpitsuLin/cc51f3b326708a04f76caf797eaf46d6 to your computer and use it in GitHub Desktop.
/**
* expend version of van-js v0.11.10
* just for study
* @copyright Tao xin
* @link https://github.com/jorgebucaran/hyperapp
*/
import { BindFunc, ChildDom, DerivedProp, Primitive, Props, State, StateView, TagFunc, default as Van } from "./van"
// This file consistently uses `let` keyword instead of `const` for reducing the bundle size.
// Aliasing some builtin symbols to reduce the bundle size.
let Obj = Object, _undefined: undefined, protoOf = Object.getPrototypeOf
/**
* add set item to set
*
* if set was underfined will create a new set and run func with timeout
*/
let addAndScheduleOnFirst = <T>(set: Set<T> | undefined, s: T, func: CallableFunction, waitMs?: number) =>
(set ?? (setTimeout(func, waitMs), new Set)).add(s)
/**
* should be a Set<State> or undefined
*/
let changedStates: Set<State<any>> | undefined
/**
* using raw prototype object rather than class for reduce the bundle
*
* but maybe runtime performance or some other issue? [reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto)
*
* before v0.11.1+ the state was create by class State [permalink](https://github.com/vanjs-org/van/blob/3f98060971a8ff141179ef0fcd9f1b13b536a6f2/public/van-0.11.1.js#L11)
*
* @disadvantage must assign to `State.val` directly, can't using function in prototype such as `Array.proptotype.push`
*/
let stateProto = {
//@ts-expect-error: internal property
get "val"() { return this._val },
set "val"(value) {
// Aliasing `this` to reduce the bundle size.
//@ts-expect-error: internal property
let self = this, currentVal = self._val
if (value !== currentVal) {
//@ts-expect-error: internal property
if (self.oldVal === currentVal)
changedStates = addAndScheduleOnFirst(changedStates, self, updateDoms)
//@ts-expect-error: internal property
else if (value === self.oldVal)
changedStates!.delete(self)
//@ts-expect-error: internal property
self._val = value
//@ts-expect-error: internal property
self.listeners.forEach(l => l(value, currentVal))
}
},
//@ts-expect-error: internal property
"onnew"(listener) { this.listeners.push(listener) },
}
// stateProto is a plain object thus protoOf(stateProto) is just Object.prototype.
// protoOf(stateProto) is equivalent to protoOf({}) but saves 1 byte in the minized bundle.
let objProto = protoOf(stateProto)
/**
* do actually same thing like `new`
*/
let state: typeof Van['state'] = <T>(initVal: T) => ({
__proto__: stateProto,
_val: initVal,
oldVal: initVal,
bindings: [],
listeners: [],
} as unknown as State<T>)
//@ts-expect-error
let toDom = (v: ChildDom): Node => v.nodeType ? v : new Text(v)
let add: typeof Van['add'] = (dom, ...children) => {
//@ts-expect-error
(children.flat(Infinity) as ChildDom[])
.forEach(child => {
if (protoOf(child) === stateProto)
dom.appendChild(bind(child, v => v))
else
dom.appendChild(toDom(child))
})
return dom
}
let tags = new Proxy(
(name: keyof HTMLElementTagNameMap, ...args: Parameters<TagFunc<Element>>) => {
let [props, ...children] = protoOf(args[0] ?? 0) === objProto ? args : [{}, ...args]
let dom = document.createElement(name)
Obj.entries(props as Props).forEach(([attrsOrProps, value]) => {
/**
* refactor to IIFE
*/
let setter = (() => {
//@ts-expect-error
if (dom[attrsOrProps] !== _undefined) return (value: any) => dom[attrsOrProps] = value
else return (value: any) => dom.setAttribute(attrsOrProps, value)
})()
const isState = (v: typeof value): v is StateView => protoOf(value) === stateProto
const isDerivedProp = (v: typeof value): v is DerivedProp => protoOf(v) === objProto
if (isState(value)) {
bind(value, v => (setter(v), dom))
}
else if (isDerivedProp(value)) {
bind(...value["deps"] as [StateView], (...deps) => (setter(value["f"](...deps)), dom))
}
else setter(value)
})
return add(dom, ...children as ChildDom[])
},
{ get: (tag, name: keyof HTMLElementTagNameMap) => tag.bind(_undefined, name) }
) as unknown as typeof Van['tags']
/**
* filter state who has binding and all dom was connected
*/
let filterBindings = (s: any) => s.bindings = s.bindings.filter((b: Binding) => b.dom?.isConnected)
/**
* get all state who has binding then excute binding's func (maybe update dom)
*/
let updateDoms = () => {
let changedStatesArray = [...changedStates!]
changedStates = _undefined
new Set(
changedStatesArray
.flatMap<Binding>(filterBindings))
.forEach(binding => {
let { _deps, dom, func } = binding
//@ts-expect-error: internal property
let newDom = func(..._deps.map(d => d._val), dom, ..._deps.map(d => d.oldVal))
if (newDom !== dom)
/**
* maybe some issue here should `typeof newDom !== 'underfined'`
*/
if (newDom != _undefined)
(dom as Element).replaceWith(binding.dom = toDom(newDom));
else {
(dom as Element).remove();
binding.dom = _undefined
}
})
//@ts-expect-error: internal property
changedStatesArray.forEach(s => s.oldVal = s._val)
}
let bindingGcCycleInMs = 1000
let statesToGc: Set<BindingState> | undefined
let bind = <StateViews extends StateView[]>(...deps: [...StateViews, BindFunc<StateViews>]): Node | [] => {
/**
* splice last arguments which was function, see [doc](https://vanjs.org/tutorial#api-bind)
*/
let [func] = deps.splice(-1, 1) as [BindFunc<StateViews>]
//@ts-expect-error: should enough paramters
let result = func(...deps.map(d => d._val))
/**
* maybe an issue here
*/
if (result == _undefined) return []
let binding = { _deps: deps, dom: toDom(result), func }
; (deps as unknown as BindingState[]).forEach(state => {
statesToGc = addAndScheduleOnFirst(
statesToGc,
state,
() => (statesToGc!.forEach(filterBindings), statesToGc = _undefined),
bindingGcCycleInMs
)
state.bindings.push(binding)
})
return binding.dom
}
export default { add, tags, state, bind }
type Binding = {
_deps: [...StateView[], BindFunc<StateView[]>],
dom?: Node,
func: CallableFunction
}
type BindingState = StateView<any> & {
bindings: Binding[]
}
@enpitsuLin
Copy link
Author

enpitsuLin commented May 30, 2023

实际上L86 就有个明显的类型错误 不能将类型“[]”分配给类型“Node”。 但是由于L169 的问题导致 不会真的出现 []所以就没关系了 🤪

实际上这里的设计是比较_undefined引用所有没什么大问题,那么L86确实是会有问题了

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment