Why Discourse uses Ember.js – Evil Trout’s Blog
aidanfeldman: Why Discourse uses EmberJS: http://t.co/TOCivZGS
One question people keep asking me is “Why did you choose Ember.js?”. It’s a good one, and one that I think can be considered in two ways:
“Why use a a client side MVC Framework?” and “Why Ember out of all the frameworks?” Here’s my answer to both of those questions.
Why use a client side MVC Framework?
Interactivity
There will never be a “one size fits all” approach to web development. This blog for example is a series of static HTML pages generated by Middleman. I deploy it using rsync. Most readers will probably visit the blog via a link or RSS, will read what I have to say and bounce out. Very few will browse my other blog entries, and even fewer still will leave a comment.
It would be silly to give them a heavy Javascript interface just to do this because there is so little interactivity. Their experience would be made much worse by having to download a Javascript payload before they could read what I’ve written.
Ask yourself how interactive your web application needs to be. On the less interactive side of the scale, there are huge wins with server side rendered HTML. The more interactive your application becomes, the more you’ll benefit from a client side MVC framework.
Why not just use plain old jQuery – it’s simpler!
jQuery works well if you just want to add light interactivity to your app. It has some failings though when you introduce more state to your application. You end up having to store data in your DOM (usually in data-*
attributes), and having to remember how to find those attributes based on where the event was triggered.
For example, on the bottom of every discourse post there is a button a user can click to like a post. When clicked, it vanishes and adds a footer below the post saying you liked it.
If you implementing this in jQuery, you might add a data-post-id
to the post. Then you’d bind a click event on your button element to a function that would make the AJAX call to the server. However, the click function passes a reference to the button, not the post. So you then have to traverse the DOM upwards to find the post the button belongs to and grab the id from there. Once you have it, you can make your XHR request. If the XHR succeeds, you then have to traverse the DOM downward from the post to the footer, and add in the text.
At this point it works, but you’ve tied your implementation of the button click to a particular DOM structure. If you ever want to change your HTML around, you might have to adjust all the jQuery methods that accessed it.
If this example seems simple – consider that in the footer we offer you a link to undo your like. When clicked, the footer text vanishes and the button appears again. Now you’re implementing the opposite operation against the DOM, only in reverse of what you did before.
Discourse even takes it a step further – we know that 99% of the time when you click the like button the request is going to work, so we hide the button and show the footer text right away, even before waiting for the server to reply. In the infrequent event that request fails, we’ll show an error message and pop the UI back to the state it was in before. If we were doing that in jQuery, we’d have to have a callback on our AJAX request that knew how to put the UI back into the state it was in before.
A prudent programmer might say, okay, well I’ll have a render function that can rebuild the DOM to the current UI state of whether the post is liked or not. Then both ‘undo’ and ‘like’ can call it. If ‘like’ fails it can call it again. Oh, and we have to store the current state of whether the post is liked somewhere. So maybe we add another data-liked="true"
attribute. ACK! Just typing this all out is giving me a headache!.
Congratulations, your code is now spaghetti, your data is strewn out in the DOM and your logic is tied to a particular layout of HTML elements.
How a client side MVC framework can help
The Ember.js approach to the above is to have a simple Javascript class that represents a Post.
var Post = Ember.Object.extend({ liked: false }); var p = Post.create();
You then bind that post to a template, and the template can have simple logic like:
class='toolbar'> {{#unless liked}} {{/unless}}<footer> {{#if liked}} You liked the post! <a href='#' {{action undoLike}}>Undo like</a> {{/if}} </footer>
Thanks to the binding, if you ever change the ‘liked’ attribute, it knows to re-render the HTML for you. There’s no need to call a render method. You don’t have to worry about traversing the DOM to get at the data you need. A front end developer can change the template around and things will continue to work just fine.
Client Side MVC Performance
I saw a popular tweet recently referring to client side MVC frameworks:
What do you think is faster?a) Send HTMLb) Download big JS libs, send JSON,, compile templates, query data, generate a HTML string— Thomas Fuchs (@thomasfuchs) January 29, 2013
Despite the hundreds of retweets, you should be aware that it’s mostly FUD.
- Ember.js is not that big. It’s 47k minified and gzipped. If you think that’s too big, consider the image of Thomas on his own web site is 49k.
Discourse only requests JSON when you change views. On your initial visit it actually includes the JSON payload in the body so it doesn’t need to make an additional request.
Nobody compiles templates in production. In development mode while you are changing them, they get compiled, but when you deploy you pre-compile them for performance.
I’m not sure why you’d have to query data once you’ve already requested the JSON, but I suppose some times you might download a list of objects and then only show ones that are active or something. This isn’t a common case, and even if it was, isn’t it better than a server round trip to change the filter?
Generating the HTML string is something that has to happen. And he’s not wrong, it is slower than just getting the HTML from the server. In particular, on low powered devices you will notice that rendering is slower than regular HTML. However, I’d offer two rebuttals to this: 1) Javascript and devices are only going to get faster in the future – the latest iPad renders discourse very smoothly – and 2) It can often be faster even with slow rendering to send much less data across the wire. Consider a table view with many controls on each row. Why send the html for all those buttons across the wire for every row? You can send the basic row data and the renderer decorate it.
The truth is, Discourse is fast. Try it out yourself. And our Javascript payloads work really well with CDNs. Try it from somewhere not in North America. I think you might be surprised how fast client side MVC can be.
API-First Development
One amazing side effect of a rich client side app is you end up with a battle tested API. Our app has consumed our own API since day one, so we know it works.
Note: We haven’t documented it yet because we plan on major changes over the next few months, but after things stabilize we certainly will provide
a more rigid interface.
If we want to create a native client for Android or iOS, it would be a lot easier because we already speak JSON fluently. If people want to build services that use Discourse, they won’t have to result to screen scraping. It’s a huge win for us and the developers that use our platform.
Out of all the Client Side MVC Frameworks, why do you prefer Ember.js?
I’m actually a fan of all major efforts in client side MVC. The philosophy of binding templates to variables and communicating over an API is what I like the most, and you can get that in many different frameworks. I implore you to investigate them all and come to your own conclusions. Despite that, I am a huge fan of Ember.js and here are some of my reasons:
The documentation was simple to understand
Here’s some text right out of the Angular.JS guides, about a feature called Transclusion.
The advantage of transclusion is that the linking function receives a transclusion function which is pre-bound to the correct scope.
In a typical setup the widget creates an isolate scope, but the transclusion is not a child, but a sibling of the isolate scope.
This makes it possible for the widget to have private state, and the transclusion to be bound to the parent (pre-isolate) scope.
That text isn’t describing an Angular internal – it’s a template directive you need to use when creating reusable components! Compare it to the
Ember guides. I find the Ember ones much clearer and easier to follow. I am much more likely as a developer to pick a framework when the framework makes an effort to be easy to understand.
It worked well early on and has improved a lot in a short time
Meteor had an amazing tech demo at launch, but it was missing very important security features (they have since addressed this). What impressed me about Ember was that even early on it was very functional. If I had to launch Discourse with the first version of EmberJS I started using that would have still been a big win. Having said that, the API has improved tremendously since then. It is much faster. It also has a new router that involves a lot less boilerplate code. The community has done amazing work over the last year. Discourse is on the latest Ember 1.0 prerelease and it’s fantastic.
The Team has a proven track record with Open Source
Yehuda Katz has done amazing work on Rails 3 and Bundler. When he tells me that he’s not going to abandon Ember.JS, I believe him, because he has a track record proving so. Angular, for example, is sponsored by Google, and while I think the project would continue even if they abandoned it, it is important to me that the Ember community didn’t spring out of a corporate sponsorship.
String Templates vs. DOM templates
EmberJS uses string templates instead of the DOM-based templates found in AngularJS. This is a more personal thing and comes down to aesthetics, but I prefer the syntax of handlebars over adding attributes to the DOM. Additionally, we do some server side rendering, which is much easier with string templates because we don’t have to boot a whole PhantomJS environment.
The Run Loop
One of the more complicated pieces of EmberJS is the run loop. Usually you aren’t aware of it, but behind the scenes Ember batches up updates to the DOM and bindings for performance. In some other frameworks if you have a list of 100 items, and iterate through them changing them all, you will end up with 100 separate DOM updates. Ember will batch them and update all at once, providing a better user experience.
Ember.JS has been a huge win for Discourse. Our application really benefited from the framework it provides and the excellent support of the community behind it. I would recommend it to anyone wanting to create a web application that needs a lot of interactivity. You’ll find your front end code a lot easier to manage and modify, which is a huge win for productivity. If you have some time, check out our Discourse’s Ember App’s source code and tell us what you think.