3 routers libraries tested.

By | June 19, 2017

Today i will talk about 3 routers libraries that i have tested. Crossroad, Navigo and Router_JS. Router library is the fundamental point of your system if you want to build a client application in JavaScript that not use a big Framework like Angular or Ember. I know, who really do that ? :p. So for the fun, I have test this three libraries with a webpack 2 + Handlebar bundle and i tried to achieve the same result as Angular routing that consist in :

  • Support of Nested Route
  • Auto injection of Component fields inside the template.
  • Support for route with parameter that are accessible to the component.

To create the Component i have use the babel-plugin-transform-decorators-legacy webpack plug-in that support decorator. So i can annotate my component with @Component, like angular does. This annotation will content the template and the selector of the component:

@Component({
    template: template,
    selector: 'app-container'
})
export default class App {
    constructor() {
        this.title = 'foo';
        this.type = 'bar';
    }

    submit() {
    }

}

So let’s Go !

Crossroads:

Crossroad is a minimalist library that have only one dependencies as JS-Signals to handle system event.The documentation is succinct but clear. And the API is well written. With Crossroad you need to take care of history changes by your own. The author recommand the hasher library that he is develop too.

This is a sample code that i have done to handle the criteria in introduction

/**
 * Bootstrap all the route applications.
 */
function bootstrapRoutes() {
    // Get route paramater as object.
    crossroads.normalizeFn = crossroads.NORM_AS_OBJECT;
    routes.forEach(route => {
        // Adding route to the rooter
        let crossRoute = crossroads.addRoute(route.path, (params) => {
            let url = {
                path: crossRoute._pattern,
                params: params
            }
            // Add route parameter to the component. So that component can access it.
            route.component.prototype.url = url;
            // Building component instance
            let instance = new route.component;
            // Hydrate template with component attribute.
            let injectedData = hydrateTemplate(instance);
            // Inject component into the DOM.
            document.querySelector('router-outlet').innerHTML = instance.template(injectedData);
        });
    });

    // Tell crossroad to handle the browser url.
    crossroads.parse(document.location.pathname);
}

/**
 * This function take a class instance as parameter
 * and hydrate handlebar template
 * @param {*} clazz The class to hydrate
 */
function hydrateTemplate(clazz) {
    let injectedData = {};
    let fields = Object.getOwnPropertyNames(clazz);
    fields.map(field => {
        injectedData[field] = clazz[field];
    });
    return injectedData;
}

This is a simple code and it can be improve. You can for example create a true object. But it works for simple routes. Nested route are not supported and i haven’t find this support inside the library. So if you want this common functionality. You will needs to implement it by yourself.

Pro :

  • Easy  to understand and write.
  • Rules base validation parameters

Cons :

  • Lacks support of nested route.

Navigo

Navigo is a  simple minimalistic JavaScript router with a fallback for older browsers. It use Hash as fallback. Like Crossroad it does not support nested route. The Api is really clear. It support chain method, params URL, hook, navigation.

This is the code that i have use.


export default class Bootstrap {
    constructor() {
        this.router = new Navigo();
        this.outlet = document.querySelector('router-outlet');
    };

    bootstrapRoutes(routes) {

        routes.forEach(route => {
             this.activateLink();
             this.buildRoute(route);
        });
        router.resolve();
    }

    buildRoute(route) {
        this.router.on(route.path, params => {
            console.log('Route activated', route.path);
            let url = {
                path: route.path,
                params: params
            }
            route.component.prototype.url = url;
            let instance = new route.component;
            let injectedData = this.hydrateTemplate(instance);
            let template = instance.template(injectedData);
            let component = '<' + instance.selector + '>' + template + '</' + instance.selector + '>';
            this.outlet.innerHTML = component;
            if (route.children !== undefined) {
                route.children.forEach(child => {
                    this.outlet = document.querySelector(instance.selector + ' router-outlet');
                    console.log(route.path + child.path);
                    this.buildRoute(route.path + child.path);
                });
            }
        });
    }

