

On "The Future of JavaScript MVC Frameworks" - swah
http://blog.reverberate.org/2014/02/on-future-of-javascript-mvc-frameworks.html

======
swannodette
I appreciate the skepticism :) But remember the goal of Om is to be simple and
fast in more scenarios than are allowed in traditional designs. Emphasis on
_simple_.

Take a look at my undo example implemented in about 5 lines of relevant code
[http://swannodette.github.io/todomvc/labs/architecture-
examp...](http://swannodette.github.io/todomvc/labs/architecture-examples/om-
undo/index.html). Note the render time when undoing changes, much less than
16ms. That's undo playback at 60 frames per second.

The ability to snapshot the application state is not a small thing, it's a
global application concern with lots of benefits besides user level undo - UI
rollback on network request failure, app state restoration sans boilerplate
when the user cancels something, simpler model for offline applications, and
UIs based on scrubbing come immediately to mind.

I'm sure you could achieve similar or better performance in a system that
relies on events with batching and mutable objects but I'm not convinced that
it can be done as simply as we can do it today in Om.

~~~
skrebbel
David, thanks for your work on Om and, especially, writing about it. Your
writing introduced me to React and how to use it with a de-emphasis on state.
Having very little FP experience and zero Clojure experience, however,
sometimes you're going a little fast for me, so was hoping whether you or
someone else who reads this could help me out.

I don't completely understand how entirely immutable data can be effectively
used in front end apps (I do understand how, _if_ you have immutable data,
React can be made fast and undo is easy). If data changes, it needs to mutate,
right? Do you then maintain one global object that does change, and have that
object be a giant immutable tree with all model data? Or am I completely
missing the approach?

If that's the way, how can you efficiently do updates on relatively large
chunks of data? Say, I'm coding Gmail and my user wants to star the 2053rd
email in a list? Doesn't that mean 2052 items in a list have to be copied? In
general, doesn't this make model code much much more complicated? Or am I
missing something and is it really not that bad?

Yeah, a bit many question marks, but I hope you catch my drift :)

If anyone could point me to some good reading about thinking in immutable data
terms, that would be warmly appreciated too. Thanks!

~~~
grayrest
In order to understand the frame of reference for Om, you really need to watch
some Rich Hickey presentations [1][2] (or have a good understanding of
functional programming). The first one is more relevant to your questions
here, the second is just because Clojure programmers have very specific
definitions of "simple" and "easy" and because it's a good talk.

