npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

prouter

v10.0.26

Published

Fast, unopinionated, minimalist client-side routing library inspired by the simplicity and flexibility of express middlewares

Downloads

279

Readme

prouter

license tests coverage status npm version

Fast, unopinionated, minimalist client-side routing library inspired by the simplicity and flexibility of express middlewares.

Essentially, give prouter a list of path expressions (routes) and a callback function (handler) for each one, and prouter will automatically invoke these callbacks according to the active path in the URL.

Why prouter?

  • Performance: fast and tiny size (currently under 5kb before gzipping) are both must-haves to smoothly run in any mobile or desktop browser.
  • KISS principle everywhere: do only one thing and do it well, routing! Guards? conditional execution? generic pre and post middlewares? all that and more is easily achievable with prouter (see examples below).
  • Learn once: express router is very powerful, flexible, and simple, why not bring a similar API to the frontend? Under the hood, prouter uses the same (wonderful) library that express for parsing routes path-to-regexp (so it allows the same flexibility to declare routes). Read more about the concept of middlewares here.
  • Unobtrusive: it is designed from the beginning to play well with vanilla JavaScript or with any other library or framework.
  • Forward-thinking: written in TypeScript for the future and transpiled to es5 with UMD format for the present... thus it transparently supports any module style: es6, commonJS, AMD. By default, prouter uses the modern history API for routing.
  • Unit tests for every feature are created.

Installation

# With NPM
npm install prouter --save

# Or with Yarn
yarn prouter --save

# Or just include it using a 'script' tag in your HTML file
<script src="https://cdn.jsdelivr.net/npm/prouter/prouter.min.js"></script>

Examples

basic

// Using es6 modules
import { browserRouter } from 'prouter';

// Instantiate the router
const router = browserRouter();

// Declare the paths and its respective handlers
router
  .use('/', async (req, resp) => {
    const people = await personService.find();
    const html = PersonListCmp(people);
    document.querySelector('.router-outlet') = html;
    // end the request-response cycle
    resp.end();
  })
  .use('/about', (req, resp) => {
    document.querySelector('.router-outlet') =
      `<h1>Some static content for the About page.</h1>`;
    // end the request-response cycle
    resp.end();
  });

// start listening for navigation events
router.listen();

guard middleware which conditionally avoid executing next handlers and prevent changing the path in the URL

// Using commonJs modules
const prouter = require('prouter');

// Instantiate the router
const router = prouter.browserRouter({
  processHashChange: true // this allows to process 'hash' changes in the URL.
});

// Declare the paths and its respective handlers
router
  .use('*', (req, resp, next) => {
    // this handler will run for any routing event, before any other handlers

    const isAllowed = authService.validateHasAccessToUrl(req.path);

    if (!isAllowed) {
      showAlert("You haven't rights to access the page: " + destPath);
      // end the request-response cycle, avoid executing other handlers
      // and prevent changing the path in the URL.
      resp.preventNavigation = true;
      resp.end();
      return;
    }

    // pass control to the next handler
    next();
  })
  .use('/', (req, resp) => {
    // do some stuff...
    // and end the request-response cycle
    resp.end();
  })
  .use('/admin', (req, resp) => {
    // do some stuff...
    // and end the request-response cycle
    resp.end();
  });

// start listening for navigation events
router.listen();

// programmatically try to navigate to any route in your router
router.push('/admin');

run a generic middleware (for doing some generic stuff) after running specific handlers

import { browserRouter } from 'prouter';

// Instantiate the router
const router = browserRouter();

// Declare the paths and its respective handlers
router
  .use('/', async (req, resp, next) => {
    const people = await personService.find();
    const html = PersonListCmp(people);
    document.querySelector('.router-outlet') = html;
    // pass control to the next handler
    next();
  })
  .use('*', (req, resp) => {
    // do some (generic) stuff...
    // and end the request-response cycle
    resp.end();
  });

// start listening for navigation events
router.listen();

modularize your routing code in different files using Router Group

import { browserRouter, routerGroup } from 'prouter';

// this can be in a different file for modularization of the routes,
// and then import it in your main routes file and mount it.
const productRouterGroup = routerGroup();

productRouterGroup
  .use('/', (req, resp) => {
    // do some stuff...
    // and end the request-response cycle
    resp.end();
  })
  .use('/create', (req, resp) => {
    // do some stuff...
    // and end the request-response cycle
    resp.end();
  })
  .use('/:id(\\d+)', (req, resp) => {
    const id = req.params.id;
    // do some stuff with the 'id'...
    // and end the request-response cycle
    resp.end();
  });

