Introducing Rekishi.

Rekishi

Imagine that you're building a website and you need all links to a particular type of content to open in a modal. On top of that, you would also like a nice transition animation to play whenever the user moves in and out of that content type.

Working with a framework like React or Vue, there are a number of clientside routers and components available to help transition between different pages of content. However, whilst I'm a fan of frontend frameworks, I'm a big believer in incremental development. Instead of reaching straight for a framework, I tend to approach each project from a user-centric path of using the minimum-possible JS, only adding dependencies as I need them.

If you're working closer-to-the-metal with Vanilla JS, coordinating the above example can be ...tricky. In order to capture and handle URLs as a user moves around the site, you need to use the History API.

In a nutshell, the history API lets you capture each URL change, and listen to 'pop' events that fire whenever the user navigates forwards or backwards through history. You are also able to associate a particular 'state' with each change. However, there's a frustration that comes with this. When a 'pop' event is fired the URL has already changed and you no longer have any access the outgoing route, or the state object associated with it.

If you're stringing different types of content together with page transitions, this can make it particularly tricky to choreograph everything. It's precisely this frustration that led me to build Rekishi - a minimal pubsub wrapper for the history API.

Wrapping the API. permalink

Rekishi, like my Tornis library before it, uses a pubsub pattern to listen to any changes in URL. This means that you can 'subscribe' handler functions to Rekishi. Whenever you push, pop or otherwise change the browser's URL, each of those subscribed functions will fire automatically.

The key difference with the standard history API though, is that each of those subscribed functions will have access to an object containing information about both the incoming and outgoing route. That object looks a little like this:

{
incoming: {
path: String,
hash: String,
params: Object,
data: Object
},
outgoing: {
path: String,
hash: String,
params: Object,
data: Object
},
action: REKISHI_PUSH ||
REKISHI_POP ||
REKISHI__HASH ||
REKISHI_PARAMS ||
REKISHI_HASHPARAMS ||
REKISHI_NOCHANGE
}

You might also have noticed that this object contains an 'action' property. This property contains a record of the type of URL change that has occurred. This could have been a change in hash fragment, an update to the query string, or whether the user has clicked a new link or travelled backwards through history.

Similarly, both the incoming and outgoing routes can also have an associated data object. This functions a little bit like the History API's state object, however knowing both incoming and outgoing information allows you to compare and respond accordingly.

In isolation, actions and data information are useful, but in combination they provide a powerful means of choreographing content changes - Returning to our initial problem of transitioning between a modal and a page, Rekishi would allow you to handle that scenario as follows:

const handleRouteChange = ({ incoming, outgoing, action }) => {
switch (action) {
// on either history, or new link events...
case REKISHI_POP:
case REKISHI_PUSH:

if (incoming.data.type == 'modal' && outgoing.data.type == 'page') {
// AJAX the content from incoming.path
// set up your modal and inject AJAX content
}

if (incoming.data.type == 'page' && outgoing.data.type == 'modal') {
//close your modal and remove the content
}

break;
}
};

Associating path data. permalink

Aside from handling URL changes, there's another reason that clientside frameworks like React make page transitions easier. Because they take the rendering of content away from the server entirely, they have access to information about each route ahead-of-time. When building an application with the History API and server-side routing, this approach isn't available to us.

Rekishi also provides a means of bridging the two approaches. When initialising Rekishi, it's possible to pass an array of URL matchers that can associate default data with a particular path. This means that your app will know what data might be coming from a particular URL, before it is hit:

[
{
path: '/posts/*',
data: {
type: 'post'
}
},
{
path: '/posts/riding-the-shinkansen',
data: {
type: 'post',
featured: true
}
}
]

Putting it to the test. permalink

To put Rekishi to the test, I decided to refactor this website to use it instead of the (still very useful) history module. Rekishi is small library (less than 2k when gzipped) and in making the switch I have managed to reduce my JS bundle size by a total of around 4kb. In isolation that figure might not seem much, but it made up roughly 23% of the pre-Rekishi bundle size of approximately 17kb. Not too shabby.

Wrapping up. permalink

All-in-all, I've found that using Rekishi has greatly sped up my workflow for building vanilla JS websites. Using it in conjunction with Tornis to handle interactions, I'm finding that I'm needing to rely less and less on frameworks in my day-to-day website builds. In terms of my end-user's bandwidth, I consider that a very good thing.

I'm hoping that others will find it useful, so I've released Rekishi as an open-source project. The source and documentation is available on GitHub. It is also available to install as a clientside dependency from npm.

Hi, my name is Robb.

I'm a freelance creative developer helping awesome people to build ambitious yet accessible web projects.

Hire me
© MMXX. Gwneud yn Ne Cymru.