Skip to content

Instantly share code, notes, and snippets.

@artemave
Last active July 5, 2019 14:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save artemave/cd793c6d7670811ce341deb67ed8d9a5 to your computer and use it in GitHub Desktop.
Save artemave/cd793c6d7670811ce341deb67ed8d9a5 to your computer and use it in GitHub Desktop.
hyperdom vs mithril.md

Mithril vs Hyperdom

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 both operate under these two fundamental assumptions: "component instances are not recreated on each render" and "automatically redraw everything 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 a regular 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 passing the ultimate state management test:

my js state management library: {}

— TJ Holowaychuk 🙃 (@tjholowaychuk) January 29, 2018
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

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 again 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.

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.

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).

Links

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 using just javascript you already know.

In code, the difference looks like this:

Hyperdom:

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

Mithril:

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

return 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 only 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.


random stuff:

view components in hyperdom, but not in mithril?

they seem to have their own implementation of virtual dom

docs aren't great - couldn't find how to use input binding; passing url params with layout wasn't in the docs either - I had to figure it out myself

hyperdom mutates component object, whereas mithril's API is provided by a module

no "create mithril app"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment