low-router 🚌
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
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 }) => {
})
Or, with an object param:
router.resolve({ name: "user", params: { id: 123 } }).then(({ response, context }) => {
})
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")
Or, with an object param:
const { response, context } = router.resolveSync({ name: "user", params: { id: 123 } })
createUrl
The createUrl
method generates a URL based on a route name and optional parameters.
router.createUrl({ name: "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)
})
history.push("/foo")
history.push(router.createUrl({ name: "bar", params: { id: 123 } }))
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"
)
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)
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:
API
LowRouter
const router = new LowRouter(routes, options)
router.resolve(path)
router.resolve({ name: "", params: {} })
router.resolveSync(path)
router.resolveSync({ name: "", params: {} })
router.createUrl({ name: "", params: {} })
router.dispose()
Options
const options: Options = {
base: "/",
onInit: () => {},
onError: (context, error) => {},
onResolve: ({ response, context }) => {},
onDispose: () => {},
matcher: createMatcher(),
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 {
pathname: string
params: RouteParams
query: QueryParams
hash: Hash
base: string
route: Route
parent: RouteContext | null
}
Route
Route
is the route object definition passed to the LowRouter
constructor, define by the developer.
interface Route {
path: string
name?: string
action?: (context: RouteContext) => Promise<any> | any
children?: Route[]
props?: Record<string, any>
}
createBrowserHistory
createBrowserHistory()
will return an object:
export interface HistoryAPI {
listen: (callback: ({ location, action }) => void) => () => void
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:
Credits
© Willy Brauner