[1] [http://www.infoq.com/presentations/Are-We-There-Yet-Rich-
Hic...](http://www.infoq.com/presentations/Are-We-There-Yet-Rich-Hickey) [2]
[http://www.infoq.com/presentations/Simple-Made-
Easy](http://www.infoq.com/presentations/Simple-Made-Easy)

That said, on to answering your questions:

> If data changes, it needs to mutate, right? ...

Data doesn't change in this model (see [1]). Incoming events (keyup, network
request, page load) occur and your program responds to those events by
producing a new, immutable set of data that you're going to use going forward.
Om apps keep one reference to the root of the data tree–Om examples use app-
state as the name–which represents the official current state of the data. You
can reasonably argue that swapping out app-state is changing data except that
anything that had a reference to the previous root(s) still has that reference
and you have to go dereference app-state to get the current value instead of
having it changed out from under you.

> If that's the way, how can you efficiently do updates on relatively large
> chunks of data? ... Or am I missing something and is it really not that bad?

[3] [http://eclipsesource.com/blogs/wp-
content/uploads/2009/12/cl...](http://eclipsesource.com/blogs/wp-
content/uploads/2009/12/clojure-trees.png)

Clojure(script)'s data structures use structural sharing. The above picture
shows inserting a single node into the middle of the structure. The red
outlined nodes are the parents which need to be copy+updated, as shown on the
right with all the dotted lines being shared references. The most misleading
thing about the picture is that the actual cljs trees have 32 branches at each
node instead of 2 or 3 so the lookup time is log32 N (basically constant [4],
impl in a systems language vs classic datastructures for comparison). In your
Gmail example you'd have to make ~3 new nodes.

[4] [https://github.com/michaelwoerister/rs-persistent-
datastruct...](https://github.com/michaelwoerister/rs-persistent-
datastructures)

> In general, doesn't this make model code much much more complicated?

It requires a different mindset and generally some helper code. In Clojure
using Om it's pretty straightforward once you're over the initial hurdle. In
javascript using Mori [5] it looks a lot like awkward Backbone with very heavy
Underscore use. I've poked around at it and if I were going to try to adopt
Mori+React for a real project I'd want to do some quality of life tweaks on
Mori. Mostly setting a prototype on the Mori objects to get the feel closer to
Backbone+Underscore and trying to get the data structure console output to be
more useful.

[5] [https://github.com/swannodette/mori](https://github.com/swannodette/mori)

~~~
skrebbel
Thanks a lot for the detailed explanation. I'll watch the videos (saw the
Simple/Easy one already, they're good definitions of the words and it would be
great if they'd be adopted more broadly outside the Clojure community too).

I think I'm catching the drift here. This app-state variable was the concept I
was missing. That, and how smart the Clojure(Script) data structures really
are.

Mori looks pretty damn nice, actually. Would consider using it in practice.

------
scelerat
In OP's conclusions: "the DOM is slow, so use a framework that batches updates
to it."

It would be nice to see a comparison of React/Om with some less naive approach
to handling Backbone events.

In my own apps, for instance, I've dealt with rapid batch DOM updates simply
by putting a short timeout on the event handler and canceling it if another
draw-inducing event arrived in the timeframe (5ms, let's say). Doing that has
led to similar order-of-magnitude speedups, and while not as sophisticated as
using requestAnimationFrame, has been sufficient and maintainable.

~~~
jbeja
Do you have a open example to this? I would like to know how is implemented.

~~~
eddieplan9
He just described the _.debounce function in underscore.js

[http://underscorejs.org/#debounce](http://underscorejs.org/#debounce)

------
drfloob
Well, we can check a few of those guesses pretty easily. Here is the stock
React TodoMVC demo augmented with Swannodette's benchmarks:

[http://drfloob.github.io/todomvc/architecture-
examples/react...](http://drfloob.github.io/todomvc/architecture-
examples/react/)

Benchmark to your heart's content! On my system, Om is about 4x faster on
benchmark 1, and 1000x faster on benchmark 2. I don't think
requestAnimationFrame has a lot to do with it, and immutability only slightly
more; I think the real performance gain is in having an application data
policy tailored to make the most of React's behavior.

To plug my own work a bit, I built a TodoMVC example with React and my own
immutable data structure library in pure JS. It performs a lot like Om on both
benchmarks, and actually seems a bit snappier in places, like toggling and
clearing all completed todos:

[http://drfloob.github.io/todomvc/labs/dependency-
examples/re...](http://drfloob.github.io/todomvc/labs/dependency-
examples/react__tree/)

~~~
haberman
Thanks for posting this; I wished when writing the article that I had plain
React benchmarks to run.

However when I profiled your benchmark it seems like it is invoking React's
event loop more than I would expect (and more than the React/Om benchmark
does) -- do you have any idea why this would be? It seems like for this to be
apples to apples, it shouldn't be invoking React's update logic (ie. re-
rendering everything) until the end of the benchmark. Perhaps this difference
is because of requestAnimationFrame?

I don't understand this comment:

> I don't think requestAnimationFrame has a lot to do with it, and
> immutability only slightly more; I think the real performance gain is in
> having an application data policy tailored to make the most of React's
> behavior.

As I mentioned in my article, "Benchmark 2" is a no-op on the DOM. So
literally all React should be doing is calling render() before the benchmark
(which returns basically nothing), letting the entire benchmark run, then
calling render() again (in which it again returns basically nothing).

In other words, I don't see what the "application data policy" has to do with
making React efficient in this case; all we're asking React to do here is
calculate a no-op diff and then do nothing.

~~~
drfloob
> it seems like it is invoking React's event loop more than I would expect ...
> Perhaps this difference is because of requestAnimationFrame?

I think so, yes. The performance difference is already negligible with Om, so
I didn't see a real need to optimize it any further. Pete Hunt's react-raf-
batching (mentioned already) could probably be dropped in to get that last bit
of optimization, but I haven't tried.

As for what I mean by "application data policy", I think that if you work with
your application's data in such a way that it batches modifications, renders
entirely from the top down, and uses fast `shouldComponentUpdate`
implementations, you'd achieve most of the improvement you see in the Om vs
React+Backbone TodoMVC comparison. Lots of things could achieve that.
Immutability isn't a hard requirement to get those features done. And if my
React+_tree example is any indication, requestAnimationFrame isn't buying you
that much performance either.

I haven't really analyzed what Om's Benchmark 2 was doing under the hood, so
thank you for that. I assumed it was doing _something_ , but with the ~4ms
benchmark, I just assumed that particular something was wizardry.

~~~
haberman
> I think so, yes. The performance difference is already negligible with Om,
> so I didn't see a real need to optimize it any further.

Sorry, I was unclear: my comments were about the (slow) first benchmark you
posted that is using React but not using your library. I think it would be
orders of magnitude faster with requestAnimationFrame.

~~~
drfloob
_EDIT:_ not true. see below.

\----------------------------

Good call! Benchmark 1 is about 33x faster in my browser (with the setTimeout
fallback being used, I think).~

[http://drfloob.github.io/todomvc/labs/architecture-
examples/...](http://drfloob.github.io/todomvc/labs/architecture-
examples/react-raf/)

This is the same React TodoMVC example with a different `react-with-
addons.js`, built using React v0.9.0 and [https://github.com/petehunt/react-
raf-batching](https://github.com/petehunt/react-raf-batching).

~~~
swannodette
The timings you are presenting in the UI are not accurate. But you are correct
that this approach results in timings that are almost identical to Om. Use the
Chrome Dev Tools profiler flamegraph if you want to confirm what I'm saying.

~~~
drfloob
Ah, right. Benchmark 1 is actually ~350ms on my machine. Thanks for the lesson
in profiling asynchronous code. I revisited the React+_tree benchmark claims,
too, and I think they are still spot on. If you're interested at all, I'd
appreciate you taking the time to check my work there.

~~~
swannodette
I took a look but it's hard to judge since you are using React 0.8.0 and Om is
now on React 0.9.0.

------
coldcode
The most amazing and irritating thing about JS frameworks is that every week
you find something better.

~~~
Joeri
And then you decide on one to do a really big single page app (100.000 lines
of code) and end up stuck on a legacy codebase watching the pretty new
frameworks sailing by. (Happened to me on an ExtJS codebase started in 2008.)

~~~
bokchoi
Yep, I'm in the same boat. And good luck upgrading ExtJS 3.x to 4.x.
Occasionally minor point releases are painful to upgrade to as well. Good
lord.

~~~
lightblade
Ha! We're even having trouble going from 4.0 to 4.1

Not really funny come to think of it :(

~~~
k__
I started with 4.0 and switched to 4.1 and 4.2 without any problems.

But yes, I heared 3 to 4 should be a PITA

------
cies
I've used Backbone, Spine, Angular and some handrolled solutions. It never
clicked.

But maybe I just don't like JS.. Recently I was trying to use Fay
([http://fay-lang.org](http://fay-lang.org) a proper subset of Haskell that
compiles to JS) and I loved it. Now if only I can use it with some sort of FRP
library :)

~~~
skrebbel
If it compiles to JS, I assume that you can call JS from it? The framework of
question in this thread, React, actually meshes really well with an FRP kind
of thinking and with immutable data. Read the post Haberman links to.

------
loz220
Having recently worked on optimizing the performance of my own view extension
library to Backbone (kettle.js), I can give some explanation about the
relatively poor performance of Backbone's todoMVC implementation in
benchmark#1 and #2.

Benchmark 1) This benchmark deals with adding a bunch of todos and seeing how
fast they render. The major performance impact here seems to be jQuery.
Specifically jQuery event delegation. I was surprised to find that binding DOM
events in jQuery are incredibly slow, in fact the majority of the time in that
benchmark is spent event binding. For comparison have a look at exoskeleton, a
Backbone fork which removes the jQuery dependency and uses the native DOM
methods instead. It's also available on todomvc.com and uses pretty much the
same code as the Backbone todo implementation.

Given the following benchmark :

    
    
        var benchmark = function() {
          app.todos.reset();
          var s = [];var i=0; while (i<1000) {s.push({title:'foo'});i++};
          var t  = performance.now();
          app.todos.reset(s);
          return performance.now() - t;
        }
    

Backbone: ([http://todomvc.com/architecture-
examples/backbone/](http://todomvc.com/architecture-examples/backbone/))

    
    
       >>benchmark()
       >>667.2439999965718
    

Exoskeleton: [http://todomvc.com/labs/architecture-
examples/exoskeleton/](http://todomvc.com/labs/architecture-
examples/exoskeleton/)

    
    
       >>benchmark()
       >>226.4119999963441
    

It's worth mentioning that Backbone has a couple pull requests open that is
meant to address this.

Benchmark 2) This benchmark deals with toggling all of the todos events at
once. It builds up 200 todos and toggles them on and off 5 times.

The problem lies with using 'all' and 'change' events way too liberally within
the todo application. Which causes a ton of needless render calls. During the
course of this benchmark the main view has render called on it 6000 times (!)
while the child views have render called on them for a total of 3000 times.
I'm actually surprised that Backbone is as fast as it is given those type of
numbers. What should happen is the main view should only need to render a
total of 5 times (once for each toggle) and the child views 1000 times (200
items x 5 times).

------
quaunaut
I hadn't read the original article, "The Future of JavaScript MVC Frameworks",
and the article itself[1] is unreadable for what seems like either font or CSS
issues. I made a pastie of the text with links to the text there for those who
want to read it too.

[1][http://swannodette.github.io/2013/12/17/the-future-of-
javasc...](http://swannodette.github.io/2013/12/17/the-future-of-javascript-
mvcs/)

[2][http://pastie.org/8768093](http://pastie.org/8768093)

~~~
cbaleanu
Also happens to me when using Chrome. They must have broken something with the
latest updates.

~~~
STRML
There's been a surprisingly bad number of font bugs in the last few months.
It's bizarre; I haven't seen browser bugs this visible in years.

------
tomphoolery
It's kind-of funny to me see an article entitled "the future of javascript"
anything that _only_ talks about a ClojureScript framework.

------
tomjakubowski

        Om is an immutable data structure library:
        a structure is  never mutated once created,
        which means that the contents of the entire
        tree are captured in the identity of the object
        root. Or in simpler terms, if you are passed an
        object that is the same object (by identity)
        that you saw earlier, you can be assured that it
        hasn't changed in the meantime. This pattern has
        a lot of nice properties, but also generates more
        garbage than mutable objects.
    

Is this true to the extent that it typically matters? I would expect so with
sort of naive immutability (copying everything whenever you make a change),
but not with the kind of structural sharing that Clojure(script)? persistent
data structures use.

~~~
haberman
It's hard for me to say how much it matters, because that would depend a lot
on the efficiency of the GC and how many copies are forced (which is a
property of the application's data structures).

Even with Clojure's sharing, it is still the case (as I understand it) that
you're forced to copy all nodes between the root and any node that actually
changed. A mutating approach doesn't have to do any copies; it can just mutate
the interior node of the tree.

As an example of a degenerate case, imagine that your root has 10,000
children. If I understand correctly, any mutation to any object in the entire
graph requires duplicating the root and the entire 10,000-element array.

I honestly have no idea if this would be an important issue in practice or
not, I just wanted to mention it as a super-brief pro/con list of immutable
data structures.

~~~
swannodette
Clojure and ClojureScript data structures use 32 way branching nodes. This
means even with >2 billion elements in say a persistent vector you'll never
have more than 7 hops on any particularly path. This means in the worst case
you will need to copy and update 7 children all which are small arrays.

I did a bunch of GC testing with Om, the GC profile wasn't dramatically
different than naive Backbone.js on TodoMVC which is also working GC by
blowing away the DOM on every update.

~~~
haberman
Ah, that's clever. Thanks for the info.

