There's a good bit of confusion and misinformation in these ten reasons, so perhaps it would be helpful to walk through them and clarify a bit:
1. "A Clear Architecture". The MVC in Spine is structurally identical to the MVC in Backbone (or MV*, or whatever you'd prefer to label it) ... except for:
2. "Models are Models". Following Rails' lead, Spine conflates the class of a Model with the object that represents the Collection of all models of that type. This makes sense on the server-side, where you have ready access to the entire table; but is a misstep on the client side, where most of your lists of models will only represent small filtered collections of the server-side table. It's very useful not to be limited to a singleton, making it easier to have different sets of the same kind of model with different endpoints (/documents/101/notes vs. /documents/202/notes, for example).
3. "Spine.app". Yep -- Backbone doesn't include a file generator. Some folks like putting every class in a separate file ... others prefer to leave their entire (small) app in a single file. It's up to you.
4. "Dynamic Records". The code example shown here works exactly the same way in Backbone. If you find a model by id, you get a reference to the that record. It's not surprising that changing one changes the other because both are the same object.
5. "Elements Hash". It's been proposed for addition to Backbone, and there are plugins for it (https://github.com/chalbert/Backbone-Elements), but we aren't adding it to core, because it's not a great default. With frequent re-renders of Views, cached elements are likely to be either incorrect ... or they all need to be re-queried every time the view re-renders, which would be very inefficient. `this.$(selector)` is a nicer default to have.
6. "The Release Method". Removing Views from the DOM is all well and good ... but the Spine release method doesn't actually unbind model events that the view is listening to. Which makes it completely ineffective at fixing the memory leaks you're referring to. In fact, you can simply stop calling it (just remove the DOM element instead), and see that your app's memory profile remains identical. Unbinding model events you'll still have to do by hand.
7. "Routing Lives in the Controller". In Backbone, routing lives in the Router. Call it whatever you like ;)
8. "Model Adapters". Naturally, this paragraph would read pretty much the same if you replaced Spine with Backbone. Models are in memory, if you call `.save` they'll be persisted with the default REST-style Ajax calls, and there are plugins to make `.save` use LocalStorage, WebSockets, CouchDB, Mongo, etc.
9. "Get a Model from its HTML Element". Yes, by default Backbone doesn't use jQuery.data in order to attach references from the DOM back to models. Most of the entire point of using JS models and views is to stop tying your data concretely to the DOM ... so if you're relying on this feature, it's likely a place that's ripe for refactoring.
10. "Logging". No argument here. Backbone doesn't have any special logging over the built-in JS `console.log`.
Sorry for the exhaustive teardown, but there's enough FUD already. (And full disclosure: I work on Backbone.)
I'm the co-author of Composer.js, a Backbone/Spine-flavored framework built specifically for Mootools. I feel like because we borrowed many concepts from these two excellent frameworks, I have a very good understanding of their internals. I almost cringed reading down the list of reasons the author provided. I came on here to start clearing some stuff up, but Jeremy beat me to it. Anyway, some more responses:
4. Since models are just javascript objects, and variables in JS hold references to objects, you can pass an "object" all over the place updating it every which way, and all references to that object will stay up to date since they're pointing at the same place. Backbone/spine work the same in this regard. There's no magic object system separate from javascript where object instances are stored in variables. They are just plain old javascript objects.
6. Release methods cannot be automatic. You can remove an element out of the DOM easily, but as far as unbinding any associated events, this has to be manual. Why? Because models can exist under different controllers ("views" in backbone). If one controller releases all the events for that model, the other controller now won't update automatically when the model changes. Unbinding logic has to be very specific and precise and there's no magic way around it...and spine and backbone both handle it the same way: they don't.
7. Routing does not belong in the controller (once again, "view" in backbone). Maybe it does in Rails, but controllers are very specific objects meant to deal with one part of a page. Sure, you can make a controller that deals with an entire page and give it sub-controllers, but unless it's actually binding to elements on that overall page, it can make more sense just to route to a simple function that does the loading. It doesn't always make sense to cram everything into controllers.
8. IIRC this is what the "sync" function in backbone is for. Nothing special here, you can overwrite the function to do whatever you want with model data when it saves.
9. From what I know, this creates JS -> DOM -> JS dependencies, which is a recipe for memory leaks. Not to mention, it's probably best to be accessing models directly, since that's the point of these frameworks.
Please let me be clear: I love both Backbone AND Spine. I love them enough to have copied the hell out of them when building Composer.js. They are both excellent frameworks and do great things to advance the world of javascript apps. I just don't think the author quite understands their internals enough to be posting about them.
The flexibility which backbone provides is a big thing for me.
Secondly, Choosing "CoffeeScript" is a turnoff me, as it require additional learning curve.
There's indeed a learning curve, but because 'it's just JavaScript (c)' is CoffeeScript's motto, it doesn't take much. On the other hand, IcedCoffeeScript [1] introduces a fe extremely handy keywords that run afoul of CoffeeScript's motto.
And the debugging is just getting better, and with SourceMap, hopefully will be a non-issue in the very near future.[2]
In the long term, I think it's better to use CoffeeScript as it is a lot more readable and maintainable.
I'm glad you did the teardown because, despite the amount of people who are using any one of the MV* frameworks, few are authoritative on the differences/subtleties between.
Spine's "features", to a novice, are indistinguishable to Backbone's, which is probably what prompted the author's post.
In the world of Javascript frameworks, devs seem to pick a side based on the following:
1. Naming convention (Mootools' getStyle()+setStyle() vs. jQuery's css(), Router vs. Controller, etc.), which is admittedly personal preference.
4. Framework / Library size. I'm turned off by many JS MV* Libraries due to their larger size. Backbone is very small and lightweight - perfect for mobile web apps.
I wrote https://github.com/rhysbrettbowen/PlastronJS which is an MVC for Closure Tools. I agree with Jeremy on this. but just as an extra with some features I put in PlastronJS that you might want to think about adopting or rolling your own on top of the MVC of your choice.
4. Just create a object store that acts as a factory that will return the same object. In plastronJS there is already an mvc.Store that does this for you.
6. I built on top of goog.ui.Component that has a dispose() method that gets called automagically. I added in methods on the control that will dispose the event handlers when the control is disposed (plus it only has on handler for each type and will react based on an element test).
7. I like the router separate as it decouples the routing string. Instead I use a route and then have a mediator broadcast the new route which controls can listen to.
9. Why would you do this? if you are then you're doing it wrong.
Regarding point 2, I pulled together a boilerplate that my group uses as a template for new apps, and I'm working on creating a file gen in python. We make heavy use of AMD, and rely on a handful of legacy non-RESTful backend apps we need to overwrite sync to use, so I'm not sure an out-of-the-box backbone.app would work for us, anyway. Spine looks interesting, but backbone is working incredibly well for now.
What I like more about Backbone is loose that it provides loosely coupled small components. Small components that work well independently. This is a great feature. It makes it easier to adapt them according to an application's model/architecture. To top it off, this has encouraged developers to build a rich ecosystem of plugins.
Could you (or anyone else) elaborate on how would you refactor storing references to model id's in DOM?
How do I get the proper model instance in my event handler without using attributes? If I have collection view that renders a list, for example. What are the other options I even have then?
Your collection view should render multiple item views. You pass the model instance to each item view, and then the view handles all the event bindings, and the model is scoped to `this`
EDIT: This way if you update that one model, you're only calling one view's render method. Each view sets up it's own bindings, and when the view is removed, it can remove just the bindings on itself.
Most of those complains seem to be pretty basic. To me the only interesting one is #4, about syncing models.
As for collections - how would you handle adding/removing objects in Spine? Backbone's collections just let you listen for add/remove events so you don't need to do any manual work there.
No. 4 is something which I've solved in Backbone.js with about 50 lines of (commented) code. It's a pretty simple setup inspired by Rails and it has solved this problem in all use cases I've had. I've put it in a Gist here: https://gist.github.com/2762417
Semi-related question for you experts out there: how do these frameworks fit into a multi-page web app (ie: a typical Rails app)? They all seem to target the single-app market and the examples are always so simple that I have yet to figure how to build a larger application with either Backbone, Spine or Ember.
Is the idea to treat each page as a unique application?
In backbone, I will create a new Router for each page (though sometimes you might reuse the same router for groups of pages that contain the same views).
Where a typical Rails app has a controller per model with all the crud actions for that model, we have used one Backbone router per model. It worked out so that there was one Backbone router per top level nav item.
My team & I originally tried out a non-trivial app using KnockoutJS, AngularJS and EmberJS.
Knockout was comfortable for me, as I had a Dojo templating background where you'd often specify "dojo-attach-point"s for direct references to specific DOM nodes within your view/template. Knockout seems to be half-angular (DOM attribute bindings) and BackboneJS (Javascript views, logic).
AngularJS seems to be all DOM-specific logic, binding, etc. You're able to extend it for your custom logic, but as things got more complicated we had difficulty grepping the large markup.
EmberJS polluted the DOM too much for us, particularly when creating dynamic forms, although it seemed to be a much more opinionated BackboneJS.
Currently, we're back to using BackboneJS + RequireJS, specifically where our Views have no idea what markup is under them, and the template only defines which "attach points" are needed for events.
Really, BackboneJS, SpineJS, AngularJS, EmberJS & KnockoutJS are all extremely fine frameworks, each with their own methodologies (or lack thereof). We've found that each one is best when applied to a specific use-case, while none are ideal for the majority of cases. (Some would argue Backbone is great in every case, but that's because it's practically a blank slate. That's like saying Javascript is great in every case! :D)
That review is helpful. I'm going to keep BackboneJS + RequireJS for my project. Technically BackboneJS + almond.js since I build require for production. It seems BackboneJS is the most abstract implemented framework. I always prefer unobtrusive framework.
Thanks for the quick review. I'll be curious to see how the javascript MVC & MVVM patterns come of age when Windows 8 and Metro apps allow for native javascript applications.
This was a great overview, thanks. One off-topic thing though: I'm browsing on my kindle fire, and unless I turn off javascript, your site crashes the browser after a few seconds.
1. "A Clear Architecture". The MVC in Spine is structurally identical to the MVC in Backbone (or MV*, or whatever you'd prefer to label it) ... except for:
2. "Models are Models". Following Rails' lead, Spine conflates the class of a Model with the object that represents the Collection of all models of that type. This makes sense on the server-side, where you have ready access to the entire table; but is a misstep on the client side, where most of your lists of models will only represent small filtered collections of the server-side table. It's very useful not to be limited to a singleton, making it easier to have different sets of the same kind of model with different endpoints (/documents/101/notes vs. /documents/202/notes, for example).
3. "Spine.app". Yep -- Backbone doesn't include a file generator. Some folks like putting every class in a separate file ... others prefer to leave their entire (small) app in a single file. It's up to you.
4. "Dynamic Records". The code example shown here works exactly the same way in Backbone. If you find a model by id, you get a reference to the that record. It's not surprising that changing one changes the other because both are the same object.
5. "Elements Hash". It's been proposed for addition to Backbone, and there are plugins for it (https://github.com/chalbert/Backbone-Elements), but we aren't adding it to core, because it's not a great default. With frequent re-renders of Views, cached elements are likely to be either incorrect ... or they all need to be re-queried every time the view re-renders, which would be very inefficient. `this.$(selector)` is a nicer default to have.
6. "The Release Method". Removing Views from the DOM is all well and good ... but the Spine release method doesn't actually unbind model events that the view is listening to. Which makes it completely ineffective at fixing the memory leaks you're referring to. In fact, you can simply stop calling it (just remove the DOM element instead), and see that your app's memory profile remains identical. Unbinding model events you'll still have to do by hand.
7. "Routing Lives in the Controller". In Backbone, routing lives in the Router. Call it whatever you like ;)
8. "Model Adapters". Naturally, this paragraph would read pretty much the same if you replaced Spine with Backbone. Models are in memory, if you call `.save` they'll be persisted with the default REST-style Ajax calls, and there are plugins to make `.save` use LocalStorage, WebSockets, CouchDB, Mongo, etc.
9. "Get a Model from its HTML Element". Yes, by default Backbone doesn't use jQuery.data in order to attach references from the DOM back to models. Most of the entire point of using JS models and views is to stop tying your data concretely to the DOM ... so if you're relying on this feature, it's likely a place that's ripe for refactoring.
10. "Logging". No argument here. Backbone doesn't have any special logging over the built-in JS `console.log`.
Sorry for the exhaustive teardown, but there's enough FUD already. (And full disclosure: I work on Backbone.)