JavaScript
Article
By Jorge Bucaran

HyperApp: The 1 KB JavaScript Library for Building Front-End Apps

By Jorge Bucaran
Last chance to win! You'll get a... FREE 6-Month Subscription to SitePoint Premium Plus you'll go in the draw to WIN a new Macbook SitePoint 2017 Survey Yes, let's Do this It only takes 5 min

The problem is creating sophisticated web applications and dealing with the complexity of the tools we have at our disposal.

HyperApp was born out of the attempt to do more with less. We have aggressively minimized the concepts you need to understand while remaining on par with what other frameworks can do. What makes HyperApp unique among the alternatives like React, Preact, and Mithril, is its compact API, built-in state management, and the unrivaled, small bundle size.

In this article, I’ll introduce you to HyperApp and walk you through a few code examples to help you get started. Experience with other frameworks is not required, but since you are interested in HyperApp, I’ll assume you have some knowledge of HTML and JavaScript.

What is HyperApp?

HyperApp helps you build interactive web applications. It follows many of the principles popularized by React like one-way data flow, JSX, and Virtual DOM.

HyperApp is based on the Elm Architecture. This means that application design is like application design in Elm or React/Redux.

Try it online

import { h, app } from "hyperapp"

app({
  state: "Hi.",
  view: state => <h1>{state}</h1>
})

The examples in this article use JSX for familiarity, but you are not required to use JSX with HyperApp.

Here is the example above using Hyperx and ES6 template literals.

Try it online

import { h, app } from "hyperapp"
import hyperx from "hyperx"

const html = hyperx(h)

app({
  state: "Hi.",
  view: state => html`<h1>${state}</h1>`
})

And for completeness, here is the example again without using external libraries.

Try it online

import { h, app } from "hyperapp"

app({
  state: "Hi.",
  view: state => h("h1", null, state)
})

Concepts

Virtual Nodes

HyperApp exposes h, a function that follows the HyperScript signature and is used to create virtual nodes.

h("div", { id: "app" }, [
  h("h1", null, "Hi.")
])

A virtual node is a JavaScript object that describes an HTML/DOM tree. The example above produces the following object:

{
  tag: "div",
  data: {
    id: "app"
  },
  children: [{
    tag: "h1",
    data: null,
    children: ["Hi."]
  }]
}

JSX is a JavaScript language extension used to represent dynamic HTML. Hyperx is an ES6, standards compliant alternative. Both boil down to the same h function call behind the scenes.

Hyperx/JSX in:

<main id="app">Hi.</main>

Vanilla out:

h("main", { id: "app" }, "Hi.")

The state, view, and actions

‘Hello World’ gets boring fast. Here is an interactive example using the essential ingredients of every HyperApp application.

Try it online

app({
  state: 0,
  view: (state, actions) => (
    <main>
      <h1>{state}</h1>
      <button onclick={actions.add}>+</button>
      <button onclick={actions.sub}>-</button>
    </main>
  ),
  actions: {
    add: state => state + 1,
    sub: state => state - 1
  }
})

The state represents the entire data model of your application. In this case, it is a number and its initial value is 0.

state: 0

When the app function runs, the state is passed to the view and its value is displayed inside an <h1> tag.

<h1>{state}</h1>

There are also two buttons in the view that have onclick handlers attached to them. The handlers are the actions that get passed to the view as the second argument.

<button onclick={actions.add}>+</button>
<button onclick={actions.sub}>-</button>

Notice that neither actions.add or actions.sub update the state directly, instead, they return a new state.

add: state => state + 1,
sub: state => state - 1

When the state is updated as a result of calling an action, the view function is called and the application is rendered again.

Visualizing the architecture

Users call actions by interacting with the application and they are the only available mechanism to update the state. Under the hood, HyperApp injects actions with code that knows when to render the application.

Actions that have no side effects are also known as reducers. If you are coming from Redux, this will make sense right away. A reducer is a pure function that receives the current state of the application and some data to compute a new state.

Visualize a chain in which each link represents a state configuration. The most recent link in the chain is the current state. The view function receives the current state and actions and uses them to construct the DOM tree. When an action is called, a new link in the chain is created.

Side effects

You can call multiple actions inside an action. You can call actions inside a callback passed to an asynchronous function like fetch too.

HyperApp knows that if an action returns null or undefined the state does not need to change and it skips the render pipeline. Incidentally, if an action returns a Promise, HyperApp does not update the state either and leaves it up to the resolve callback to figure out what to do, e.g., call other actions.

Examples

GIF search box

Let’s build a GIF search box using the Giphy API. In this example, we’ll demonstrate how to update the state asynchronously.

See the Pen Giphy Gif Search by SitePoint (@SitePoint) on CodePen.

The state stores a string for the GIF URL and a boolean flag to know when the browser is fetching a new GIF.

