Read Hyperapp source code


What's Hyperapp?

If you haven’t heard about Hyperapp, then let me be the one to tell you about it. Hyperapp is a modern JavaScript library for building fast and feature-rich applications in the browser. It’s the smallest out there (1.3 KB), it’s simple, and fun to use. Hyperapp’s architecture borrows from React, Redux, and Elm, bringing my own ideas and community-contributed feedback into the mix. Here is an example that shows all the moving parts working together. Try it live on CodePen. Source:Hackernoon - Jorge Bucaran

Implementation sample

hyperapp/hello-world.md at master · hyperapp/hyperapp · GitHub

const state = {
  count: 0
}

const actions = {
  down: value => state => ({ count: state.count - value }),
  up: value => state => ({ count: state.count + value })
}

const view = (state, actions) =>
  h("div", {}, [
    h("h1", {}, state.count),
    h("button", { onclick: () => actions.down(1) }, "–"),
    h("button", { onclick: () => actions.up(1) }, "+")
  ])

window.main = app(state, actions, view, document.body)

When executed, the following HTML is displayed, and when you press the button, the number is increased/decreased.

<div>
  <h1>0</h1>
  <button>-</button>
  <button>+</button>
</div>

It looks like it was seen in React etc, but it is written with less code.

Concept of Hyperapp

hyperapp/README.md at master·hyperapp/hyperapp·GitHub

ConceptMemo
Virtual Nodes  The virtual Node uses h function or JSX etc. DOM events, life cycle events, keys can be included. 
Keys  You can manage Node with a unique key. 
Components  Componentization of virtual Node is possible.   
Lifecycle Events      It is possible to set life cycle events when creating, updating and deleting virtual Nodes.   
SanitationWhen not using virtual Node please be careful of XSS.  
Hydration Hydration. Every drawing is disadvantageous in terms of SEO as it is done in JS, so even if the initial display HTML is written in the source code OK.  

Hyperapp is realizing these things with the minimum code.

Read Hyperapp source code

Source code is here. hyperapp/index.js at master·hyperapp/hyperapp·GitHub

Only exported functions are "h()" and "app()".

h() function

Create a virtual Node. ↓ When you write

h("div", {id: 'app'}, [
  h("h1", {class: 'title'}, 'Title'),
  h("button", { onclick: () => {hoge: 'hoge'} }, "–"),
  h("button", { onclick: () => {hoge: 'fuga'} }, "+"),
  'some text'
]);

↓ It will be like this

{
  name: "div",
  props: {
    id: "app"
  },
  children: [
    {
      name: "h1",
      props: { class : "title" },
      children: ["Title"]
    },
    {
      name: "button",
      props: { onclick: () => {hoge: 'hoge'} },
      children: ["-"]
    },
    {
      name: "button",
      props: { onclick: () => {hoge: 'fuga'} },
      children: ["+"]
    },
    "some text"
  ]
}

When "h() function" is nested, the "h() function" inside (inside the third argument array) is executed in order, and the outermost h () function is executed at the very end.

In JSX case, since you can not run as JavaScript as it is, you need trans-pile, but the "h() function" moves as it is.

app() function

export function app(state, actions, view, container) {
  var patchLock
  var lifecycle = []
  var root = container && container.children[0]
  var node = vnode(root, [].map)

  repaint(init([], (state = copy(state)), (actions = copy(actions))))

  return actions

  // Internally used functions
}

Declare some variables and call init () while calling repaint () and finish.

The argument of app () and the variable declared at the beginning are used as variables of the closure in the function group used internally.

init() function

When state is returned at the time of executing the actions function, it registers to update the state and repaint ().

