

Virtual DOM in Elm - chunkstuntman
http://elm-lang.org/blog/Blazing-Fast-Html.elm

======
loz220
Having done some benchmarks with TodoMVC before, I knew something was off
about these results.

They play to the strengths of the virtual dom approach by manipulating the
benchmark via dom instead of what ever interface was implemented within each
TodoMVC implementation.

So I forked the benchmark and changed both the Backbone and Mercury
implementations to work through their respective apis.

Here it is: [https://github.com/smelnikov/todomvc-perf-
comparison](https://github.com/smelnikov/todomvc-perf-comparison)

as you can see the giant gap between Backbone and mercury is now gone while
both tests perform the exact same tasks. (feel free to step through it in the
debugger to see for yourself)

Here's my commit log: [https://github.com/smelnikov/todomvc-perf-
comparison/commit/...](https://github.com/smelnikov/todomvc-perf-
comparison/commit/7e1a5f8f4150a6436b9eeba233102b64a2bd5a78)

Note: I've added a new method to the Backbone implementation for toggling
completed state as the old one was horribly in-efficient. This is not
something inherit in Backbone but rather is specific to this TodoMVC
implementation. See my comments in the commit log.

Note 2: Exoskeleton, which is basically backbone without the jQuery dependency
is roughly 2-3x faster than vanilla backbone, I'm going to predict that it
will actually be significantly faster than mercury.

Note 3: I think the virtual dom is great and seemingly has many benefits but I
feel as though the speed benefit has been greatly exaggerated.

~~~
Raynos
In all javascript apps the part that is slow is the DOM, not the javascript
interface.

