@wbe/low-router
v0.9.0
Published
<h1 align="center" style="text-align:center">low-router πβ</h1> <p align="center"> <img alt="npm" src="https://img.shields.io/npm/v/@wbe/low-router"> <img alt="npm bundle size" src="https://img.shields.io/bundlephobia/minzip/%40wbe%2Flow-router"> <img al
Downloads
70
Maintainers
Readme
LowRouter
is a lightweight (~=1.8Kb), low-level router implementation designed for use in nodejs, javascript or typescript applications. By default, LowRouter
has no link with the browser history, but this repository provide a createBrowserHistory
util ready to use. It also includes a createMatcher
function to convert a route path to a regular expression, but still open to use a custom one.
Table of Contents
- Playground
- Installation
- Usage
- Handle history
- Matcher
- Custom matcher
- debug
- API
- workflow
- Acknowledgement
- Credits
Playground
The examples of this repo are available on codesandbox:
Installation
npm i @wbe/low-router
Usage
Instance
import { LowRouter } from "@wbe/low-router"
const routes = [
{
path: "/",
name: "home",
action: () => "Hello home!",
},
{
path: "/admin",
name: "admin",
action: () => "Hello admin!",
children: [
{
path: "/config",
name: "config",
action: (context) => `Hello ${context.route.name}!`,
},
{
path: "/user/:id",
name: "user",
action: (context) => `Hello user! with id ${context.params.id}`,
},
],
},
]
const router = new LowRouter(routes)
resolve
The resolve
method allows you to match a given pathname or route object to a defined route and execute its associated action. It returns a Promise that resolves with the action result and route context.
router.resolve("/").then(({ response, context }) => {
// response: "Hello home!"
})
Or, with an object param:
router.resolve({ name: "user", params: { id: 123 } }).then(({ response, context }) => {
// response: "Hello user! with id 123"
})
resolveSync
The resolveSync
method is the same than resolve, but synchronously. It returns the action result and route context directly.
const { response, context } = router.resolveSync("/admin/config")
// response: "Hello home!"
Or, with an object param:
const { response, context } = router.resolveSync({ name: "user", params: { id: 123 } })
// response: "Hello user! with id 123"
createUrl
The createUrl
method generates a URL based on a route name and optional parameters.
router.createUrl({ name: "config" })
// "/admin/config"
dispose
The dispose
method is used to clean up the router instance.
router.dispose()
Handle history
Internal createBrowserHistory
provide a way to interact with the browser's history and listen to changes in the URL. You can integrate this functionality with the LowRouter
class to enable client-side routing with browser history support.
import { LowRouter, createBrowserHistory } from "@wbe/low-router"
const router = new LowRouter(routes, options)
const history = createBrowserHistory()
const unlisten = history.listen(async ({ location, action }) => {
const response = await router.resolve(location.pathname)
// Do something with the response...
})
// Push to the browser history will trigger the router resolve method
history.push("/foo")
history.push(router.createUrl({ name: "bar", params: { id: 123 } }))
// Stop listening to history changes
unlisten()
On the same way, you can use every history lib you want to handle history changes, and resolve the new pathname with the router, like remix-run/history.
Matcher
The matcher
is the function used to convert a route path to a regular expression. By default, LowRouter
use an internal matcher function. this matcher is called when the resolve method is called. You shouldn't have to use this function directly, but it's interesting to understand how it works, specially if you need to use a custom one.
import { createMatcher } from "@wbe/low-router"
const matcher = createMatcher()
const [isMatch, routeParams, queryParams, hash] = matcher(
"/user/1?lang=fr&cat=foo#section-2",
"/user/:id"
)
// isMatch: true
// routeParams: { id: "1" }
// queryParams: { lang: "fr", cat: "foo" }
// hash: "section-2"
This returns values are returned by RouteContext
when the route match. For more information about the matcher full matcher API, read the createMatcher unit tests.
Custom matcher
If the internal matcher doesn't respond as needed, it's possible to use a custom matcher function: like the original path-to-regexp package.
import { LowRouter, createMatcher } from "@wbe/low-router"
import { pathToRegexp } from "path-to-regexp"
const customPathToRegexpFn = (path: string): { keys: Record<string, string>[]; regexp: RegExp } => {
let keys = []
const regexp = pathToRegexp(path, keys)
return { keys, regexp }
}
const customMatcher = createMatcher(customPathToRegexpFn)
// ex: customMatcher("/about/:id", "/about/1")
// return: [true, { id: "1" }, {}, null]
// then, pass this customMatcher to the router options
// Now, the router will use this custom matcher with path-to-regexp to match routes
const router = new LowRouter(routes, { matcher: customMatcher })
This flexible custom matcher pattern as been created by molefrog on wouter π
debug
@wbe/debug tool is used as dependency in this project. To enable debug logs, you can use the following commands:
Browser debug:
localStorage.debug = "low-router:*"
Node debug:
DEBUG=low-router:*
API
LowRouter
// LowRouter(routes: Route[], options?: Options)
const router = new LowRouter(routes, options)
// Resolve a pathname or a route object
// resolve(pathnameOrObject: string | { name: string; params?: RouteParams })
router.resolve(path)
router.resolve({ name: "", params: {} })
// Resolve synchronously
// resolveSync(pathnameOrObject: string | { name: string; params?: RouteParams })
router.resolveSync(path)
router.resolveSync({ name: "", params: {} })
// Create a URL based on a route name and optional parameters
// createUrl({ name: string; params?: RouteParams }): string
router.createUrl({ name: "", params: {} })
// Clean up the router instance
// dispose(): void
router.dispose()
Options
const options: Options = {
// The base URL path for all routes.
// default: `/`.
base: "/",
// called when the router is initialized
// onInit: () => void
onInit: () => {},
// called when no matching route is found during resolution
// onError: () => void
onError: (context, error) => {},
// called after a route's action has been executed successfully
// onResolve: ({response: ActionResponse<A>, context: RouteContext<A, P>}) => void
onResolve: ({ response, context }) => {},
// called when the router is disposed of using the `dispose` method
// onDispose: () => void
onDispose: () => {},
// Custom function to convert a route path to a regular expression.
// Default: the internal `createMatcher()` fn
// matcher: Matcher
matcher: createMatcher(),
// give an id to the router instance, useful when you have multiple router instances
// and you want to identify them from debug logs
// id?: number | string
id: 1,
}
RouteContext
RouteContext
is the 1st level route object, passed to the route action function.
It contains all the information about the current context, plus the route object itself.
interface RouteContext {
// The current pathname
pathname: string
// The current path params
// (ex: /:foo/:bar)
params: RouteParams
// The current query params
// (ex: ?foo=bar&baz=qux)
query: QueryParams
// The current hash
// (ex: #foo)
hash: Hash
// the route base URL
base: string
// β the route object associated to this context
route: Route
// parent route context, useful when the current is a child route
parent: RouteContext | null
}
Route
Route
is the route object definition passed to the LowRouter
constructor, define by the developer.
interface Route {
// The route path
// (ex: /foo/:bar)
path: string
// The route name, useful to get a route by name
name?: string
// The route action function is the main function of the route
// this function is called when the route is resolved
action?: (context: RouteContext) => Promise<any> | any
// The route children
children?: Route[]
// The route props can be any data you want to pass/associate to the route
props?: Record<string, any>
}
createBrowserHistory
createBrowserHistory()
will return an object:
export interface HistoryAPI {
// associate a callback to the history change event
// return a function to stop listening
listen: (callback: ({ location, action }) => void) => () => void
// Push a new patname to the history
push: (pathname: string) => void
}
Workflow
# clone repo
git clone {repo}
# install all dependencies
pnpm i
# run build watch
pnpm run build:watch
# run test watch
pnpm run test:watch
# run examples dev server
pnpm run dev
Acknowledgement
This project is inspired by the following projects: