JavaScript MVC frameworks: A Comparison of Marionette and Chaplin
JavaScript application development is a hot topic and people are wondering which framework they should pick.
In this post I’m going to compare two of them.
Marionette and Chaplin are frameworks on top of the popular Backbone.js library. Both seek to ease the development of single-page JavaScript applications. In such applications, the client performs tasks that were typically performed on the server, like rendering raw data into HTML.
Backbone is designed as a minimalist library instead of a full-featured framework. My experience has shown that Backbone can only provide the foundation of a JavaScript application architecture. Both Marionette and Chaplin arose because Backbone is providing too little structure for real-world apps. They respond to the same problems. So there are a lot of similarities between the two – perhaps more than differences.
First of all I have to disclose that I’m a co-author of Chaplin. But I’ve also worked with Marionette in production and I’m following Marionette’s development. There is another ambitious framework on top of Backbone, named Thorax. Since I haven’t worked with it in production I don’t feel entitled to include Thorax in this comparison.
Non-technical aspects
I’m going to talk about the technical details soon, but let’s face it, decisions between software libraries are largely influenced by their perceived momentum, reputation, success stories and documentation.
Marionette and Chaplin are MIT-licensed open-source projects that are being actively developed on Github. The authors have developed several bigger Backbone apps and took their experience to write layers on top of Backbone so you don’t have to repeat their mistakes again.
Well-known companies are using Marionette and Chaplin to develop their products. It’s hard to estimate, but the user base is probably about the same size. The Marionette ecosystem is broader, so a lot of people use parts of Marionette without using the whole library.
Chaplin was more popular in the beginning, but Marionette has recently gained popularity. Marionette is beginner-friendly and has a great documentation, which is probably the most important reason for people to choose it. I think the commitment of Derick Bailey, the creator of Marionette, is one of the reasons for Marionette’s success. He wrote numerous key articles about developing Backbone apps. He is giving talks and recording screencasts, too.
Common features that fill Backbone’s gaps
Event-based architectures without the mess
Backbone’s key feature is the separation of concerns between models and views. They are connected using events and event listening. Using Backbone.Events, you can build an event-driven architecture. This is a great way to decouple the parts of your application.
Both Marionette and Chaplin identify the major pain points with Backbone apps. In an event-based architecture, cleaning up listeners is crucial. Components in your application need to have a defined lifecycle: A particular component creates another and is responsible for its later disposal. Marionette and Chaplin both address this problem with different approaches. They not only advocate event-based communication using Publish/Subscribe and related patterns, but also provide good means to avoid its pitfalls.
Application architecture
Models and views are low-level patterns. On top of that, Backbone only provides Routers. This is a very thin layer and probably the most confusing and problematic part of Backbone. With Backbone.Router alone, it’s not possible to set up a proper top-level architecture that controls the lifecycle of your objects. Both Marionette and Chaplin re-introduce controllers and a managing layer on top of them.
Strong view conventions
Following Backbone’s philosophy of simplicity, Backbone views and view rendering are rather abstract patterns. A Backbone view holds and controls a specific DOM element, but Backbone leaves it up to you how to fill this element and how to add it to the live DOM – the render
method of views is empty per default.
Marionette and Chaplin provide view classes with a sane default rendering mechanism (see Marionette.ItemView and Chaplin.View). You just need to choose a template language like Mustache/Hogan, Handlebars of HAML Coffee.
Both libraries have conventions on when to render views and how to add them to the DOM. You can transform the model data before it is passed to the template. This is useful for computed properties, for example.
Views are probably the most complex part of your application, so Marionette and Chaplin provide several helpers and shortcuts. They allow to nest views in a safe way and declare named regions. They allow to register model events in a declarative way, which is easier and more readable than calling this.listenTo(this.model, …);
several times.
If you’re using plain Backbone you will definitely miss the view classes for rendering collections (see Marionette.CompositeView and Chaplin.CollectionView). Using item views and two templates – a container template and an item template –, complex interactive lists can be implemented with clean and well-structured code. These collection views listen for collection events and render only those models that have been added, removed or changed their position.
Key features of Marionette
Marionette is a treasure trove for useful patterns to structure your app. It’s quite modular, you don’t need to use all what Marionette provides. It’s easy to start with some features of Marionette and discover others later. Some of Marionette’s features come from separate Backbone plugins, namely Backbone.BabySitter and Backbone.Wreqr. They are part of the Marionette family.
Marionette has some great unique features. In my opinion, the strongest points are application modules and the smart view management.
Application modules
Application modules are independent parts of your app that may consist of routers, controllers, models and views. Modules can be started and stopped, and you can define initializers as well as finalizers. Modules can also be lazy-loaded when a route matches, they don’t need to be active right from the beginning.
BBCloneMail is an example app that consists of two modules (mail and contacts). In this example, only one module is active at the same time. In general, app modules don’t have to be exclusionary. The modules have associated routers that need to be active since the beginning (contacts router, mail router).
Modules can be nested. Your main application, Marionette.Application, is also module. Technically there are some differences between Marionette.Application and Marionette.Module, but I hope in the future they will get more similar.
You probably don’t need several modules right from the beginning, but it’s a powerful feature that helps to break up an app into smaller, coherent units.
View management
Another strong part of Marionette is its sophisticated view management. Views can be nested easily and safely using the aforementioned BabySitter. In addition, Marionette introduces abstractions called Layouts and Regions. A Layout is a view that holds several named Regions. So what is a Region? It’s an object that manages an element in the DOM where it can insert a view. Example regions are header
, navigation
, main
, sidebar
and footer
.
How and where should I render views and append them to the DOM? Regions are the answer. Instead of messing with DOM element references directly, you declare a Region once and later just say mainRegion.show(view)
, for example. This renders the view and attaches it to the DOM element that corresponds to the region. A Region holds only one view at a given time, so the old view is “closed” (i.e. removed from DOM and disposed safely).
With nested regions, building a complex UI gets easier and the code gets more readable and maintainable.
Downsides of Marionette
For brevity, I have just mentioned two unique points of Marionette. Most of its features are mature and well implemented. What I don’t like are thin abstraction layers and unclear best practices.
Routing and controllers
For example, Marionette provides little on top of Backbone.Router. In my opinion, this in important because Backbone.Router provides no convention on how to dispose the objects created (typically models and views) when another route gets active. It’s possible to implement a central cleanup using route
events, but that’s a hack.
In Marionette there are application modules that can be stopped and Regions then can be closed. But as far as I can see, you’re not supposed to start and stop modules over and over and close regions explicitly.
Marionette.AppRouter is a step in the right direction. The idea is to separate the route configuration from the actual handler code. An AppRouter delegates all route matches to a separate Controller instance.
Controllers in Marionette don’t have a single fixed purpose, they just control something. They can listen to events using the Backbone.Events mixin, they have initialize
and close
methods. This is definitely useful, but it’s up to you whether you use them and how. Typically, this is the place where you create models and views.
Global vs. private objects
In Marionette, the modules and classes are saved in a global hierarchical namespace, for example BBCloneMail.MailApp.Controller
. The actual instances don’t have to be global, but it’s tempting to do so. In the BBCloneMail example, some objects are passed and returned while others are global (e.g. BBCloneMail.MailApp.controller
).
From reading the code it’s unclear which objects are global and which are actually accessed globally. When using Marionette, I suggest to implement an object-capability model that defines ways to connect objects without using the global scope.
Templating defaults
Per default, views read their templates from the DOM and compile them using the Underscore template engine (_.template). That’s easy to start with, but it’s not a good practice to embed the template code in your HTML. Eventually, templates should be separated files that can be precompiled and lazy-loaded. Of course, you can change Marionette’s default behavior easily: The Renderer singleton is in charge.
Key features of Chaplin
Compared to Marionette, Chaplin acts more like a framework. It’s more opinionated and has stronger conventions in several areas. It took ideas from server-side MVC frameworks like Ruby on Rails which follow the convention over configuration principle. The goal of Chaplin is to provide well-proven guidelines and a convenient developing environment.
CoffeeScript and OOP
Chaplin is written in CoffeeScript, a meta-language that compiles to JavaScript. However, Chaplin applications do not have to be written in CoffeeScript. In the end, Chaplin is just another JavaScript library.
Using CoffeeScript is part of Chaplin’s idea to make application development easier and more robust. CoffeeScript enforces guidelines from Douglas Crockford’s book “JavaScript – The Good Parts”. Like Marionette, Chaplin is advocating the ECMAScript 5 Strict Mode.
With CoffeeScript, class declarations and class-based inheritance are more compact compared to Backbone’s extend
feature. While Marionette tries to get around super
calls, Chaplin embraces method overriding and tries to make class-based inheritance work smoothly. For example, if you declare event handlers on a derived class and on its super class, both will be applied.
Modularization using CommonJS or AMD
Chaplin requires you to structure your JavaScript code in CommonJS or AMD modules. Every module needs to declare its dependencies and might export a value, for example a constructor function or a single object. In Chaplin, one file typically contains one class and defines one module.
By splitting up your code into reusable modules and declaring dependencies in a machine-readable way, code can be loaded and packaged automatically.
Using AMD isn’t easy, you need to get familiar with loaders like Require.js and optimizers like r.js. As an alternative, you can use the CommonJS module format and Brunch as a processor.
Marionette also supports AMD. You can structure Marionette apps using AMD modules if you like, but it’s not a requirement.
Fixed application structure
Chaplin provides a core application structure that is quite fixed. It handles the main flow in your app.
- The
Application
is the root class that starts the following parts - The
Router
replaces Backbone.Router - The
Dispatcher
starts and stops controllers when a route matches - The
Layout
is the top-level view that observes clicks on links
In Chaplin, there is a central place where to define all routes. A route points to a controller action. For example, the URL pattern /cars/:id
points to cars#show
, that is the show
method of the CarsController
.
A controller is the place where you create models. It’s also responsible for creating the view for the main content area. So a controller usually represents one screen of your app interface.
Object disposal and controlled sharing
The main idea of Chaplin are disposable controllers. The basic rule is: The current controller and all its children (models, collections, views) are disposed when another route matches. Even if the route points to another action of the same controller, the controller instance is disposed and a new one is created.
Throwing objects away when another route matches is a simple and effective rule for cleaning up references. Of course, some objects need to remain in memory in order to reuse them later. The Chaplin.Composer allows you to share models and views in a controlled way. You need to mark them as reusable explicitly. If the saved object is not reused in the next controller action, it is automatically disposed.
In a Chaplin app, every object should be disposable. All Chaplin classes have a dispose
method that will render the object unusable and cut all ties.
Private instances and Publish/Subscribe
A well-known rule of JavaScript programming is to avoid global variables. Chaplin tries to enforce this best practice. Classes are CommonJS/AMD modules that are hidden in a closure scope. All instances should be private. Two instances should not have references to each other unless they are closely related, like a view and its model.
Objects may communicate in a decoupled way using the Publish/Subscribe pattern. For this purpose the Chaplin.Mediator exists. The mediator can also be used to share selected instances globally, like the user object. After creating the necessary properties, the mediator object is sealed so it doesn’t become the kitchen sink of your app.
View management
Chaplin is also strong at view management. It has app-wide named regions and subview managing. Chaplin takes a different approach on rendering views and attaching them to the DOM. Views may have an autoRender
flag and a container
option. With these enabled, views are rendered on creation and are automatically attached to the DOM. Instead of container
, you can specify region
in order to attach the view to a previously registered region.
Apart from the app-wide regions there are no abstraction classes like Marionette.Layout and Marionette.Region. In a Marionette app, you typically create several nested Layouts and Regions. In a Chaplin app, you have fewer key regions and directly nest views inside of them. Of course you can create reusable views that behave like a Marionette.Layout, for example a ThreePaneView
.
Downsides of Chaplin
As one of the main authors of Chaplin, I may be biased. But I do see weaknesses and room for improvement. It’s obvious that Marionette found better solutions to specific problems.
As I pointed out, Chaplin defines each component’s lifecycle and therefore is strong in memory management. When developing Backbone applications, this was one of our major problems. Chaplin found a solution that works well, but it isn’t perfect and it’s surely debatable. This feature already evolved and needs to evolve even further.
For beginners, it’s not easy to grasp the whole Chaplin picture. Memory management, modularization and other Chaplin concepts are still new to many JavaScript developers. While Chaplin’s rigidity seems to be a burden in the beginning, an app will benefit from it in the long term.
Publish/Subscribe isn’t a unique feature of Chaplin but can be compared to Marionette’s application vent. In fact Marionette is more flexible because every application module comes with its own Event Aggregator.
Chaplin is using Publish/Subscribe to broadcast events, but also to trigger commands with callbacks. This is rather a misuse of the pattern. Backbone.Wreqr implements the Command and Request/Response patterns for this purpose. Chaplin should learn from Marionette in this regard.
Conclusion
Marionette is rather modular, you can pick the patterns you like. (In my opinion, you should pick most of them because they can improve your app.) Instead of having one central structure, you can create a composite architecture with independent application modules. This offers great flexibility and allows decoupling, but you need to figure out how to use these building blocks properly.
Chaplin is more like a framework, it’s centralized and rather strict. The Chaplin authors think these guidelines offer convenience and boost productivity. Your mileage may vary, of course.
Because of its goals, Chaplin has a broader scope and deals with several problems that other libraries do not address. For example, Chaplin has a feature-rich routing and dispatching system that replaces Backbone.Router but makes use of Backbone.History.
Compared with Marionette, Chaplin is rather monolithic. That doesn’t mean you can’t do things differently. You can configure, modify or exchange the core classes and break all rules.
Standing on the shoulders of giants
So which library should you pick? I don’t think it’s an exclusive choice. Obviously, you should build upon the library whose core concepts meet your demands. But you should examine both to understand and apply their patterns.
When using Backbone, you need to set up a scalable architecture yourself. Do not write applications in plain Backbone and make the same mistakes others did, but put yourself on the shoulders of giants. Have a deeper look at Marionette, Thorax, Aura, Chaplin and other architectures to learn from them.
To get started with Chaplin, I recommend to use one of the boilerplates:
The CoffeeScript boilerplate with Handlebars templates or the same in plain JavaScript. These incorporate several conventions we consider useful: Folder structure and file naming conventions, coding style, template engines. These are part of “the Chaplin experience”.
If you’re looking for a mature quick-start developing environment, you may give Brunch with Chaplin or Chaplin’s Ruby on Rails boilerplate a try.
For a more hands-on introduction to Marionette, see this article on Smashing Magazine: part one and part two. In the Marionette Wiki, there’s a whole list of articles, screencasts and presentations.
Credits
Thanks to Derick Bailey, Sebastian Deutsch, Paul Miller and Paul Wittmann for their feedback on this article and their contributions to both Marionette and Chaplin.