This benchmark was taken from the webkit source code then forked into
[http://vuejs.org/perf/](http://vuejs.org/perf/) then forked to include
mercury then forked again to include elm.

Neither elm nor mercury came up with this benchmark and just added themself to
it.

What this benchmarks shows is that async rendering is really fast. Mercury,
vue & elm all use async rendering where DOM effects are batched and only
applied once.

A better, close to real user experience benchmark would be one using mousemove
since that's an event that can happen multiple times per frame.

~~~
loz220
There is interaction occurring with the DOM in both benchmarks.

The way that the Backbone TodoView is designed does not take into account the
possibility of a user adding 100 items using the dom within a tight loop.
Probably because such a use case is impossible outside of this type of
benchmark. By doing so the Backbone implementation ends up performing a lot of
unnecessary renders. Therefore as far as Backbone performance is concerned
this benchmark is not indicative of any real world scenario.

Just to re-iterate; when you're loading a set of todos from your local storage
to display when the user first opens the page, you would not populate the "new
todo" input box and fake an enter event for each item that you want to add.
Instead you would reset the Backbone.Collection with a list of your new todos
(go through the interface). That's basically the change I made to the
benchmark. Sorry if it wasn't clear.

~~~
jbish
Running your perf test, I consistently get Backbone being the fastest, Angular
the slowest, and the projects using Virtual DOM approach somewhere in the
middle. Is that expected? [http://evancz.github.io/todomvc-perf-
comparison/](http://evancz.github.io/todomvc-perf-comparison/)

edit: I was running the original test instead of your fork.

------
danabramov
There's another advantage to virtual DOM: you can hot-swap code while
developing and have React run its diff algorithm without reloading the page.
If your component has no (or little) side-effects, it means you get live
reload as you edit for free. This is impossible with Backbone/jQuery soup of a
view.

See my proof of concept video:
[https://vimeo.com/100010922](https://vimeo.com/100010922)

And actually runnable example that you can edit without refresh:
[https://github.com/gaearon/react-hot-
loader](https://github.com/gaearon/react-hot-loader)

I plan to write a blog post explaining how to integrate it into React project
soon.

~~~
dustingetz
Yeah, the Om beginner tutorial demos this in lighttable with clojurescript,
it's pretty epic.

edit: [https://github.com/swannodette/om/wiki/Basic-
Tutorial](https://github.com/swannodette/om/wiki/Basic-Tutorial)

~~~
danabramov
Where can I take a look? In my case, this is pure JS, no Light Table or
browser plugins needed. No messing with V8.

~~~
swannodette
Om hot reload doesn't require Light Table or browser plugins, just eval
support from your editor setup. There's no way to see this easily beyond going
through the Om tutorial. But as you say this is just a benefit that more or
less falls out of React if you're careful w/ state - Devcards is another
ClojureScript example of the possibilities -
[http://rigsomelight.com/2014/06/03/devcards-taking-
interacti...](http://rigsomelight.com/2014/06/03/devcards-taking-
interactivity-to-the-next-level.html)

------
jlongster
This technique may be the most revolutionary thing in web development in the
last several years, IMHO. I've been using React for a while, and starting to
integrate Mori for persistent data structures, and the things I can do with it
is insane. The fact that it's not only far better performant, but a way better
abstraction for dealing with UIs, is crazy.

~~~
ghayes
If this technique is much more performant (batching diffs of the DOM), why
don't browsers perform this "back-buffering" natively?

~~~
nostrademons
They do perform this "back-buffering" natively. When you manipulate the DOM in
a modern (post-2009 or so) browser, it's just changing a pointer and flipping
a dirty bit.

The problem is that it's _very_ easy to force a full recalculate of the whole
page layout. Whenever you call .offsetHeight or .offsetWidth or
.getComputedStyle, you're doing it. The full list of properties is about 2
dozen strong:

[http://gent.ilcore.com/2011/03/how-not-to-trigger-layout-
in-...](http://gent.ilcore.com/2011/03/how-not-to-trigger-layout-in-
webkit.html)

Most web developers don't know this, and so they're actively making their
pages slow. Worse, many popular frameworks build this into the library, and so
if you use them, there is _no way_ to keep your pages responsive. JQuery, for
example, can easily cause 4-5 layouts with a single call to .css; on a mobile
phone and a moderately complex page, that's about a second of CPU time.

~~~
joesb
This got me wondering how React handles this requirement. Can you use React if
you need to know offsetWidth/Height to do complex layout?

~~~
ksherlock
Once the component is mounted (there's a componentDidMount callback) you have
access to the real DOM node and can access them (or perhaps store them as a
property if necessary). The DOM node is also available from event callbacks
and such.

------
colinramsay
My first thought when looking at the benchmarks is that I find it strange that
Backbone is faster than React. Not that I imagine Backbone to be slow,
particularly, just that this article is about one of React's key features -
the virtual DOM - and that's something which Backbone doesn't have. I'd expect
to see React up there with Om, Mercury & Elm.

I've just had a look at the code and React is using a localstorage backend,
while Backbone is using its models + collections with a localstorage
extension... so I'd expect there to at least be some overhead there, but
apparently not.

Does anyone have any quick thoughts on what might be happening here? I can't
shake the feeling that these benchmarks might not be terribly useful.

~~~
pestaa
I've seen some AngularJS vs React benchmarks a while back. I believe it was
[http://jsperf.com/angular-vs-react/5](http://jsperf.com/angular-vs-react/5)

I consistently got the result of Angular utterly and completely destroying
React. Initially I blamed the virtual DOM approach, but after seeing other
frameworks utilizing it and outperforming Angular by a huge margin, it seems
to me that React is not written for performing well on small DOM documents.
(There might be a turning point, considering how bloated Facebook pages it was
designed for.)

~~~
syntern
Our performance benchmarks suggest that application performance is most
certainly better off with: (a) DOM reuse (b) calculating expensive things only
once (c) reducing GC pressure == not discarding/recreating things (d)
coordinating actions that may trigger reflow. It is independent of what
framework you are using.

My limited understanding of React is that it fails in (a), (b) and (c), and
only limited measures can be applied to improve them. Re-creating the entire
DOM on each update probably does not help. I have no information if (d) is
possible with it.

I am using Angular.dart for a while now, and it can be used to get all of them
in an optimal way.

Disclaimer: I'm working at Google.

~~~
vjeux
(a) You can use the key attribute in order to get DOM reuse. If you are
looping over N keys then React is going to reuse the nodes and move them
around.

(b) You can implement shouldComponentUpdate in order to have a quick way not
to re-render a sub-tree if nothing changed.

(c) See (b) but we're also working on changing the internal representation of
the virtual DOM to plain js objects that can be reused[1]. We were super
worried about GC but it turns out that it hasn't been the bottleneck yet for
our use cases.

(d) If you are writing pure React, all the actions are batched and actually,
it's write-only, React almost never reads from the DOM. If you really need to
read, you can do it in componentWillUpdate and write in componentDidUpdate.
This will coordinate all the reads and write properly.

A really important part of React is that by default this is reasonably fast,
but most importantly, when you have bottlenecks, you can improve performance
without having to do drastic architecture changes.

(1) You can implement shouldComponentUpdate at specific points and get huge
speedup. We've released a perf tool that tells you where are the most
impactful places to[2]. If you are bold, you can go the route of using
immutable data structures all over the place like Om/the elm example and
you're not going to have to worry about it.

(2) At any point in time, you can skip React and go back to raw DOM operations
for performance critical components. This is what Atom is doing and the rest
of their UI is pure React.

[1] [https://github.com/reactjs/react-
future/blob/master/01%20-%2...](https://github.com/reactjs/react-
future/blob/master/01%20-%20Core/05%20-%20Descriptors.js) [2]
[http://facebook.github.io/react/docs/perf.html#perf.printwas...](http://facebook.github.io/react/docs/perf.html#perf.printwastedmeasurements)

~~~
auggierose
I am currently writing an implementation of React in Scala.js. It's inspired
by React and by the documentation of React, but I have not looked at the
actual source code so far.

You seem to be an implementor, so two questions that maybe spare me looking at
the source code :-)

1\. How do you batch updates? 2\. I am currently using an algorithm for
longest increasing subsequences for avoiding superfluous dom insertions of
children when diffing lists. I also make sure that the node containing the
active element will not be removed from the tree during the diffing (if
possible at all). Are you doing the same?

~~~
vjeux
1\. The boundaries are currently at event loop. Whenever an event comes in, we
dispatch it to React and every time the user calls setState on the component,
we mark it as dirty. At the end of that dispatch, we go from top to bottom and
re-render elements.

It's possible to change the batching boundaries via "Batching Strategies" but
we haven't exposed/documented it properly yet. If you are interested, you can
look at requestAnimationFrame batching strategy.
[https://github.com/petehunt/react-raf-
batching](https://github.com/petehunt/react-raf-batching)

2\. We cannot really use normal diff algorithms for list because elements are
stateful and there is no good way for React to properly guess identity between
old and new. We're pushing this to the developer via the `key` attribute.

See this article I wrote for a high level overview of how the diff algorithm
is working:
[http://calendar.perfplanet.com/2013/diff/](http://calendar.perfplanet.com/2013/diff/)

~~~
auggierose
Thanks a lot, very useful info. Yes, I know that because of state "diff" is
really not diffing but synchronization of "blueprints" with actual
"components". Still, after the update the order of the existing children of a
node might have changed, and it is possible to devise a simple, not too costly
(n log n, n is the number of children), and optimal strategy for rearranging
the nodes.

------
glibgil
Why so many strings and so few types, especially for somethings like Elm?

The same in PureScript would be

profile user = mkUI spec do div [ className "profile" ] [ img [ src
user.picture ] , span [ text user.name ] ]

See, types everywhere. [https://github.com/purescript-contrib/purescript-
react/blob/...](https://github.com/purescript-contrib/purescript-
react/blob/master/example/tutorial/tutorial.purs)

~~~
brandonbloom
Because the underlying DOM is essentially untyped. This is a low-level adaptor
library. One could easily build a more strictly typed wrapper on top.

~~~
glibgil
Let me rephrase the question: why haven't they already?

~~~
brandonbloom
Why haven't _you_ already?

They're literally announcing the untyped base library layer and the post
specifically calls out the desire to build higher level abstractions. Elm is
moving incredibly fast, so your question is totally unreasonable.

~~~
glibgil
I don't use Elm. I'm more interested in PureScript. But, the API looks silly
for a Haskell like language. There is some positive feedback for whoever
cares. Like, I wouldn't take that API out in public because everyone will
think, "why is there string-based programming in Elm?"

------
EvanYou
Virtual DOM is fast, but it's not the only way to provide free and fast DOM
updates. The author removed the Vue.js implementation from the benchmark,
which does not use Virtual DOM but is as fast or even faster than Mercury and
Elm.

Disclaimer: I'm the author of Vue.js. The benchmark is a fork of a fork of the
Vue.js perf benchmark ([http://vuejs.org/perf/](http://vuejs.org/perf/)).

~~~
girvo
Ah! Well done on Vue. I'm finding it quite nice to play with outside work,
currently we're using Mithril for our "small-non-angular" apps and components,
but unfortunately while I adore it, a lot of the other devs Javascript isn't
strong enough to deal with the flexibility Mithril gives you. Does Vue have
the same issue? From what I've seen it sort of does, but with a bit more
structure thus mitigating it a little. Thoughts?

------
saurabhnanda
As a startup who's built all of its UI on AngularJS, does introducing React/Om
into the stack make sense? We have a B2B product where the users deal with a
lot of CRUD forms and dashboards.

React looks interesting, but only if it gives significant advantages (time-to-
market, maintainability, etc) vis-a-vis AngularJS in managing a large code-
base.

Any first hand reviews?

~~~
dustingetz
YES - I've built two enterprise app frontends (CRUD forms and dashboards) in
react since react came out. Now that I know what I'm doing and have my tools
built out, I am contracting and hitting ridiculously tight schedules that I
would never have been able to hit without the level of abstraction react
enables. It helps that a react codebase is massively smaller than a comparable
OOP-style codebase (I've used Backbone, Knockoutjs, and ExtJS in comparable
apps)

~~~
porker
Are there any helpers/libraries you use when building CRUD forms? I haven't
seen one for React yet - or TBH a well functioning one in any language I use -
and it's a pain point I would like to solve.

~~~
dustingetz
[https://github.com/wingspan/wingspan-
forms](https://github.com/wingspan/wingspan-forms)
[https://github.com/dustingetz/react-
cursor](https://github.com/dustingetz/react-cursor)
[http://www.dustingetz.com/2014/02/18/react-dynamic-
forms.htm...](http://www.dustingetz.com/2014/02/18/react-dynamic-forms.html)

~~~
porker
Fab thank you! I look forward to exploring these... any system that makes CRUD
less painful is a system I want :)

------
andrewljohnson
All of the empty brackets to describe the virtual DOM looks bad to me. I'm
curious if this is regarded as a language smell.

~~~
wetmore
It's easy to use partial application to get rid of that problem: the type of
the function node is:

    
    
      node : String -> [Attribute] -> [CssProperty] -> [Html] -> Html
    

We could define a convenient function div, for example, with

    
    
      div : [Attribute] - > [CssProperty] -> [Html] -> Html
      div = node "div"
    

that would let us say "div [] [] [text "Hello world"]" instead of "node "div"
[] [] [text "Hello world"]". Of course, this doesn't fix your problem with the
empty brackets. This can be fixed with something like:

    
    
      bareDiv : [Html] -> Html
      bareDiv = div [] []
    

letting us do "bareDiv [text "Hello world"]"

------
pestaa
Elm looks extremely promising to me. However, I tried the Elm's implementation
of TodoMVC here:
[http://evancz.github.io/TodoFRP/](http://evancz.github.io/TodoFRP/) and got
an unusable list of strings with basically none of the features seen in other
prototypes. Is it the one used in these benchmarks? What results would we see
in an identical test?

Update: the benchmark uses a correct implementation available here:
[https://github.com/evancz/todomvc-perf-
comparison/tree/maste...](https://github.com/evancz/todomvc-perf-
comparison/tree/master/todomvc/elm) so it was a false alarm on my part. Tried
it out at [https://rawgit.com/evancz/todomvc-perf-
comparison/master/tod...](https://rawgit.com/evancz/todomvc-perf-
comparison/master/todomvc/elm/index.html)

~~~
michaelbjames
I think you may have gone to the wrong address. The second link in the article
is the most recent implementation (using a virtual DOM)

[http://evancz.github.io/elm-todomvc/](http://evancz.github.io/elm-todomvc/)

------
polskibus
I've never heard about mercury before! Great to see more virtual DOM
development.

I hope the new framework gets animation right, I'd love to see it as flexible
as in D3, in my opinion this is something currently React currently lacks (the
transitions are there but they are too simplistic to cover complex web app
cases).

~~~
chenglou
Care to try out [https://github.com/chenglou/react-tween-
state](https://github.com/chenglou/react-tween-state)? I've been experimenting
with animation in React and would love to see the general paradigm behind this
and implement it in React.

------
LukeHoersten
Anyone know of a Haskell or proper Haskell subset that can do something
similar (hopefully batteries included)?

~~~
takeoutweight
Absolutely not "batteries included" but I've put some work into a React
interface for Haste, which is a Haskell->JS compiler.

[https://github.com/takeoutweight/shade](https://github.com/takeoutweight/shade)

~~~
LukeHoersten
Awesome. Thanks a lot.

------
xkarga00
You can run the tests by yourself here: [http://evancz.github.io/todomvc-perf-
comparison/](http://evancz.github.io/todomvc-perf-comparison/)

------
lightblade
I've been thinking why there's no virtual dom implementation other than react.
Glad to see there's finally some out there.

~~~
skybrian
If you're using Dart, I have an experimental implementation:
[https://github.com/google/dart-tagtree](https://github.com/google/dart-
tagtree)

I'm sure others are experimenting as well, so we should see more
implementations soon.

------
batiste
Elm seems Incredibly fast. By using requestAnimationFrame like described in
the article I managed to bring my own library to satisfying performances
[https://github.com/evancz/todomvc-perf-
comparison/pull/1](https://github.com/evancz/todomvc-perf-comparison/pull/1)

------
boubiyeah
What could possibly be done in the React.js version to make its performance
closer to Mercury/Elm? I notice some 'shouldComponentUpdate' methods are
already overwritten.

Or is this the limit of an overly mutable language like js?

~~~
boubiyeah
After investigation, it turns out react has the potential to be faster than Om
if it fixed its batched updates. Another huge performance issue is
Function.prototype.bind which is called extensively.

The Om example does some kind of Event delegation using channels which is much
faster.

------
nickthemagicman
Anybody have any good reviews on elm?

~~~
kasbah
It is fun to play with and I managed to pick it up very quickly (after having
already spent a significant amount of time learning Haskell).

I am a bit concerned about the lack of typeclasses and what that could mean if
I try and build something bigger using it. Maybe I could use Purescript and
Elm together.

~~~
judk
Type classes are syntactic sugar for explicit dictionary (record) passing.

