Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/rogerpadilla/prouter
Fast, unopinionated, minimalist client-side routing library inspired by the simplicity and flexibility of express middlewares
https://github.com/rogerpadilla/prouter
browser frontend library mobile router routing web
Last synced: 9 days ago
JSON representation
Fast, unopinionated, minimalist client-side routing library inspired by the simplicity and flexibility of express middlewares
- Host: GitHub
- URL: https://github.com/rogerpadilla/prouter
- Owner: rogerpadilla
- License: mit
- Created: 2015-04-16T13:13:08.000Z (over 9 years ago)
- Default Branch: main
- Last Pushed: 2024-10-30T11:32:41.000Z (9 days ago)
- Last Synced: 2024-10-30T12:28:37.900Z (9 days ago)
- Topics: browser, frontend, library, mobile, router, routing, web
- Language: TypeScript
- Homepage:
- Size: 1.61 MB
- Stars: 51
- Watchers: 6
- Forks: 6
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# prouter
[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/rogerpadilla/prouter/blob/main/LICENSE)
[![tests](https://github.com/rogerpadilla/prouter/actions/workflows/tests.yml/badge.svg)](https://github.com/rogerpadilla/prouter)
[![coverage status](https://coveralls.io/repos/github/rogerpadilla/prouter/badge.svg)](https://coveralls.io/github/rogerpadilla/prouter)
[![npm version](https://badge.fury.io/js/prouter.svg)](https://www.npmjs.com/prouter)Fast, unopinionated, minimalist client-side routing library inspired by the simplicity and flexibility of [express middlewares](https://expressjs.com/en/guide/writing-middleware.html).
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](https://github.com/rogerpadilla/prouter/blob/master/src/browser-router.spec.ts#L7) 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](https://github.com/pillarjs/path-to-regexp) (so it allows the same flexibility to declare routes). Read more about the concept of middlewares [here](https://expressjs.com/en/guide/writing-middleware.html).
- **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](https://developer.mozilla.org/en-US/docs/Web/API/History_API) API for routing.
- Unit tests for every feature are created.Do you like Prouter? [please give it a 🌟](https://github.com/rogerpadilla/prouter)
## Installation
```bash
# With NPM
npm install prouter --save# Or with Yarn
yarn prouter --save# Or just include it using a 'script' tag in your HTML file
```
## Examples
### basic
```js
// 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') =
`Some static content for the About page.
`;
// 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
```js
// 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 handlersconst 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
```js
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
```js
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
```js
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.](https://github.com/rogerpadilla/prouter/blob/master/src/browser-router.spec.ts)