I’ve just finished writing the documentation for a client-side routing library which I’m rather proud of, Routing.js.

The Previous Incarnation

Over the past year or so at my work we’ve been using a library called PathJS. This is a lightweight, simple-to-use routing library which worked absolutely fine for us… until recently when I came across a limitation.

No problem, I thought. PathJS is hosted on GitHub so I’ll just fork it, add a few extra lines of JavaScript and the world will be thanking me.

So I unwrapped it and took a look inside. Now, don’t get me wrong, PathJS has obviously been written by someone with a good understanding of JavaScript, and it works well. But I couldn’t for the life of me get my head around the convoluted string-parsing logic that was being used internally.

And once I had an understanding of the internal processes of PathJS I started to see how optimisations could be made, as well as new features added.

The Back Story

To step back a little, let me explain what a client-side routing library is.

There’s a trend for building websites in a style known as Single-page applications. This means that when the website loads, a page loads. So far so normal. The difference between a traditional website and a single-page application lies in what happens when you click a link, a button, etc.: whereas a traditional website will load a new webpage, a single-page application will load new content onto the same webpage. It may look as if a new page has loaded, depending on how much content has changed, but certain aspects of the page will have remained static over that content-loading: perhaps a menu bar stayed showing the same selections, for example. Usually the telltale sign is that some animation effect took place and only a part of the page (perhaps behind the animation) was changed.

The trick behind loading user-requested content onto these single-page applications lies in a specific section of the URL.

#

URLs have for a long time offered a ‘hash’ portion. You may sometimes see that a URL contains a ‘#’ symbol. Traditionally this has been used to guide the user towards a specific section of the page (for example, a long article may have an index at the start, and clicking on titles within that index causes the page to scroll to that part of the page). Single-page applications have re-appropriated the ‘hash portion’, and they do this by capturing whenever a change occurs to this part of the URL.

window.onhashchange = function() {
   // do something here
};

So capturing changes to the URL’s hash is one part of client-side routing, and the other aspect lies in how simply JavaScript allows developers to change the hash portion.

window.location.hash = “new-route”;

Putting these two aspects together allows the site’s developer to write JavaScript that changes a specific part of the URL, and then also to write some top-level, encompassing script which captures when that part of the URL changes and then do something with it.

More advanced use cases allow developers to place dynamic parameters into the URL, and even optional parameters.

To make use of this client-side routing is also a two-stage process: the developer must first of all define routes, and then create the actions which cause the hash portion to be directed to one of these pre-defined routes. In Routing.js this is done in the following manner

Routing.map("#/new-route")    // defining a route
    .to(function () {    // defining what happens when route is ‘hit’
        // change the content
    });

Goodbye PathJS, hello Routing.js

So now that the background has been laid out, I can explain that I was having difficulty with the way in which PathJS determined which pre-defined route to use for the current hash portion. I was looking because I wanted to change the way in which route parameters were presented to the function which handled a route. I looked at it and came to the realisation that if I wanted to change the parameter handling, I would have to change the routing decision-making.

I did this in a basic way one afternoon at work but came away thinking that PathJS was inefficiently running the decision-making process over and over again, potentially looking through every route before finding a match, and doing this every time the route changed, regardless of whether it had previously determined which route to implement for a given hash portion.

So over a weekend I forked the original branch of PathJS on GitHub, and rewrote the internal logic. Broadly speaking, my changes treat the hash portion as a series of objects rather than one text string, and this is done ‘up-front’, i.e. when a route is declared rather than when a new route has been requested by the URL. As a consequence of this there are a couple of ways in which the number of candidate routes can be filtered down, which allows Routing.js to arrive at a decision sooner. I’ve also introduced route caching so that if a decision has previously been made for a route then it can quickly be retrieved rather than going through the decision-making process again.

Putting it back out there

The author of PathJS has cleverly provided a small test suite in the form of a webpage, so I then had to modify the tests to verify my changes.

I also added a couple of other features to cut down on repetition when declaring routes, so I then had to add some extra tests for these.

Then came the boring part: documentation. I was of course able to fork the wiki pages from the original PathJS branch and then modify these for Routing.js, as well as adding a couple of other pages to better explain some of the new aspects.

But I’ve come out the other side of this work with a slight feeling of guilt: about 50% or more of the extant work (both in the JavaScript, the tests and the documentation) is from the original branch of PathJS, yet it shows up on a webpage with my GitHub username and under the name that I’ve assigned the project, i.e. Routing.js. Incidentally, I chose to change the name for a couple of reasons:

  1. because Routing.js is not backward-compatible with installations of PathJS
  2. because PathJS hasn’t been touched in 3 years

My hope is that the copyright at the top of Routing.js’ JavaScript and on the homepage for Routing.js on GitHub is sufficient, because the original author did same great work.

Finally, the last step was to create a NuGet package for the script.

Once again, here is the homepage for Routing.js.