function init(path, slice, actions) {
    for (var key in actions) {
      typeof actions[key] === "function"
        ? (function(key, action) {
            actions[key] = function(data) {
              slice = get(path, state)

              if (typeof (data = action(data)) === "function") {
                data = data(slice, actions)
              }

              if (data && data !== slice && !data.then) {
                repaint((state = set(path, copy(slice, data), state, {})))
              }

              return data
            }
          })(key, actions[key])
        : init(
            path.concat(key),
            (slice[key] = slice[key] || {}),
            (actions[key] = copy(actions[key]))
          )
    }
  }

First, the last code is explained.

 typeof actions[key] === "function"
        ? // 省略
        : init(
            path.concat(key),
            (slice[key] = slice[key] || {}),
            (actions[key] = copy(actions[key]))
          )

If actions [key] is not a function, add key to path and call init() again. This seems to make it possible to group by using the same key in actions and state. (It is not listed in the official document sample so far) For this reason, states and actions in init () generally have the form of processing keys at that time. For example, the slice variable is an associative array corresponding to the current key from state.

Next on the code below.

 if (typeof (data = action(data)) === "function") {
                data = data(slice, actions)
              }

This corresponds to the writing style (function returning function) in which two arrow functions are connected in samples. The outer function is executed as an argument when called, the inner function is executed as slice, actions as arguments. This makes it unnecessary to pass state and actions as arguments on the view side.

For example, an action that contains a function that returns this function that comes up in the sample.

const actions = {
  down: value => state => ({ count: state.count - value }),
}

The view side corresponding to this action is a call called onclick: () => actions.down (1), with only one argument. However, since Hyperapp automatically executes the second function with state and actions as arguments, you can use these variables within the action.

Finally this code.

if (data && data !== slice && !data.then) {
                repaint((state = set(path, copy(slice, data), state, {})))
              }

              return data

If the return value of the function is not slice, I update state and repaint (). Also, under the condition "!data.then", repaint() is not executed at promise or async, and "return data" is connected to call back.

repaint() function and render() function

First, about patchLock around.

function render(next) {
    patchLock = !patchLock
    // 処理
  }

  function repaint() {
    if (!patchLock) {
      patchLock = !patchLock
      setTimeout(render)
    }
  }

In this way, it is divided into two functions in order to reduce useless processing execution. By executing setTimeout in repaint() and executing render() on another thread, the series of processing of the function which called repaint () is finished earlier. Also, by using patchLock, render () is called only once when repaint () is called many times within a series of processes.

I follow the processing of render().

function render(next) {
    next = view(state, actions)

    if (container && !patchLock) {
      root = patch(container, root, node, (node = next))
    }

    while ((next = lifecycle.pop())) next()
  }

In next = view (state, actions), a new virtual Node next is created with the latest state as an argument. Note that view () is a function to create a virtual Node. In the sample it is as follows.

const view = (state, actions) =>
  h("div", {}, [
    h("h1", {}, state.count),
    h("button", { onclick: () => actions.down(1) }, "–"),
    h("button", { onclick: () => actions.up(1) }, "+")
  ])

Next, root = patch (container, root, node, (node = next)) updates the view by executing the patch () function, replacing node with the latest one. The last while ((next = lifecycle.pop ()) next () is executing the lifecycle event of Node added / updated by patch ().

patch() function

While reflecting the difference between the old and new virtual Node, it is properly reflected in the real DOM. In addition, execution of life cycle events, processing by unique Key by Node, etc. are also done.

1.If the old and new Node are the same, I do nothing. 2.If old Node is null Insert new Node. 3.If the Node names of the old and new Node are the same Update it as well as consider Keys. 4.Set to nodeValue if text Node. 5.Otherwise insert the new Node and delete the old element.

The official document of Keys is here. hyperapp/keys.md at master · hyperapp/hyperapp · GitHub

Other functions

An auxiliary function called from the above main function.

  • copy()
  • set()
  • get()
  • getKey()
  • setElementProp()
  • createElement()
  • updateElement()
  • removeChildren()
  • removeElement()

Delete an article

Deleted articles are gone forever. Are you sure?