What is Cerebellum.js?
With Cerebellum you can achieve fast initial load for mobile clients. Browser bootstraps from server state and continues as a single-page app without any extra configuration. Being able to render the same content in server and client is also a huge boost for search engine visibility as all your content is instantly crawlable.
Cerebellum was born as a result of my experimentations on how to share the same routes between server and client. Piece by piece it has advanced to a state where it’s fully usable for many different usage scenarios.
I’ve developed multiple apps over the years with Backbone.js and it was my weapon of choice framework until I discovered React. However, React handles only the view layer and just replacing Backbone.View with React will not fully utilize React’s strengths and leads to a complex app structure. After considering different options, I didn’t want to give up using Backbone’s excellent collections and models, so I included them as default data structures / API interfaces for Cerebellum.
Under the hood
Cerebellum’s three main pillars are Server, Client and Store.
Server is an express.js app that renders given route and snapshots Store’s state to JSON. You pass your shared routes to Server constructor and you can then add your API routes to the server instance returned from constructor.
Client bootstraps Store’s state from initial JSON and renders given route. When using React, it will do a diff between DOM & virtual DOM, client’s render does not have to touch DOM at all as nothing has changed.
Store is a central dispatcher for all data related operations. Server and client initialize a single instance of Store, it’s created for every request on server side and only once on client side.
You register all your available models and collections with the Store instance, giving them unique names (required for caching).
Cerebellum has unidirectional data flow, everything gets rendered through the router with route handlers. What this means in practice is that your route handlers define what data they need and render the view component when all required data has been fetched.
Cerebellum caches all fetch requests automatically. All data you fetch on server side gets transferred to client as a JSON snapshot, so you won’t need to fetch the same data again on client side.
When you need to change data, you send a change event (create, update or delete) to Store instance that processes the corresponding API request for given collection or model. When the API request finishes, Store triggers a success event that you can react to on your client. Usually you want to clear the cache for modified collection/model and re-render your view with new data by triggering a route handler.
Cerebellum’s data flow is heavily inspired by Flux architecture. The biggest difference to Flux is that you pass the data down the tree as props instead of each component having their local state updated by events. For situations where you need the extra level of decoupling that Flux provides, you can replace the built-in Store with any Flux implementation. Cerebellum is designed to be as non-restrictive and decoupled as possible.
Cerebellum comes with Vertebrae’s collections and models. Vertebrae is a CommonJS version of Backbone. However, you are free to use any collection & model implementation you like, as Cerebellum’s Store has no direct dependencies on any data library.
GET routes are 100% shared between server and client and you’ll only need POST, PUT and DELETE in your APIs. Cerebellum uses express.js‘ router on the server and page.js‘ router on the client. They both use path-to-regexp module for parsing the routes, so you can use named parameters, optional parameters and regular expressions.
Check out this routes configuration for a real example.
Use any (isomorphic) view layer
Cerebellum is view layer neutral, so feel free to implement your client’s and server’s render methods with the framework of your choice. Obviously it wouldn’t make sense to re-render everything with traditional view layer like Backbone’s View, but when using virtual DOM based solution like React, you can safely invoke your route handlers to re-render whenever you like.
Recently I’ve experimented with integrating Omniscient.js with Cerebellum. That integration still needs some helpers to reduce the amount of code needed for immutable data updates and better way to share the initial data fetch code between server and client. Using cursors and sub-cursors to provide data for a tree of components, with a listener at the top level, is certainly a great pattern and it will be greatly helpful in certain types of applications.
I’ve also been thinking about adding an option to automatically convert the collection & model data to Immutable.js structures after fetch. That way view components could perform fast equality checks for passed in data and they would have all the Immutable’s excellent Map and List methods available.