state: {
  url: "",
  isFetching: false
}

The isFetching flag is used to hide the GIF while the browser is busy. Without it, the last downloaded GIF would be shown as another one is requested.

style={{
  display: state.isFetching ? "none" : "block"
}}

The view consists of a text input and an img element to display the GIF.

To handle user input, the onkeyup event is used, but onkeydown or oninput would work as well.

On every keystroke actions.search is called and a new GIF is requested, but only if a fetch is not already pending and the text input is not empty.

if (state.isFetching || text === "") {
  return { url: "" }
}

Inside actions.search we use the fetch API to request a GIF URL from Giphy.

When fetch is done, we receive the payload with the GIF information inside a promise.

fetch(
  `//api.giphy.com/v1/gifs/search?q=${text}&api_key=${GIPHY_API_KEY}`
)
  .then(data => data.json())
  .then(({ data }) => {
    actions.toggleFetching()
    data[0] && actions.setUrl(data[0].images.original.url)
  })

Once the data has been received, actions.toggleFetching is called (which allows further fetch requests to be made) and the state is updated by passing the fetched GIF URL to actions.setUrl.

--ADVERTISEMENT--

TweetBox Clone

For the last example, we’ll build a simple tweetbox clone.

See the Pen TweetBox by HyperApp (@hyperapp) on CodePen.

The state stores the text of the message and the number of remaining characters count, initialized to MAX_LENGTH.

state: {
  text: "",
  count: MAX_LENGTH
}

The view consists of our TweetBox component. We use the attributes, often referred to as props, to pass down data into the widget.

<TweetBox
  text={state.text}
  count={state.count}
  update={e => actions.update(e.target.value)}
/>

When the user types in the input, we call actions.update() to update the current text and calculate the remaining characters.

update: (state, actions, text) => ({
  text,
  count: state.count + state.text.length - text.length
})

The subtracting the length of the current text from the length of the previous text tells us how the number of remaining characters has changed. Hence the new count of remaining characters is the old count plus the aforementioned difference.

When the input is empty, this operation is equal to (MAX_LENGTH - text.length).

When state.count becomes less than 0, we know that state.text must be longer than MAX_LENGTH, so we can disable the tweet button and display the OverflowWidget component.

<button
  onclick={() => alert(text)}
  disabled={count >= MAX_LENGTH || count < 0}
>
  Tweet
</button>

The tweet button is also disabled when state.count === MAX_LENGTH, because that means we have not entered any characters.

The OverflowWidget tag displays the unallowed part of the message and a few adjacent characters for context. The constant OFFSET tells us how many extra characters to slice off state.text.

<OverflowWidget
  text={text.slice(count - OFFSET)}
  offset={OFFSET}
  count={count}
/>

By passing OFFSET into OverflowWidget we are able to slice text further and apply an overflow-text class to the specific overflowed part.

<span class="overflow-text">
  {text.slice(count)}
</span>

Starting a New Project

The easiest way to get started with HyperApp is via a CDN.

<script src="https://unpkg.com/hyperapp"></script>

A more realistic approach is setting up a build pipeline with npm/Yarn and bundle HyperApp with your application. A build pipeline consists of:

  • A package manager, e.g. npm or Yarn. It makes it easy to share and reuse third-party packages.
  • A compiler e.g. Babel or Bublé. It transforms modern JavaScript into code compatible with older browsers.
  • A bundler, e.g. Webpack, Rollup or Browserify. It takes modules and their dependencies and generates a single bundle that can be delivered to the browser.

Please refer to the official documentation for instructions on how to setup a specific pipeline using a bundler. If you already know the process, refer to any of these boilerplates.

HyperApp vs React

At a conceptual level, HyperApp and React have a lot of similarities.

Both libraries use a virtual DOM, lifecycle events, and key-based reconciliation.

React popularized the view as a function of the state. HyperApp takes this idea a step further with a custom state management solution based on the same principles as the Elm Architecture.

React has stateful components. HyperApp has stateless components.

Stateless components have no knowledge of the rest of the application they are a part of. This translates to high reusability and makes testing and debugging much simpler since there are no side effects.

In many ways, React is lower level than HyperApp and it often relies on third-party libraries for routing, XHR and state management.

Wrapping Up

HyperApp has a built-in state management solution inspired by the Elm Architecture. This makes it a lot like React and Redux but without the boilerplate.

HyperApp is 1 KB. This means it is faster to transfer over the network and faster to parse than anyone else.

HyperApp diffs the Virtual DOM against the DOM itself, uses real event handlers, and is easy to integrate with third party libraries.

To learn more about HyperApp check out the official documentation or follow us on Twitter for updates and announcements.

Login or Create Account to Comment
Login Create Account
Recommended
Sponsors
Get the most important and interesting stories in tech. Straight to your inbox, daily.
Is it good?Is it good?