Sunday, Coffeeshop and Cherrytree
First things first, why is cherrytree written as a single word instead of cherry-tree? You got me, it’s been bothering me for a while too. I’d say that represents the wabisabi nature of the library.
What’s the big deal?
Cherrytree is in a lot of ways similar to Ember router and react-router. Both of those projects are super sweet. However, the key objective for Cherrytree router has always been — no coupling to any framework. I’d like to think that creating a smaller and more generic package can also be a sign of a better design. Think about how redux split out react-redux making redux simpler, clearer and most important — reusable in any application backed by any framework. On the other hand, a more comprehensive solution can have it’s advantages too — establishing a common vocabulary and best patterns in the community, enabling an ecosystem, helping you hit the ground running without spending forever figuring out the edge cases.
The goal of cherrytree however is to allow using the nested routing concepts you find in Ember and react-router, but with any framework, such as Backbone, Cycle.js, deku, virtual-dom or even Ember and React. Sidenote: I’ve only used it with Backbone and React so far, do let me know if you managed to use it with some other framework — that would be exciting.
Cherrytree was first created while building a Backbone app at Qubit. As the app grew I felt the need for more sophisticated routing. It’s very important for any web app to handle
- opening links in new tabs
- usage of browser’s back/forward buttons
- always, I mean always, correctly keeping the URL and app state in sync
There’s a great talk by Tom Dale — Stop Breaking the Web that explores this topic in more depth.
At Qubit, we went through several approaches in our app — Backbone’s in built router, Chaplin.js controller approach, navstack.js — a routing library based off @augustl’s work, which was one of the first inspirations for cherrytree. The problem with all of those solutions back then was that the API didn’t quite feel right making it difficult to structure code and think about routing. Those routers didn’t handle asynchronous transitions very well and they didn’t help with the nested nature of UIs. All of those problems meant the app just wouldn’t feel very solid when navigating around and the code felt brittle when managing the different states of nested UIs and fetching nested resources.
For example, imagine a page for a user profile that has a sidebar. Navigating to a subsection of this profile using a link in the sidebar should not re-request the user profile from the server since we already have it loaded. And now imagine doing such a thing in Backbone router where a new URL simply triggers a single function call that now needs to somehow know about what data is already fetched and what is already rendered on the screen.
I’m not saying that a router is necessarily the best place to help with fetching data for such nested UIs — it simply was one of the reasons back then that made me look for better routing. Today, the thinking on these topics is changing considerably. Utilising the concepts of virtual DOM and immutability, it’s no longer expensive to re-render the entire app. Consequently, state management is being re-thought with projects such as the legendary Redux. Perhaps — it’s also time for a completely fresh take on routing.
Back then (~3 years ago) — Ember was the first to solve the routing problem. The guys at Ember came up with a really neat solution. Some of the core ideas they introduced were nested routes, transitions as first class citizens, route lifecycle hooks responsible for fetching data, rendering views, cleaning up between transitions and a very clean API / DSL for defining the routes that inspired a lot of (all?) future routers.
For example react-router takes a lot of ideas from Ember’s routing. This is how react-router got started in 2014
Other routers were also inspired by the nested concept in Ember - for example:
- Angular’s ui-router https://github.com/angular-ui/ui-router
- non-mainstream libs such as https://github.com/AlexGalays/abyssa-js
- the first wave of react routers https://github.com/andreypopp/rrouter
We tried using Ember’s tildeio/router.js. It is separate from Ember, meaning it’s not coupled to any framework. However, I found it quite difficult to use in practise and I felt that the line has been drawn in a slightly wrong place between what was put into router.js and what stayed in Ember router, making router.js not useful enough by itself (e.g. no browser history management) and still too tailored to Ember’s requirements (the multitude of hooks such as beforeMode/model/afterModel). Your mileage may vary. But very importantly — I’m grateful to tildeio/router.js since Cherrytree 1.0.0 series used it as the core engine for a long time and it remains the biggest inspiration for cherrytree.
Cherrytree crash course
Now for a quick intro to Cherrytree. It is a hierarchical router that translates every url change into a transition object and then calls all your middleware with that transition object. We’ll see what that means in a second. First, cherrytree’s core API consists of 3 functions:
- map — provide your route configuration
- use — install middleware that operate on transitions
- listen — start listening to URL changes and transition to the state matching the current URL
There’s also 3 other functions that you usually use from within your app, after the router has been started:
- transitionTo — navigate to a new route
- replaceWith — like transitionTo, but does not add a browser history entry (so clicking browser’s back button will skip this route)
- generate — create a URL from route name and params
Let’s take a look at an example.
As you navigate through your app, each transition matches the URL to an array of route descriptor objects and passes them through all of the middleware. You can get a feel for how that works in the cherrytree logs:
The concept of cherrytree middleware can be used for things such as:
- rendering (as in the example above)
- loading parts of the app dynamically during transitions
- fetching data
- adding declarative redirect functionality
- implementing hooks such as `willTransitionTo`, `activate`, `deactivate`
- optimizing the rendering in non virtual-dom apps and optimizing the data fetching by computing already active routes by comparing transition.prev and transition and marking each route descriptor with an `alreadyActive` flag
- displaying a global (or local) loading animation
- creating login redirects
- managing browser scroll position
- handling errors
For example, here’s the middleware used in the original cherrytree app
I’ll share the code for those in some other blog post if anyone’s interested.
I hope this demonstrates enough of Cherrytree functionality for you to see if it could be useful in your own apps. For working examples that you can run on your machine visit the main cherrytree repo.
And by the way, while we’re on the topic of routing, the pushState/hashchange browser interaction in cherrytree is abstracted using this tiny library called location-bar which itself has been extracted from the beautiful Backbone source code (with all of the Backbone tests still passing). Check it out if you need to interact with the browser URL — the core API there consists of `update` and `onChange`functions that let you update the URL in brower’s location bar and listen to any changes to it.
Wow, I can’t believe you’re still with me. On to the last part of this blog post then. This next part is a review of what I was up to today. I basically had a crack at finally implementing some examples of how one would use cherrytree with React (and Redux and react-hot-loader). I’ve created and updated the following npm packages and repos:
I’ve created the first official cherrytree plugin — cherrytree-for-react. The implementation of the rendering logic and Link component was informed by the implementation of react-router’s <Router> and <Link> components. The implementation was also informed by react-redux’s <Provider> component in order to make the whole thing hot reloadable.
Turns out supporting hot-reloading in a React app is quite simple — react-hot-loader preserves the state of each component between the hot reloads. So keeping the cherrytree router in the state of the <Router> component allows us to destroy() it during hot reloads in case a new instance of cherrytree is passed in via props. Destroying is important in order to detach browser event listeners in case we’re starting a new cherrytree instance. You can see a live, github hosted example using the cherrytree-for-react at http://kidkarolis.github.io/cherrytree-redux-react-example. This is just a first cut of the project, in particular, I’d like to make server side and client side usage more symmetric.
Another thing I’ve put together is an example of how one would use cherrytree on the server in an express app. Here’s the gist of how it works.
Visit cherrytree/examples/server-side-react to view the full working example.
Perhaps next — I should smash both of those examples (the client side and the server side) into a single isomorph.. I mean universal app.
There are lots of more interesting things left to explore here besides the isomorphism. For example, keeping the router state in a redux store sounds like it could be the right thing to do. There’s already a cool project for react-router doing just that over at https://github.com/acdlite/redux-react-router. Another thing I haven’t fully explored in these React examples is the different approaches to data fetching and combining that with asynchronous transitions.
We’ve been using Cherrytree 2.0.0-alpha at Qubit since April and I kind of forgot I never pushed the 2.0.0 stable. Here’s the 2.0.0 roadmap https://github.com/QubitProducts/cherrytree/issues/69 with 2.0.0 stable coming soon. Hopefully. (wanna help?)
To install the 2.0.0-alpha and not 1.0.0, you currently must use the @latest tag.
npm install --save cherrytree@latest
Keep calm and route on! If you’re a routing geek (or not)—follow me at https://twitter.com/KidkArolis.