published on Ramiel's Creations
Saturday, September 7, 2019
!#/ at the end of the regular url. Inside that library there were some good ideas and it has been used a lot, so it is a good starting point.
If you aren't interested in reasons behind the code or if you prefer to get your hands dirty, go checkout these libraries:
My idea is that the library should be "view agnostic". I'd say more, it must be framework agnostic. It should be possible to use it with vanilla JS, React, Vue, outside of the browser or anywhere else you like. Let's focus on the meaning of being view agnostic. I feel like a routing library is really unrelated from the fact that we use it to show a particular view: we will do it eventually, in most of the cases, but showing a view is a side effect of the routing. Routing is the ability to map a state (usually the current url) with another state. Actually the pushState API does the same: it lets you define a state while navigating with the browser but tells nothing about what should be shown on the screen. This is what I missed so much using react-router: it is so coupled with the view, the react components, that is hard to use it for other use cases. It's not impossible, it's just unnatural for me.
So let's try to stay simple and say that a route is a function bound to an url match. This is what happens in express.js or zeit-micro. Here how to do it with RouterJS:
simple routes definition
The take is easy: define a route through a string (in this case there are also named parameters like
:name) and bind a function to it. This is the simplest pattern we can have for a routing library and in fact it's very, very common.
Where is defined which view to show? Nowhere. We're free to setup our own mechanism to select a view inside those functions or use a more generic approach: for example we can create a React library that accepts the route definition and somehow show a View, but we'll see this later or, if you're curious, read the "React integration" section.
What changes, in all of these cases, is the way we navigate, the way we understand the route is changed and so on. For this reason the router accepts an engine. The default is an engine based on browser history but we can pass our own implementation.
While just the first engine has a real implementation and maybe the other makes no sense at all (I'm not very practical of react native coding :p), you have more freedom now and you can implement your own engine with peculiar features. An engine is something very trivial: a function that returns an object with various methods, to get the current route or to execute redirects and so on.
Middleware is a handy concept: basically the idea is to provide a set of components (middleware) that run before your main function. The most known implementation of middlewares is the one of
express but I think the most powerful one comes from
zeit-micro: a middleware is a function. Nothing special, just a function. It has no metadata associated, no special class derivation, no factory. Nothing, just a function. So in the frontend router of my dreams a middleware is a function. A similar approach is taken by
page.js by visionmedia. An additional feature I need to have is that an error should stop normal flow.
Now, let's see the most classical middleware example: authorization. The idea is to forbid a route if the user is not authenticated. First of all lets see how a route function is done:
A typical, simple route Now let's implement a middleware that checks if the user is authenticated
A simple middleware, a function So, a middleware is a function that takes the next function as parameter (which can be another middleware) and applies some logic. Let's apply this to our protected route
If you need to apply more middlewares you just need to wrap one in the other, or use a typical compose function, provided by the library.
Middleware composition Easy, isn't it?
There's not enough space in this post to explain what else is available in the library, so you should have a look at the official documentation, you will find:
Let's talk about the integration with React. Even if I talked a lot about how the router library shouldn't be tied to the view system, this doesn't mean they should not interact. It's very powerful, in my opinion, to have them separated, the same way redux can work without React even if they're often coupled. The integration is left to another little module called
[react-routerjs](https://github.com/ramiel/react-routerjs). Let's see how it works.
First, it provides you a RouterProvider, similar to the one from redux or similar libraries. It accepts your routing definition:
This should be placed as on top as possible in your components hierarchy. It's really nothing fancy, just a context provider.
This give you access to a series of components. One of this is a
Link component. While RouterJS doesn't need any special component for the links, you may have defined your routes with a
basePath because your application is served under an
/app path for example. So, any time you want an anchor, you should write something like
Link component can read the router configuration and let you write
instead, taking care of transforming the url in the correct, expanded path.
The main usage of the React binding is the ability to select a view component. Let's see it in action:
Later we can use the
<RouteView> component to show it in the application
<RouteView /> component will use the
Home or the
Profile components to show the correct view. Since we can return a promise through React.lazy, we'll take advantage of React Suspense to lazy load our components 😍
withView couple can also specify a target to set the current view for different parts of the application.
If you liked what you read, discover the libraries on Github, there's much more!