The Future of JavaScript MVCs
swannodette.github.io
swannodette.github.io
What react ultimately opens up is a way to do immediate mode UI [1] on top of the DOM _efficiently_, which changes things pretty dramatically. It means we can start to treat the browser as just a renderer and get the infectious design decisions of the DOM out of our programs. If nothing else, this gives us freedom, but as david is suggesting, I think this also gives us an opportunity to treat UI much more directly than we currently are. If you want to know what the state of your UI is, you just have to read linearly down through the code that produces your tree. No nest of dependencies, no state hidden in the UI components, you could even get rid of event hierarchies if you wanted.
More important than anything else, this gives us a chance to dramatically simplify our model for UI and magically be even faster than we were before. Sounds like a win to me.
It's not so much about being able to do it with canvas. It's the fact that you have to reinvent all of your tools on top of canvas.
I'm uncertain that having to reimplement all your high-level drawing routines by hand and figuring out how to handle responsive and reimplementing all activation and behaviors on top of that is less complicated than just using what the DOM provides.
Even text wrapping must be done by hand when you're using canvas.
Text baseline is the tricky part - if you want to e.g. put a taller piece of text next to a shorter one, then they should be put together so they share a baseline, but there's no decent way to get the text baseline.
The hack I use is to draw the text in a hidden span, and put a 1px inline-block span next to it, and check where the 1px span ends up. This works though it is ugly.
However, I think you would still end up using a hack. The general idea of creating a replacement for the DOM has bugged me for a while too. It is a great to replace the DOM completely by a statemachine, I think. We need a New Document Model that supports "Batch Operations", is "Stateful" and uses the latest best performing algorithms published to do so.
Components are the CORRECT way, I fully agree with the facebook team here.
It seems that there is one guy who ALREADY DID all those things and replaced the DOM by something that's more akin to 2013's technology requirements. Link: http://www.nidium.com/
https://github.com/facebook/react-art
Canvas APIs are currently not as fast as the DOM renderer. There's also a lot of added complexity with regards to layout, text flows and text input. The code you'd have to ship down to solve all that with pure Canvas isn't worth it for a lot of applications.
For things that has it's own layout and no text input (like charts/data visualizations) or complex editors like Khan Academy's math content editor (http://bjk5.com/post/53742233351/getting-your-team-to-adopt-...) it definitely makes sense.
> Thus we don't need React operations like setState which exists to support both efficient subtree updating as well as good Object Oriented style. Subtree updating for Om starting from root is always lightning fast because we're just doing reference equality checks all the way down.
I don't think anyone actually likes using explicit setters/getters in frameworks like Backbone and Ember. Of course Angular avoids it but that's by the crazy "dirty-checking". Obviously the new Object.observe will help this situation, but I love how simple Om/CLJS makes this.
> This also means that Om UIs get undo for free. You can simply snapshot any state in memory and reinstate it whenever you like. It's memory efficient as ClojureScript data structures work by sharing structure.
> VCR playback of UI state
I can't wait for details on this. This has gotten me really excited about client-side apps again.
Why? I'm curious what I'm missing about it and the possibilities you see?
For example:
* Rename model: inverse is to rename back to stored name
* Delete model: inverse is to create with same data
* Create model: inverse is to delete the model
* Add model to collection: inverse is to remove from collection
.. and so on. It's pretty simple. I use something similar to JS-Undo-Manager[1] to manage it. Om isn't going to get you around needing to make those calls and your UI should (generally) be able to handle executing the inverse of an action at any time.
In particular, if you have any triggers in your API they may need to be deferred until after undo is no longer possible, or have explicit commits. Lets say you have the ability to add a user to a group, and they will get an email about it. You accidentally add your boss to the "I hate my boss" group. How do you support undo in this case? Some sort of timeout before sending is really the only valid approach, but where?
One was is to do the deferred part on the client side, but this means closing the browser after adding somebody will mean they won't get notified at all. You really need to have support for undo in the API for this to be meaningful (or the "add to group" api returns a notification ID that you can cancel).
As an aside, I'm pretty sure the inverse of deleting a model is typically going to be far more complex than just recreating it. Deleting a social network profile, for example, involves deleting all photos, associations, posts, etc. Easier just to deactivate models so you get easy undo, and periodically flush deactivated objects.
Edit: Ok, I now see how the Om todo example is handling update, and it's really cool. It creates a set of channels that encapsulate the knowledge of how to handle each type of change to a todo [0]. That gets passed in to the todo widget as "chans" and the widget sends messages to it in its event handlers [1]. I wonder if this whole channel CRUD abstraction is general enough to make it part of Om or another layer so that it didn't have to be recreated each time.
[0] https://github.com/swannodette/todomvc/blob/gh-pages/labs/ar...
[1] https://github.com/swannodette/todomvc/blob/om/labs/architec...
this is precisely how React works.
Feel free to ask any questions.
Both frameworks seem to offer similar advantages: decoupled model and view, UI state playback (VCR style) and instrumentation. How significantly do these frameworks differ? Or are we seeing different paths towards a convergence around a new set of model/view practices?
I already have my own React.js+Clojurscript bridge for personal projects, because I think it's an extremely powerful web dev combination. I'm glad I can finally abandon my own library for Om!
BTW, it was your React mailing lists posts that also really got me digging into this.
Ember.js has done this since day one with the Run Loop. Additionally it allows to coalesce operations yourself if you need control.
Angular also would not update the DOM as many times as the backbone example as it uses dirty checking to get around this problem.
At least according to this data, React and Backbone are the fastest. (Om is faster than vanilla React)
One suspects that with hand-optimization the other frameworks could be a lot faster, but the point of this post is that it is fast without hand-optimizations.
I'd be interested to see these numbers after doing `npm install react-raf-batching` and doing `require('react-raf-batching').install()`.
Example: Average code written in node.js is typically just as slow as, say, PHP (in fact node.js was 3x slower than php in techempower benchmarks). However, it has potential to be much faster when optimized whereas PHP doesn't reward such optimization almost at all.
Angular.js still suffers as shown by the optimization article I linked to in my post. These kinds of typical optimizations can and should be pushed out of the user's hands.
The same holds for dependent computed properties. If you change a dependencies many times in a row, the dependent property only reevaluates once.
1. It's not running Ember in production mode which is much faster.
2. It's running an older release candidate version of Ember.
3. It is purposely wrapping individual events in run loop executions rather than just letting ember figure out when you put things into run loops.
I re-ran the benchmarks with the latest production mode Ember.js and removed the incorrect run loop usage got the following results:
https://gist.github.com/eviltrout/8058560
It's still slower than ReactJS but much less so than before. In fact it's now #3 in that benchmark suite behind ReactJS and Backbone (and much faster than ReactJS in the completing benchmark), although I wouldn't be surprised if the other frameworks were set up as incorrectly too.
I don't see a large movement to immutable data structures on the horizon in JS. I can appreciate the performance implications in Om, and would be interested in using React + Mori to the same end, but I'm not sure that it would keep me from having mutable data structures to represent most of my application state.
There are so many now-solved problems in JS MVCs that were a complete trainwreck several years ago - client-side routing, sanely managing data, and intelligently organizing your code base - that all assume mutable data structures and traditional object-oriented paradigms.
This might be the future of ClojureScript (in fact, it should be the future of CLJS, as it's much more elegant than any other view solution I've seen for it), and functional data structures may be a clever way to optimize the DOM, but this certainly doesn't seem like the future of JavaScript to me.
So I dunno, seems like it might catch on eventually ;)
Instagram's really not a good example here. Do comments and likes even update in real time?
Only if starting ClojureScript development wasn't so hard. I'd like to use browser REPL, IDE like LightTable. I am used to LiveReload's speed which makes loading changes instantaneous. But ClojureScript compiling seems to be still slow(ish) and I have already found half a dozen of different cljsbuild configuration examples. Compiling simple cljs files can take anything from sub second to 20 secs, and I don't understand why.
Could you perhaps tell more about your development process, Swannodette? How do you develop ClojureScript apps? I don't see anything beyond base cljsbuild in Om's project.clj. I confess I haven't yet had time to play with Om and see how fast it can be compiled.
I'd start with this, http://swannodette.github.io/2013/10/27/the-essence-of-cloju...
External browser with REPL inside LightTable seems easiest for a noob like me. Unfortunately LT doesn't support that use case yet, and you can't use Chrome debugger inside it. I still need to see what the browser thinks is happening and not just the ClojureScript's side.
I did manage to get React + JSX, CoffeeScript and ClojureScript + bREPL working with LiveReload, in a single page app. That felt truly powerful.
I personally use Emacs and Austin to hook the browser REPL into nrepl. Here are some sample files that you can take a look at.
Here's a sample project.clj: https://github.com/aamedina/foundation/blob/feature/cljs/pro...
I've become a fan of Stuart Sierra's Reloaded workflow: http://thinkrelevance.com/blog/2013/06/04/clojure-workflow-r...
Here's my dev/user.clj
https://github.com/aamedina/foundation/blob/feature/cljs/dev...
Some notes about what I did here: you start the server by running (go), you can quit the CLJS repl easily by typing in :cljs/quit as normal and return to the user namespace and then safely (stop) or (reset), stop will stop the server and reset will refresh all of your Clojure namespaces (useful if you're writing macros in Clojure for use in ClojureScript).
Aside from this, just turn on cljsbuild auto with :optimizations :none, and code recompiles are sub-second.
Hope any of this helps you.
It certainly does :)
On the connections panel click 'Add connection' and then 'Browser (External)'
The first time you do that it will give you a script tag to add to your page. Once you've added that and loaded the page in the browser, try again and it will connect.
You can email me (jamie@kodowa.com) or the LT group (light-table-discussion@googlegroups.com) if you have any problems :)
I'll see how it goes and will post to the LT group if I stumble on something.
Ah, that's because its a two stage process. You need something that can compile cljs to js (eg nrepl) and something that can eval js (eg browser, node, lighttable).
I think I was confused about that at first too. We should figure out how to make it clearer.
I keep meaning to make a game with it, but always give up because it's so much more painful than pretty much any other to-JS language.
One logical step from here is instead of having a one-to-one correspondence between the "virtual" DOM and the browser DOM is to introduce a higher level meta representation based upon the context. This seems like a logical path towards a generative, projectional approach to controlling UI and browser document rendering in general. It's been tried before in several contexts and the hacks I've tried myself have always been to hard to get my head around since it's a complex problem, but this seems like it could be a really decent foothold to build a projectional, transform-based paradigm. For example, having a meta-DOM that encodes mathematical notation (probably inspired by LaTeX), which gets transformed into the current virtual DOM, which is used to update the real DOM. User manipulates a integral on screen, and the downstream transformations are performed lazily and efficiently all the way to the screen. This type of lazy evaluation from document to screen is essentially the core challenge (from a engineering standpoint) in building a usable real-time projectional editor like that demonstrated by intentional software back in 2010 [1].
[1] http://www.infoq.com/presentations/Intentional-Software-at-W...
Other approach I have been testing is contenteditable, but this won't work with virtual DOM at all. Though how you parse an existing XML document to React's virtual DOM is beyond me. Perhaps you have to generate function calls SAX style.
With content editable we can just parse he html. We have an example in the repo.
Parser for HTML and XML to generate React components and generate virtual DOM from that? This got me thinking.
> With content editable we can just parse he html. We have an example in the repo.
It must be the weekend denseness setting in... I grepped through React repo, but I just found bits in the core that had something to do with content editable. Could you perhaps point me to right direction? If I understand this correctly, I could parse the entered HTML to virtual DOM.
However, as a framework author, I feel obliged to point out that of course you can create a more expressive and performant UI framework in a language with S-expressions, macros, and value semantics. What's hard is doing it in JavaScript. :)
It also feels a bit like reverse logic to cite ever-faster JavaScript VMs as a reason to choose a new framework for performance reasons -- shouldn't it matter less exactly how you structure your application logic when you're running on a "fusion reactor"? -- but I realize there's some subtlety here about lower constants enabling better algorithms. (Still, if your framework includes a language compiler or gets to take advantage of an expressive macro system, it should be able to run on anything.)
Once it's possible to compile ClojureScript without booting up a JVM -- which could happen if it becomes self-hosting -- I'll make a Meteor package for it. I'd also like to see the compile times get a little shorter and the runtime library get a little smaller.
> If you're a JavaScript developer, I think taking a hard look at React is a really good idea. I think in the future, coupling React with a persistent data structure library like mori could bring JS applications all the way to the type of flexible yet highly-tuned architecture that Om delivers.
I think there are a couple reasons Mori and other libraries like it haven't caught on in the JS mainstream:
1. It's clunky to use them in vanilla JS compared to the default mutable objects and arrays. While you gain simpler semantics, the code becomes harder to read, and that tradeoff isn't always worth it.
2. Most JS developers are not experienced with building programs around immutable values.
var fooString = "foo";
var secondFooString = fooString;
secondFooString; // => "foo"
fooString = "bar";
secondFooString; // => "foo"
We set the variable fooString to point to a different string, but the original, underlying string hasn't changed. In JavaScript, we can think of a string as a value.This is not the case with arrays in JavaScript:
var firstArray = [1, 2, 3];
var secondArray = firstArray;
firstArray[0] = 100;
firstArray; // => [100, 2, 3]
secondArray; // => also [100, 2, 3]
Because we can change the underlying contents of the array, an array in JavaScript isn't a value. It's a place: a reference to a location in memory. The underlying value could be changed at any time.But, using Mori, collections are values, just like strings:
var firstVec = m.vector(1, 2, 3);
var secondVec = firstVec;
firstVec = m.assoc(firstVec, 0, 100);
firstVec; // => [100, 2, 3]
secondVec; // => still [1, 2, 3]
Instead of modifying firstVec in place, mori.assoc creates a new vector that is identical to firstVec except for the change we want. We then assign the result to firstVec. secondVec is unchanged. We are unable to go in and change the underlying values because a vector is a value, not a place.The most obvious way to build this would be to deep-copy the entire collection when it's changed, but that would of course be way too slow and wasteful — imagine copying a one-million-long array just to change one element. Clojure, ClojureScript and Mori minimize unnecessary copying using a very thoughtfully designed data structure you can read about here: http://hypirion.com/musings/understanding-persistent-vector-... The short story is that, surprisingly, you get "effectively O(1)" copying when you use assoc.
Meteor could use Miro inside minimongo on the client, for example, and turn objects into vanilla JS before passing them to the app, if that implementation offered significant performance benefits (or equal or better performance and cleaner code, though the size of the Miro payload would also have to be weighed). Once the app has to deal with Miro objects, though, it starts to feel less like the JavaScript people know.
I'm getting pretty good results with swannodette's benchmarks - http://danshearmur.github.io/backbone-fast-view/
I'm getting approx 150ms in Chrome for benchmark 1 and about 400 ms for Chrome for benchmark 2
I'm wondering how this compare to the Javelin library as that seems to offer the same functionality when combined with hlisp. Would I be correct in saying that Om achieves the same by using ClojureScript's data structures and core.async to offload the FRP part to React?
I also don't really believe in templating languages, but I also think that functional boilerplate for rendering children is a bummer. I'd like to see a client side query language modeled after Datomic's Datalog syntax instead.
You can pull this up on an iPhone 4S or newer (it uses the same technique with tons of allocations but doesn't really drop frames): http://petehunt.github.io/react-touch
The secret sauce is that you can animate CSS transforms every requestAnimationFrame without breaking out of React's natural data flow.
So we declaratively express the UI as a function of a single float which represents the scroll position (i.e. how open/shut the nav is or what position in the photo viewer you're at).
When we do that, we can use the excellent Zynga Scroller touch gesture physics engine (reverse engineered from iOS) to do the touch gesture stuff, then we can declaratively rotate, fade and translate everything as a function of this float.
Code is on github! And is actually in a reusable library!
I'm also super interested in this combination. However, on my new moto x, the photo scroller drops to ~45 fps and looks pretty chunky.
I also noted that you say the demos work best iPhone 5 on iOS 7, but unclear if you meant in contrast to earlier iPhones, or in contrast to Android, or both. Have you looked at all at the perf issues on Android?
The FPS drop on the photo scroller is likely due to jpeg decoding and painting on the main thread (I thought this was fixed in Blink but maybe not). Try "warming it up" by scrolling through all of the images a few times so the decoded jpegs get cached. I think this is the biggest perf problem on the Web today.
The reason Android may "feel" sluggish while reporting good FPS is because Android simply has a huge problem with touch event latency in the browser that you can't get around. This is where iOS really kicks Android's ass, at least on the web.
So the reason I limited to iPhone 5 and iOS 7 was because I can guarantee the touch latency and that the images fit in texture memory. Maybe they don't on the moto x.
The point of rebuilding scrolling is to get the scroll position every frame so you can update the UI (ie rotate a photo or fade the left nav). You don't get scroll events during momentum scrolls so you have to rebuild it.
Yes, what about them? Have they been measured to be a problem with this approach?
If anything, it leads so colletions of objects of small lifetimes, that are quickly collected by the GC.
React is a really great fit for Om, persistent data structures, and functional programming in the following ways:
1. We want to allow developers to elegantly describe their user interface at any point in time, as a pure-as-possible function of data dependencies.
2. We allow hooks for your system to help guide the updating process along. These hooks are not necessary. Often, we'll add optimizations long after we ship. We strongly believe that perf optimizing shouldn't get in the way of writing code elegantly and shouldn't get in the way of the creative development process and actually shipping to your users. At the same time, performance matters - a lot. So we ensure that at any point in the update process, if you know better than the framework, you can help guide the system. The fact that this is optional and doesn't change the functionality or correctness of the system is critical. Persistent data structures are an excellent (likely the very best) way to hook into the update system without making the developer do anything special.
Some people here were wondering about the apparent OO influence in React. Here's how I personally think of React's OO support/influence:
1. It's there to help you bridge with other existing mutative, stateful libraries in your stack - you know you have them. The DOM falls into this category as well.
2. It's there when you want to treat state as an implementation detail of a subcomponent. This is only because we don't have a good way of externalizing state changes, while simultaneously keeping the nature of them private. We just need more people to think about it (I'm sure the ClojureScript community can help us chew on this). Our internal motto is to keep things as stateless as possible.
3. A lot of the OO support in React is there as a concession, more than being considered a virtue. It's really cool to have the FP community involved in the UI space. Those people are already sold on FP and statelessness and get the luxury of programming in tomorrow's paradigms today (how ironic that FP has been around for decades!) To accelerate this momentum, we also want to reach out to people who aren't yet sold and change how they think about building UIs and software in general. The most effective way to do this is to reach out to them where they stand today, on some middle ground. It's really great to see eyes light up when they see that they can use simple functional composition in order to build large, sophisticated apps.
We're really glad to have swannodette and the ClojureScript community checking out React (github.com/facebook/react). We should consider adding some level of support for persistent data structures in the React core. Let us know if there's anything we can do to help.
http://swannodette.github.io/mori/
https://github.com/hughfdjackson/immutable
The first one uses the same data structures from ClojureScript.
As Sir William said, "There is blood in the old ways yet".
I'm not surprised that persistent data structures can make things fast, in fact I've spent the last week speeding up a Rails app in some ugly spots by preloading the data structures to keep DB queries from happening, effectively turning a lot of just in time queries at the ORM level into a pre-loaded data graph. The speed is fantastic, but what is interesting is you could add a level of immutability to this and would be potentially even faster, especially on top of the JVM.
I've been playing with the idea of immutable entities in Obvious Architecture for a while and it really changes the way you look at your business logic and performance.
It's the performance.
So many of us who want JS alternatives have made our peace with the idea that we'll have to sacrifice a bit of performance if we want to use a nice language.
But being able to improve performance while using a nicer one?!
Count me in! I already have a serious project in mind for this.
Clojure and its libraries has the worst documentation, and this malpractice seems to be continued in ClojureScript.
http://swannodette.github.io/2013/10/27/the-essence-of-cloju...
The overhead from localStorage appears to account for a significant chunk of the difference in performance. You can remove the localStorage calls with 'Backbone.sync = $.noop' or similar. After doing that and clearing localStorage, benchmark one drops to around 350ms, and benchmark 2 drops to around 2000ms.
Of course, benchmark 2 is where your library really shines, and backbone still takes its time with that one.
Seeing React.js at JSConf.asia last month got me excited that I don't have to touch the DOM anymore, but I still had to deal with Javascript the language itself.
And now this comes along. Now I don't have to deal with DOM (or at least it offers better abstractions for working with it) and I get to use the most pleasant language I've tried so far.
Christmas came early. :)
To be honest at this point there is little that Om does over React other than provide really good defaults ;)
I'm sure doing a similar thing with LayoutManager wouldn't be too hard. I think LayoutManager uses _render internally too so you may want to call it something else.
It also definitely does make the benchmarks faster http://danshearmur.github.io/backbone-fast-view/
I'd imagine a Backbone integration with React would get us even closer. I'm not sure if it actually makes sense to go too far with an LM conversion as React appears to do a better job. But LM could certainly benefit from waiting until RAF.
The Angular model doesn't preserve referential transparency and therefore is inherently mutable. You mutate the existing model to track changes. What if your data source is immutable data or a new data structure every time (such as a JSON response)?
Dirty checking and Object.observe does not work on closure scope state.
These two things are very limiting to functional patterns obviously.
Additionally, when your model complexity grows, it becomes increasingly expensive to do dirty tracking. However, if you only do diffing on the visual tree, like React, then it doesn't grow as much since the amount of data you're able to show on the screen at any given point is limited by UIs. Pete's link above covers more of the pref benefits.
The difference is that React constrains the operations a user can do (i.e. we only give them the DOM node if they explicitly ask for it and only let them manipulate it at certain times) so we eliminate the operations that cause the DOM to be slow.
If the DOM were to implement this it'd have to break backwards compatibility.
We think that "two-way binding" is actually very, very hard to get right since it flies in the face of the way data flows in a von Neumann program.
Instead, we provide some sugar that lets you get the conciseness of two-way binding but without making it hard to debug or understand performance: http://facebook.github.io/react/docs/two-way-binding-helpers...
But we don't really recommend that for newbies since it's important to understand how the data flows in your program before covering it up with sugar.
However, once you have certainly complexity in your asynchronous flow, RxJS is a great way to express that.
The interesting part of our experiments (which we designed together with Erik Meijer) can be found here:
https://github.com/facebook/react-page/blob/082a049d2a13b141...
RxJS makes it easy to create abstractions from complex pieces of your async flow. The getStreams method in that example could easily be broken apart into multiple pieces.
This fits very well into the React model and is definitely a great compliment if your organization is already familiar with Rx.
While some big games have used it, a lot more games are still stuck on class hierarchies.
It is true that most games don't use MVC. I think that's because MVC isn't a good fit for games. It seems to work fine for business apps, though more experimentation is always good.
I love how different programming communities never talk to each other.
Couldn't this even be done at the browser level?
I think I must be missing something.
Also... an interesting read, thank you :)
Compare this with an alternative JS MVC framework (like, say, Knockout.js) and another modern "javascript-compatible" language (like, say, TypeScript), and see for yourself. [2]
While I didn't run any benchmarks, it's safe to assume the Om demo is faster. However which sample do you think is easier to write, test and maintain? If "The Future of JavaScript MVC Frameworks" is supposed to look like the Om sample, sorry but I'll pass.
[1]: https://github.com/swannodette/todomvc/tree/gh-pages/labs/ar...
My point is that if Om claims to be superior to other JS MVC frameworks - as it is presented as "the future of MVC frameworks" - it needs to be good (if not superior) in many aspects. Performance is a big deal all right, but another aspect (which seems essential to me) is that using this framework should be easy, concise and intuitive. And I've been kinda disappointed to see that the sample code given to present Om in action is not. At all. If I wanted to fiddle a bit with this code right now, to add a few features for example (like adding a timestamp besides each item indicating when it was created), I wouldn't have any clue where to start.
What bothers me is that Om has clearly been written with ClojureScript in mind, so in a way this framework is supposed to be sublimated by the language. It should "feel" right. And I know it's possible with other frameworks (and my comparison with Knockout+TypeScript was meant to illustrate that), so I expected it to be the case with Om.
You could add a timestamp to each todo with two changes:
https://github.com/swannodette/todomvc/blob/gh-pages/labs/ar...
(defn handle-new-todo-keydown [e {:keys [todos] :as app} owner]
...
(om/update! app [:todos] conj
{:id (guid) :title (.-value new-field)
:created-at (js/Date.) ;; <-- add this line
:completed false :order (count todos)})
...)
https://github.com/swannodette/todomvc/blob/gh-pages/labs/ar... (defn todo-item [{:keys [id title editing completed] :as todo} {:keys [comm]}]
...
;; change this line
(dom/label #js {:onDoubleClick #(handle-edit % todo m)} (str
(:title todo) (:created-at todo)))
...)Still thanks for the comparison!