Skip to content

Instantly share code, notes, and snippets.

@benalbahari
Created November 25, 2017 15:12
Show Gist options
  • Save benalbahari/119312ff95104d26f69676e0eb308695 to your computer and use it in GitHub Desktop.
Save benalbahari/119312ff95104d26f69676e0eb308695 to your computer and use it in GitHub Desktop.
hyperapp.ts
export function app<State, Actions> (props: AppProps<State, Actions>, container?: HTMLElement | null): Actions {
var lock: boolean
var root = (container = container || document.body).children[0]
var node = vnode(root, [].map)
var lifecycle : (() => void)[] = []
var appState: State = props.state || <State>{}
var appActions = <Actions>{}
repaint(init(appState, appActions, props.actions, []))
return appActions
function vnode(element: Element, map: any) : VNode<VProps> {
return (
element &&
h(
element.tagName.toLowerCase(),
{},
map.call(element.childNodes, (element: HTMLElement) =>
element.nodeType === 3
? element.nodeValue
: vnode(element, map)
)
)
)
}
function set<T>(to: T, from: T) {
for (var i in from) {
to[i] = from[i]
}
return to
}
function merge<T>(to: T, from: T): T {
return set(set(<T>{}, to), from)
}
function setDeep<T>(path: string[], value: T, from: T): T {
var to = <T>{}
return 0 === path.length
? value
: ((to[path[0]] =
1 < path.length
? setDeep(path.slice(1), value, from[path[0]])
: value),
merge(from, to))
}
function get<T>(path: string[], from: T) {
for (var i = 0; i < path.length; i++) {
from = from[path[i]]
}
return from
}
function isFunction(any: any) {
return "function" === typeof any
}
function init(state: State, actions: Actions, from: MyActions<State, Actions>, path: string[]) {
for (var key in from) {
isFunction(from[key])
? ((key, action) => {
(<any>actions)[key] = (data: any) => {
state = <State>get(path, appState)
if (
isFunction((data = (<any>action)(data))) &&
isFunction((data = data(state)))
) {
data = data(actions)
}
if (data && data !== state && !data.then) {
(<any>repaint)(
(appState = setDeep(path, merge(state, data), appState))
)
}
return data
}
})(key, from[key])
: init(
(<any>state)[key] || ((<any>state)[key] = {}),
<Actions>((<any>actions)[key] = {}),
(<any>from)[key],
path.concat(key)
)
}
}
function getKey(node?: VNode<VProps>) {
if (node && node.props) {
return node.props.key
}
}
function setElementProp(element: Node, name: string, value: any, oldValue?: any) {
if (name === "key") {
} else if (name === "style") {
for (var i in merge(oldValue, (value = value || {}))) {
(<HTMLElement>element).style[i] = null == value[i] ? "" : value[i]
}
} else {
try {
element[name] = null == value ? "" : value
} catch (_) { }
if (!isFunction(value)) {
if (null == value || false === value) {
(<HTMLElement>element).removeAttribute(name)
} else {
(<HTMLElement>element).setAttribute(name, value)
}
}
}
}
function createElement<Props extends VProps>(node: VNodeChild<Props>, isSVG?: boolean): Node {
if (typeof node === "string") {
return document.createTextNode(node)
} else {
const element = (isSVG = isSVG || node.type === "svg")
? document.createElementNS("http://www.w3.org/2000/svg", node.type)
: document.createElement(node.type)
if (node.props.oncreate) {
lifecycle.push(() =>
node.props.oncreate!(element)
)
}
for (var i = 0; i < node.children.length; i++) {
element.appendChild(createElement(<VNode<Props>>node.children[i], isSVG))
}
for (var j in node.props) {
setElementProp(element, j, node.props[j])
}
return element
}
}
function updateElement<Props extends VProps>(element: Node, oldProps: Props, props: Props) {
for (var i in merge(oldProps, props)) {
var value = props[i]
var oldValue = i === "value" || i === "checked" ? (<any>element)[i] : oldProps[i]
if (value !== oldValue) {
setElementProp(element, i, value, oldValue)
}
}
if (props.onupdate) {
lifecycle.push(() =>
props.onupdate!(element, oldProps)
)
}
}
function removeElement<Props extends VProps>(parent: Node, element: Node, props: Props) {
function done() {
parent.removeChild(element)
}
if (props && props.onremove) {
props.onremove(element, done)
} else {
done()
}
}
function isVNode (vnode: VNodeChild<VProps>) : vnode is VNode<VProps> {
return (<VNode<VProps>>vnode).type != null;
}
function patch<Props extends VProps> (
parent: Node,
element: Node,
oldNode: VNodeChild<Props> | null,
node: VNodeChild<Props>,
isSVG?: boolean,
nextSibling?: Node
)
: Node
{
if (oldNode === node) {
} else if (null == oldNode) {
element = parent.insertBefore(createElement(node, isSVG), element)
} else if (isVNode (node) && isVNode (oldNode) && node.type === oldNode.type) {
updateElement(element, oldNode.props, node.props)
isSVG = isSVG || node.type === "svg"
var len = node.children.length
var oldLen = oldNode.children.length
var oldKeyed = {}
var oldElements = []
var keyed = {}
for (var i = 0; i < oldLen; i++) {
var oldElement = (oldElements[i] = element.childNodes[i])
var oldChild = <VNode<VProps>>oldNode.children[i]
var oldKey = getKey(oldChild)
if (null != oldKey) {
oldKeyed[oldKey] = [oldElement, oldChild]
}
}
var i = 0
var j = 0
while (j < len) {
var oldElement = oldElements[i]
var oldChild = <VNode<VProps>>oldNode.children[i]
var newChild = <VNode<VProps>>node.children[j]
var oldKey = getKey(oldChild)
if ((<any>keyed)[oldKey!]) {
i++
continue
}
var newKey = getKey(newChild)
var keyedNode = oldKeyed[newKey!] || []
if (null == newKey) {
if (null == oldKey) {
patch(element, oldElement, oldChild, newChild, isSVG)
j++
}
i++
} else {
if (oldKey === newKey) {
patch(element, keyedNode[0], keyedNode[1], newChild, isSVG)
i++
} else if (keyedNode[0]) {
element.insertBefore(keyedNode[0], oldElement)
patch(element, keyedNode[0], keyedNode[1], newChild, isSVG)
} else {
patch(element, oldElement, null, newChild, isSVG)
}
j++
keyed[newKey] = newChild
}
}
while (i < oldLen) {
var oldChild = <VNode<VProps>>oldNode.children[i]
var oldKey = getKey(oldChild)
if (null == oldKey) {
removeElement(element, oldElements[i], oldChild.props)
}
i++
}
for (var k in oldKeyed) {
var keyedNode = oldKeyed[k]
var reusableNode = keyedNode[1]
if (!keyed[reusableNode.props.key]) {
removeElement(element, keyedNode[0], reusableNode.props)
}
}
} else if (element && node !== element.nodeValue) {
if (typeof node === "string" && typeof oldNode === "string") {
element.nodeValue = node
} else {
element = parent.insertBefore(
createElement(node, isSVG),
(nextSibling = element)
)
removeElement(parent, nextSibling, (<VNode<Props>>oldNode).props)
}
}
return element
}
function render(next: any) {
lock = !lock
if (isFunction((next = props.view!(appState, appActions)))) {
next = next(appActions)
}
if (!lock) {
root = <Element> patch(container!, root, node, (node = next))
}
while (next = lifecycle.pop()) next()
}
function repaint(unknown?: any) {
if (props.view && !lock) {
setTimeout(render, (lock = !lock))
}
}
}
export function h<Props extends VProps>(type: Component<Props> | string, props?: Props, children?: VNodeChildren): VNode<Props> {
var node
var stack = []
children = []
for (var i = arguments.length; i-- > 2;) {
stack.push(arguments[i])
}
while (stack.length) {
if (Array.isArray((node = stack.pop()))) {
for (i = node.length; i--;) {
stack.push(node[i])
}
} else if (null == node || node === true || node === false) {
} else {
children.push(typeof node === "number" ? (node = node + "") : node)
}
}
return typeof type === "string"
? <VNode<Props>>{
type: type,
props: props || {},
children: children
}
: <VNode<Props>>type(props || <Props>{}, <any>children)
}
export interface VProps {
key?: string,
oncreate?: (element: Node, props?: VProps) => void
onupdate?: (element: Node, props?: VProps) => void
onremove?: (element: Node, remove: () => void) => void
}
export interface VNode<Props extends VProps> {
type: string
props: Props
children: VNodeChild<{} | null>[]
}
export type VNodeChild<Props> = VNode<Props> | string
export interface Component<Props> {
(props: Props, children: VNodeChild<{} | null>[]): VNode<{}>
}
export type VNodeChildren =
| Array<VNodeChild<{} | null> | number>
| VNodeChild<{} | null>
| number
export type ActionResult<State> = Partial<State> | Promise<any> | null | void
export type MyAction<State, Actions> = (
state: State,
actions: Actions
) => ((data: any) => ActionResult<State>) | ActionResult<State>
export interface AppProps<State, Actions> {
state?: State
view?: View<State, Actions>
actions: MyActions<State, Actions>
}
export type MyActions<State, Actions> = {
[P in keyof Actions]: MyAction<State, Actions> | MyActions<any, Actions[P]>
}
export interface View<State, Actions> {
(state: State, actions?: Actions): VNode<{}>
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment