Mithril vs Hyperdom

By Artem Avetisyan on July 12, 20197 min read

Mithril is a “A modern client-side Javascript framework for building Single Page Applications”. So are many other frameworks out there. So is my personal favorite - Hyperdom. What sets Mithril apart from the some other frameworks that I looked into is just how similar it is to Hyperdom in terms of development experience.

This is because they are based on similar programming ideas: “component lifecycle is not managed by the framework (that is, components are not recreated on each render)” and “automatically redraw all components on some common events”.

In practice, that means that there is no reason for the framework to manage application state. State can be simply stored in plain javascript objects that have nothing to do with the framework. If you’re familiar with React, imagine you could write state.foo = 'bar' (state being some global javascript object) instead of this.setState({foo: 'bar'}) and bar will still show up in the DOM. Even if state.foo is referenced from other components, they will all pick up the change and update their views.

In other words, both Mithril and Hyperdom are compatible with the ultimate state management solution:



To get a real taste for it, I made a small app in both technologies and they look strikingly similar. You can play with both of them on codesandbox: mithril beer app and hyperdom beer app

The project, albeit small, covers the following key points:

  • routing
  • state
  • xhr
  • input binding
  • layout
  • components composition

As mentioned above, the result was similar, but there were of course differences and the rest of this post will go over those that I managed to spot.

Disclaimer: I know Hyperdom reasonably well but I was using Mithril for the first time. It’s possible that some of the comparison below is unfair/incorrect.

Automatic redrawing

As mentioned above, both Hyperdom and Mithril feature automatic rerender of the entire component tree after certain common events. These are:

  • bound input change
  • onclick event handler
  • navigation
  • xhr response

While first three work identically in both frameworks, the last one differs a bit.

Hyperdom doesn’t actually do anything special on xhr, it simply triggers another render if an event handler happens to return a promise. So long as you’re using a promise based http client (or fetch api), it will naturally redraw after request is finished.

Mithril doesn’t have this special treatment of promises, instead it provides its own http client. Beside the “I have to learn the Mithril way of making xhr requests”, there are couple of other problems here. Consider this code:

  oninit (vnode) {
    m.request({
      method: 'GET',
      url: "https://api.punkapi.com/v2/beers"
    }).then(data => {
      this.beers = data
    })
  }

Mithril is going to render after then is executed.

It’s 2019, the immediate urge is to rewrite that code using async/await:

  async oninit (vnode) {
    this.beers = await m.request({
      method: 'GET',
      url: "https://api.punkapi.com/v2/beers"
    })
  }

But, alas, that does not work. I don’t know why.

UPDATE: this is apparently fixed in the upcoming v2 release (source)

Another thing with the custom http client: what if I am not using Rest API? What about Graphql? I didn’t find any Mithril Graphql clients that hook into autoredraw. Of course one can always call m.redraw() manually, but that’s not ideal.

Opting out of Autoredraw

Both Mithril and Hyperdom allow components to opt out of autoredraw framework. Mithril way is to mount component using m.render rather than m.mount/m.route. Since only top level component/routes are mounted, it was not clear to me how to opt out of autoredraw in some leaf component.

UPDATE: This has been clarified by a redditor in this comment

Hyperdom requires you to return hyperdom.norefresh() from an event handler to signal the opt out, so cancelling autoredraw on a case by case basis seems straightforward. There is also caching and the ability to take over the entire redraw control with refreshify.

Routing

Mithril routes are defined at the time the application is mounted. As a result all routes are listed in one place which is nice. Hyperdom on the other hand allows each component to specify which routes it wants to handle (the example projects reflects this well). This makes it harder to see “routing table” at a glance. I guess I’d prefer if it was more like Mithril (however, I haven’t used it in anger - there may be implications/restrictions that are not obvious from where I stand).

Hyperdom intercepts any link and checks if it matches any route. Mithril requires you to explicitly turn a link into the one handled by router. I prefer the Hyperdom way - it’s just one less thing to remember.

URL params binding

Hyperdom allows to bind URL query params onto the state much like an input binding. E.g., a user types into an input and the framework renders it in the DOM and in the URL. I haven’t found a way to achieve that in Mithril.

In-memory router

Hyperdom comes with an in-memory router implementation that can be used in tests. I haven’t found a similar thing in Mithril.

Components composition

Hyperdom does not impose any frameworky stuff onto how components are instantiated. Any javascript object with render/routes method can act as one. So creating components is no different from any other javascript object - just call a new on a class. Object literals are just as good.

Mithril instantiates components for you. That wouldn’t be worth mentioning if not for the fact that when you need to pass something from parent component to a child component, you need to figure out how to do it the Mithril way, rather than just using javascript you already know.

In code, the difference looks like this:

Hyperdom:

class Thing {
  constructor({stuff}) {
    this.stuff = stuff
  }
}
h(new Thing({stuff}))

Mithril:

class Thing {
  oninit(vnode) { // or any other lifecycle method
    const stuff = vnode.attrs.stuff
  }
}
m(Thing, {stuff})

On my toy project, that was merely a flavor difference, past the fact that I had to learn how to do this in Mithril.

Performance

Performance is a broad subject, but there is one aspect of it that I was particularly curious about - multiple DOM updates. Why is that interesting? For fast browser tests. Tests can click links and type into inputs extremely fast and if each of those events causes a DOM update, then it better be fast.

To get an answer to this question, I made another little project that runs a simple test for the same app implemented in both frameworks. The test types in 4000 characters into an input. Input value is bound onto a div. The test is complete once all 4000 characters show up in that div.

After running this test a few times, Mithril came out a winner, albeit by a small margin.

I also couldn’t help but include React into the comparison and, on that particular test, it scores ten times slower then either Mithril or Hyperdom!

Conclusion

This is by no means an exhaustive comparison. But, surely, the only one that exists in the observable universe - so there you go ;)

I haven’t encountered any killer features of Mithril over Hyperdom. So, as someone who is already familiar with Hyperdom, I’d stick to it. Having said that, bar some minor issues, Mithril looks solid. Something I’d chose over React any day.