// Instantiate the router
const router = browserRouter();

// Declare the paths and its respective handlers
router
  .use('*', (req, resp, next) => {
    // this handler will run for any routing event, before any other handlers
    console.log('request info', req);
    // pass control to the next handler
    next();
  })
  .use('/', (req, resp) => {
    // do some stuff...
    // and end the request-response cycle
    resp.end();
  })
  // mount the product's group of handlers using this base path
  .use('/product', productRouterGroup);

// start listening for the routing
router.listen();

// programmatically navigate to the detail of the product with this ID
router.push('/product/123');

full example: modularized routing, generic pre handler acting as a guard, generic post handler

import { browserRouter, routerGroup } from 'prouter';

// this can be in a different file for modularization of the routes,
// and then import it in your main routes file and mount it.
const productRouterGroup = routerGroup();

productRouterGroup
  .use('/', (req, resp, next) => {
    // do some stuff...
    // and pass control to the next handler
    next();
  })
  .use('/create', (req, resp, next) => {
    // do some stuff...
    // and pass control to the next handler
    next();
  })
  .use('/:id(\\d+)', (req, resp, next) => {
    const id = req.params.id;
    // do some stuff with the 'id'...
    // and pass control to the next handler
    next();
  });

// Instantiate the router
const router = browserRouter();

// Declare the paths and its respective handlers
router
  .use('*', (req, resp, next) => {

    // this handler will run for any routing event, before any other handlers

    const isAllowed = authService.validateHasAccessToUrl(req.path);

    if (!isAllowed) {
      showAlert("You haven't rights to access the page: " + destPath);
      // end the request-response cycle, avoid executing next handlers
      // and prevent changing the path in the URL.
      resp.preventNavigation = true;
      resp.end();
      return;
    }

    // pass control to the next handler
    next();
  })
  .use('/', (req, resp, next) => {

    const doInfiniteScroll = () => {
      // do infinite scroll ...
    };

    const onNavigation = (navigationEvt) => {
      console.log('new path', navigationEvt.oldPath);
      console.log('old path', navigationEvt.newPath);
      // if navigating, then remove the listener for the window.scroll.
      router.off('navigation', onNavigation);
      window.removeEventListener('scroll', doInfiniteScroll);
    };

    window.addEventListener('scroll', doInfiniteScroll);

    // subscribe to the navigation event
    router.on('navigation', onNavigation);

    // and pass control to the next handler
    next();
  })
  .use('/login', () => {
    openLoginModal();
    // as this route opens a modal, we would want to prevent navigation in this handler,
    // so end the request-response cycle, avoid executing next handlers
    // and prevent changing the path in the URL.
    resp.preventNavigation = true;
    resp.end();
  })
  .use('/admin', (req, resp, next) => {
    // do some stuff...
    // and pass control to the next handler
    next();
  })
  // mount the product's group of handlers using this base path
  .use('/product', productRouterGroup)
  .use('*', (req, res, next) => {

    // this handler will run for any routing event, after the other handlers

    // req.listening will be true when this callback was called due to a
    // client-side navigation (useful to differentiate client-side vs
    // server-side rendering - when using a mix of both SSR and CSR)
    if (req.listening) {
      const title = inferTitleFromPath(req.path, APP_TITLE);
      updatePageTitle(title);
    }

    // end the request-response cycle
    resp.end();
  });

// start listening for the routing
router.listen();


// the below code is an example about how you could capture clicks on links,
// and accordingly, trigger routing navigation in your app
// (typically, you would put it in a separated file)

export function isNavigationPath(path: string) {
  return !!path && !path.startsWith('javascript:void');
}

export function isExternalPath(path: string) {
  return /^https?:\/\//.test(path);
}

export function isApplicationPath(path: string) {
  return isNavigationPath(path) && !isExternalPath(path);
}

document.body.addEventListener('click', (evt) => {

    const target = evt.target as Element;
    let link: Element;

    if (target.nodeName === 'A') {
      link = target;
    } else {
      link = target.closest('a');
      if (!link) {
        return;
      }
    }

    const url = link.getAttribute('href');

    // do nothing if it is not an app's internal link
    if (!isApplicationPath(url)) {
      return;
    }

    // avoid the default browser's behaviour when clicking on a link
    // (i.e. do not reload the page).
    evt.preventDefault();

    // it is a normal app's link, so trigger the routing navigation
    router.push(url);
  });

see more advanced usages in the unit tests.