    /*
    * Tell navigo to handle link click event.
    * You can use router.updatePageLinks() that take all link with data-navigo attribute.
    */
    activateLink() {
        let links = document.querySelectorAll('a');
        links.forEach(link => {
            var location = link.getAttribute('href');
            link.addEventListener('click', e => {
                e.preventDefault();
                this.router.navigate(location);
            });
        });
    }

    /**
    * This function take a class instance as parameter
    * and hydrate handlebar template
    * @param {*} clazz The class to hydrate
    */
    hydrateTemplate(clazz) {
        let injectedData = {};
        let fields = Object.getOwnPropertyNames(clazz);
        fields.map(field => {
            injectedData[field] = clazz[field];
        });
        return injectedData;
    }
}

Navigo is a great library like Crossroad. I think i prefer it because of chaining method support.

Pro :

  • Simple to understand
  • Easy to write
  • Chaining function support
  • Hook support.

Cons :

  • Lack support of nested route.

Router_JS

Router_js is the library used in EmberJS so it has as lots of features. And support Nested route. It is the most powerfull of the three libraries. I found the API less clear than the two others but enough to be easy to used as well. This library needs dependencies ( route-recognizer and rsvp), but i haven’t be able to set up it. It does not support import via ES6 out of the box. A issues is open about this. I will update the review as soon i possible with the solution.

Edit : This is the code that work for simple route (No nested). I will update, when i will have find how to make nested road working.

Edit2: The example is now working for nested route. It’s a naive implementation that use router-outlet order to insert the children routes.


let Router = require('../../node_modules/router_js/dist/commonjs/main');
const uuidv4 = require('uuid/v4');

export default class Bootstrap {
    constructor() {
        this.router = new Router.default();

        this.myHandlers = {}
    };

    bootstrapRoutes(routes) {
        this.router.map(match => {
            routes.forEach(route => {
                this.router.map(match => {
                    this.buildRoute(match, route);
                });
            })
        });
        this.router.getHandler = name => {
            return this.myHandlers[name];
        };
        this.activateLink();
    }

    buildRoute(match, route) {
        let routeName = uuidv4(); // Generate unique route name for the router.
        if (route.children === undefined) {
            match(route.path).to(routeName);
        } else {
            match(route.path).to(routeName, match => {
                route.children.forEach(child => {
                    this.buildRoute(match, child);
                });
            });
        }
        this.myHandlers[routeName] = {
            // Get url params and pass it to setup method.
            model: params => {
                let url = {
                    params: params
                }
                return url;
            },
            // Set url param to the component, hydrate template and insert component into the dom.
            setup: url => {
                //Get all outlets present on the page.
                let outlets = document.querySelectorAll('router-outlet');
                //Select the last outlet
                let outlet = outlets[outlets.length - 1];
                route.component.prototype.url = url;
                let instance = new route.component;
                let injectedData = this.hydrateTemplate(instance);
                let template = instance.template(injectedData);
                let component = '<' + instance.selector + '>' + template + '</' + instance.selector + '>';
                outlet.innerHTML = component;
            }
        };
    }

    /**
     * Tell router to handle click on link.
     */
    activateLink() {
        let links = document.querySelectorAll('a');
        links.forEach(link => {
            var location = link.getAttribute('href');
            link.addEventListener('click', e => {
                e.preventDefault();
                this.router.handleURL(location);
            });
        });
    }

    /**
    * This function take a class instance as parameter
    * and hydrate handlebar template
    * @param {*} clazz The class to hydrate
    */
    hydrateTemplate(clazz) {
        let injectedData = {};
        let fields = Object.getOwnPropertyNames(clazz);
        fields.map(field => {
            injectedData[field] = clazz[field];
        });
        return injectedData;
    }
}

Pro :

  • more customizable than the 2 others.
  • Nested route support

Cons :

  • Does not support ES6 import

Conclusion :

Set up a full client application that handle rooting is not impossible but is not trivial. If you needs just basics features ans routes, it will be OK. But as you can see, only one of the three libraries handle nested route natively. For the two others you will have to take care of it yourself. So if you application is more complexe, just go for framework.

See you soon and leave comment if you have another good libraries

00

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.