prouter v10.0.27
prouter
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.
Do you like Prouter? please give it a 🌟
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.
8 months ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago