Hyperapp – Is It the Lightweight 'React Killer'?
Tags: react, javascript, discuss
Published: 2021-03-31 06:20:02

_by author [Arek Nawo](https://blog.asayer.io/authors/arek-nawo)_

For a while now, JavaScript and its ecosystem have been thriving. From different tooling categories, **UI frameworks** have enjoyed immense popularity. New libraries and frameworks seem to pop up almost every day, while Vue, React, and Angular persistently lead the way.

From these new options, there are some that deserve your attention. I’m thinking Svelte, Solid, and **Hyperapp** - an ultra-lightweight UI framework that we’ll be taking a further look at.

# What is Hyperapp?

Hyperapp isn’t really that new. It’s been around since [the end of 2016](https://github.com/jorgebucaran/hyperapp/commit/72f764932a619f415e2f4f5cefdfc4cef5a94511) and gained a lot of popularity over this period. It’s gained over **18K GitHub stars** on its repo and over **2.5K weekly downloads** on NPM.

It’s easy to see why so many developers got interested in the library given its feature set. [Tiny footprint](https://bundlephobia.com/result?p=hyperapp), high performance, simplistic architecture, and development experience designed around not using any bundler or compiler - these are all very useful features.

## v2

Hyperapp reached its popularity peak middle of 2020 and dropped quite a bit since. That’s potentially due to the rise of v2 of the framework, which, although brought many improvements, also came with many breaking changes.

These breaking changes caused the already small ecosystem to crumble and the loss of some handy features like [JSX](https://github.com/jorgebucaran/hyperapp/pull/1011), and [TypeScript typings](https://github.com/jorgebucaran/hyperapp/pull/1016), with both being still worked on for v2.

## Bright future

With that said, Hyperapp still has a bright future ahead of it. Given all of its current advantages and continuous improvements, the framework is on track to match or surpass its prior popularity peak.

Now, to give you a better sense of what Hyperapp is about, let’s compare it with the most well-known JS UI library out there - React.

# Comparing performance

Let’s start with performance - one of the strongest advantages of Hyperapp.

## Benchmark

For such comparisons, I often start with an awesome, [open-source benchmark](https://github.com/krausest/js-framework-benchmark) that covers many JS UI frameworks and libraries - including Hyperapp. You can see the most recent results [here](https://krausest.github.io/js-framework-benchmark/current.html), and an example table below:

![Hyperapp speed benchmark](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/am1i7iv5x96m9o1z9p0f.png)

Although synthetic benchmarks don’t always reflect real-world performance, they provide us with a rough, easy-to-compare metric.

Above, you can see Hyperapp trading blows with Solid and Svelte. It’s a very impressive result, especially considering that we’re comparing compiler-backed frameworks to a pure-runtime one.

As for React - it’s the last in the whole group. That’s nothing new, considering that React isn’t known for its high performance, rather than the ecosystem, innovation, and overall leading the trends.

## Bundle size

**Size** is another metric determining the overall framework’s performance. Although it’s becoming increasingly less important with modern devices and high-speed connections, it’s still worth considering for low-end devices and other constrained environments.

Hyperapp’s [really lightweight](https://bundlephobia.com/result?p=hyperapp) - about **1.7KB** minified + gzipped (what’s downloaded), and **3.7KB** minified (what’s parsed and executed). That’s for Hyperapp core library, which is perfectly usable on its own.

As for [React](https://bundlephobia.com/result?p=react), we’ve got **2.8KB** min-gzip and **7KB** min-only. That’s already almost ×2 the size of Hyperapp, and it goes much higher when we factor in [react-dom](https://bundlephobia.com/result?p=react-dom@17.0.1) (required for rendering to DOM) - **39.4KB** min-gzip and **121.1KB** min-only.

These results translate nicely into the startup timing, measured by the previously-mentioned benchmark:

![Hyperapp startup benchmark](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y6fckrsvb49153en22y0.png)

Obviously, these benefits come at the cost of the framework’s feature set. Its minimalism and simplicity might not be good for everyone, but it’s just enough to get the job done. Let’s see how it compares in this regard.

# Comparing architecture and reactivity model

Framework’s architecture, design, and API are what determines the end development experience and workflow. In the case of Hyperapp, it’s clearly going the minimalistic route, packing only the features you need while focusing heavily on performance. How does it affect its usability?

## Initial setup

Right from the start, you can see stark differences between React and Hyperapp’s philosophies. While React pushes you towards packages & bundlers or CDNs, Hyperapp focuses on native **ESM modules**. Take a look at a primary example from the README:


```js
<!DOCTYPE html>
<html lang="en">
  <head>
    <script type="module">
      import { h, text, app } from "https://unpkg.com/hyperapp"

      const AddTodo = (state) => ({
        ...state,
        value: "",
        todos: state.todos.concat(state.value),
      })

      const NewValue = (state, event) => ({
        ...state,
        value: event.target.value,
      })

      app({
        init: { todos: [], value: "" },
        view: ({ todos, value }) =>
          h("main", {}, [
            h("h1", {}, text("To do list")),
            h("input", { type: "text", oninput: NewValue, value }),
            h("ul", {},
              todos.map((todo) => h("li", {}, text(todo)))
            ),
            h("button", { onclick: AddTodo }, text("New!")),
          ]),
        node: document.getElementById("app"),
      })
    </script>
  </head>
  <body>
    <main id="app"></main>
  </body>
</html>
```


You can see how Hyperapp focuses on pure JS, runtime-based usage. That’s why things like TypeScript or JSX support aren’t the highest priority.

This focus makes such high levels of performance possible, and it’s why the framework is so simple and minimalist.

On the contrary, React focuses extensively on JSX and thus requires code pre-processing. It’s not to say that this approach is worse or better in any regard - it’s just different.

With that said, both frameworks can still be used in a variety of ways. It’s just that Hyperapp presents a pure, no-bundlers ESM module way as an official recommendation for production.

## Templating syntax

As far as creating your views go, both React and Hyperapp work similarly under-the-hood. It’s just that React’s support and push towards JSX made it  the go-to choice.

In the case of Hyperapp, again, the support for JSX is [in the works](https://github.com/jorgebucaran/hyperapp/pull/1011), but it’s not the primary focus anyway. Instead, it’s the `React.createElement()` equivalent in the form of `h()` , and `text()` functions. If you’re wondering why `text()` is a thing - it’s for [additional performance](https://github.com/jorgebucaran/hyperapp/issues/1009#issuecomment-757694817).

If you don’t want to repeatedly type `h(“tag”, …)`, then you could look into the official `@hyperapp/html` package. It’s a collection of shortcut functions that simplify `h()` calls to `tag()` calls, while still being usable without bundlers. Here’s an example:


```js 
<!DOCTYPE html>
<html lang="en">
  <head>
    <script type="module">
      import { app } from "https://unpkg.com/hyperapp"
      import {
        main,
        h1,
        button,
        text,
      } from "https://unpkg.com/@hyperapp/html?module"

      const Subtract = (state) => ({ ...state, count: state.count - 1 })
      const Add = (state) => ({ ...state, count: state.count + 1 })

      app({
        init: (count = 0) => ({ count }),
        view: (state) =>
          main([
            h1(text(state.count)),
            button({ onclick: Subtract }, text("-")),
            button({ onclick: Add }, text("+")),
          ]),
        node: document.getElementById("app"),
      })
    </script>
  </head>
  <body>
    <main id="app"></main>
  </body>
</html>
```


**JSX support**
With that said, it’s easy to see how JSX might still be more appealing than both of the provided syntaxes.

To be fair, [there are ways](https://github.com/jorgebucaran/hyperapp/issues/989#issuecomment-732165556) to use Hyperapp with JSX or even template literals already. It’s just that as long as the official support is in development, it’s hard to pick the “go-to” implementation.

Hyperapp’s flexibility allows you to use many view-constructing syntaxes, given some time and effort. Though, most of them will come with additional complexity and potential performance decrease.

## Components

Both Hyperapp and React allow you to create components for better reusability of UI logic.
In React, components can be created as plain functions but with predictable structure, and they can have a local state.


```js
const Example = (props) => {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>{props.children}</button>
    </div>
  );
}
```


As for Hyperapp, its components look similar. However, unless you want to use a specific templating syntax, you don’t have to stick to any structure. Just make it so that your function results in the creation of some virtual nodes ( `h()` and `text()` returns), and that’s it!


```js
const container = (content) => h("div", { class: "container" }, text(content));
```


Now, arguably you could do the same in React without JSX, but it wouldn’t seem equally natural. And aside from that, passing accepting `props` object as the only parameter is a good base practice.

**No local state**
With that said, there’s one big difference between Hyperapp and React components, and it’s the absence of a local state.

In Hyperapp, the entire state is defined at the very start of an app and has to be passed down the node tree. It can then be interacted with using actions, effects and subscriptions.

## Reactivity

Implementation of reactivity (state management, view updates, etc.) is where the two frameworks differ immensely.

**React hooks**
On the side of React, **hooks** have been the primary way of adding reactivity to your components for quite a while now.


```js 
import React, { useState, useEffect } from "react";

const Example = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
};
```


They’ve been well received, and the core ideas behind them have inspired the likes of [Vue’s Composition API](https://v3.vuejs.org/guide/composition-api-introduction.html) and a big part of [Solid’s API](https://github.com/ryansolid/solid/blob/master/documentation/api.md).

**Hyperapp API**
Hyperapp takes a different approach. Instead of just hooks, you’ve got actions, effects, and subscriptions.

It might seem a bit complex at first, but it really isn’t, and additional separation allows for an easier understanding of all the concepts, which are just that - concepts! They’re either simple functions with expected input and output or data structures fitting given requirements.

There are no additional APIs. In fact, the entire API of the framework is just the `h()` and `text()` templating functions, as well as `app()` serving as an entry point for any Hyperapp app and `memo()` for easy view memoization. That’s fewer functions in the entire framework than [core hooks](https://reactjs.org/docs/hooks-reference.html) in React!

Now, we won’t be diving deep into these Hyperapp concepts and APIs; however few there might be. Instead, let’s do a quick rundown.
**App**
We start from an `app()` call, which initializes and mounts Hyperapp to DOM.


```js
import { h, text, app } from "https://unpkg.com/hyperapp";

app({
  init: { todos: [], value: "" },
  view: ({ todos, value }) => h("main", {}, []),
  node: document.getElementById("app"),
});
```


`init` is where the state gets initialized, `view` serves as the main rendering function, and `node` as the mounting point.

**Actions**
To **change the state**, you have to use actions - functions which, given the current state and additional payload, output a new state.


```js 
const AddTodo = (state) => ({
  ...state,
  value: "",
  todos: state.todos.concat(state.value),
});

const NewValue = (state, event) => ({
  ...state,
  value: event.target.value,
});

app({
  init: { todos: [], value: "" },
  view: ({ todos, value }) =>
    h("main", {}, [
      h("h1", {}, text("To do list")),
      h("input", { type: "text", oninput: NewValue, value }),
      h(
        "ul",
        {},
        todos.map((todo) => h("li", {}, text(todo)))
      ),
      h("button", { onclick: AddTodo }, text("New!")),
    ]),
  node: document.getElementById("app"),
});
```


You can use actions in DOM event listeners, subscriptions, and even the `init` property for complex state initializations.

**Subscriptions**
Subscriptions provide **a way for dispatching actions**, and related side-effects, based on events outside of Hyperapp’s control.


```js
const keySub = (dispatch, props) => {
  const handler = (ev) => {
    if (props.keys.includes(ev.key)) {
      // Dispatch action
      dispatch(/*...*/);
    }
  };
  window.addEventListener("keydown", handler);

  // Cleanup function
  return () => window.removeEventListener("keydown", handler);
};
// Helper
const key = (props) => [keySub, props];

// Usage
app({
  // ...
  subscriptions: (state) => [
    key({
      keys: ["w", "a", "s", "d"],
      action: ChangeDirection,
    }),
  ],
});
```


Subscriptions are tuples, i.e., 2-element arrays, where the first value is the subscription’s function, and the second one is props to be passed to it.

You can register subscriptions from the `app()` call under the `subscriptions` property. There, you can add or remove your subscriptions based on the current state.

**Effects**
As for the mentioned effects, they can be used for handling **side-effects**, like data fetching.


```js 
const httpFx = (dispatch, props) => {
  // Side-effect
  fetch(props.url, props.options)
    .then((res) => res.json())
    .then((data) => dispatch(/*...*/)); // Dispatch action
};
// Helper
const http = (props) => [httpFx, props];
// Usage - action
const GetPizzas = (state) => [
  state,
  http({
    url: "/pizzas",
    action: SetPizzas,
  }),
];

// Usage - view
h("button", { onclick: GetPizzas }, text("Get pizzas"));
```


Like subscriptions, effects are tuples consisting of a function and props and can be executed by actions when they return an array of the new state and all the effects instead of the new state directly.

# Observability for Production React Apps

Debugging React apps in production may be challenging and time-consuming. Asayer is a frontend monitoring tool that replays everything your users do and shows how your app behaves and renders for every issue. It’s like having your browser’s inspector open while looking over your user’s shoulder.

![Asayer Frontend Monitoring](https://asayer-content.s3.eu-central-1.amazonaws.com/eb8f28fc4852447eb750d1747c53e692.png)

Asayer helps to quickly get to the root cause by reproducing issues as if they happened in your own browser. It also monitors your frontend performance by capturing key metrics such as page load time, memory consumption and slow network requests as well as *Redux* actions/state.

Happy debugging, for modern frontend teams - [Start monitoring your web app for free](https://asayer.io/register.html).


# Comparing ecosystems

With performance and API behind us, all that’s left to investigate is the ecosystem, community size, and documentation.

## Ecosystem and community

In terms of ecosystem and community, it’s become clear that unless you’re React, Vue, or Angular, you are going to struggle. Although some recent trends push for framework-independence, these 3 UI frameworks, and especially React, still have strong backing in this regard.

Hyperapp isn’t an exception. It doesn’t have nearly as large a community and an ecosystem of a tiny fraction of React’s size. Again, this doesn’t at all mean that it’s useless.

The community, however small, is very committed and actively works on improving the framework. Hyperapp works beautifully with framework-independent tools, CSS frameworks, and a handful of [official](https://github.com/jorgebucaran/hyperapp#packages) and [3rd-party](https://github.com/jorgebucaran/hyperawesome) dedicated libraries available.

## Documentation

Now, documentation is the go-to resource for starting with any framework or library.

React docs are really good - not perfect, but close. There’s a [dedicated landing page](http://reactjs.org/), detailed API docs and concept overviews, a full-blown introductory tutorial, and a community directory with useful links, of which there are tons and more unlisted.

As for Hyperapp, docs are surely not its strength. There’s no dedicated landing page (though [there was one](https://github.com/jorgebucaran/hyperapp/pull/900) for [a brief period of time](https://github.com/jorgebucaran/hyperapp/issues/961#issuecomment-660227403)), and limited documentation consisted of 2 Markdown files and a README file.

On the contrary, given Hyperapp’s simplicity, it could be argued it doesn’t need as much documentation as, e.g., React. The current one-page API docs and tutorial should be enough to give the developer a good understanding of the tool. There’s also some good [community-written content](https://github.com/jorgebucaran/hyperawesome).

At the time of writing, there’s an [open PR](https://github.com/jorgebucaran/hyperapp/pull/1042) with a massive amount of new docs, so it’s worth keeping an eye on it.

# Summary

So, to summarize this comparison, is Hyperapp a *“React killer”*? No, but it is a **good alternative** for those striving for simplicity, performance, or both. Those two are the primary focus of Hyperapp’s development.

The future looks bright for Hyperapp. Its most notable pitfalls, like JSX support, TypeScript typings, or good documentation, have [opened PRs](https://github.com/jorgebucaran/hyperapp/pulls). The framework is constantly being improved upon and has a **clear goal**.

So, if this comparison made you interested in Hyperapp, go [check it out](https://github.com/jorgebucaran/hyperapp)!