Created
May 29, 2023 10:03
-
-
Save enpitsuLin/cc51f3b326708a04f76caf797eaf46d6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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[] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
实际上L86 就有个明显的类型错误
不能将类型“[]”分配给类型“Node”。
但是由于L169 的问题导致 不会真的出现[]
所以就没关系了 🤪实际上这里的设计是比较
_undefined
引用所有没什么大问题,那么L86确实是会有问题了