Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/thihathit/rutter

Type-safe framework-agnostic Router, built with URLPattern & History API.
https://github.com/thihathit/rutter

framework-agnostic history-api react router solid svelte type-safe typescript urlpattern vue

Last synced: 3 months ago
JSON representation

Type-safe framework-agnostic Router, built with URLPattern & History API.

Awesome Lists containing this project

README

        

## About

**Rutter** is a framework-agnostic, lightweight router. Built with [URLPattern](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern) & [History](https://developer.mozilla.org/en-US/docs/Web/API/History_API) API. Internal reactivity is powered by [Signal](https://github.com/preactjs/signals).

> This library doesn't ship polyfill for `URLPattern`. You may consider installing [urlpattern-polyfill](https://www.npmjs.com/package/urlpattern-polyfill).

## Usage

- [ Vanilla JS](#-vanillajs)
- [ Vue](#-vue-bindings-via-shallowrefcomputed)
- [ React](#-react-bindings-via-usestatecontext)
- [ Svelte](#-svelte-bindings-via-readablederived)

### VanillaJS

```ts
import { CreateHistory } from 'rutter'

const router = new CreateHistory({
routes: {
index: {
pathname: ''
},
about: {
pathname: '/about'
},
blog: {
pathname: '/blog'
},
blogDetail: {
pathname: '/blog/:id'
}
}
})

router.on('index') // boolean
router.onOneOf(['index', 'about']) // boolean
```

### React bindings: via `useState`/`context`

```tsx
// router.(tsx|jsx)

import {
FC,
PropsWithChildren,
createContext,
useContext,
useEffect,
useState
} from 'react'

import { CreateHistory } from 'rutter'

export const {
redirect,
on,
summaryState,
routeState,
watchSummaryState,
watchRouteState
} = new CreateHistory({
routes: {
index: {
pathname: ''
},
about: {
pathname: '/about'
},
blog: {
pathname: '/blog'
},
blogDetail: {
pathname: '/blog/:id'
}
}
})

/**
* Although using with `context` is recommended for performance reason, you can directly use this hook if you don't want to store all the states in `context` tree.
*/
export const useRouterValues = () => {
const [routeStateValue, setRouteStateState] = useState(routeState)
const [summaryStateValue, setSummaryStateState] = useState(summaryState)

useEffect(() => watchRouteState(setRouteStateState), [])
useEffect(() => watchSummaryState(setSummaryStateState), [])

return {
routeState: routeStateValue,
summaryState: summaryStateValue
}
}

const context = createContext({
routeState,
summaryState
})

const useRouterContext = () => useContext(context)

export const RouterProvider: FC = ({ children }) => {
const value = useRouterValues()

return {children}
}

export const useRoute = () => {
const { routeState } = useRouterContext()

return routeState
}
```

```tsx
// app.(tsx|jsx)

import { FC } from 'react'

import { on, redirect, useRoute, RouterProvider } from './router'

const Routing: FC = () => {
const { is404, ...restStates } = useRoute()

return (
<>

redirect('index')}>Index

redirect('blog')}>Blog


404


Body:


{is404 ? (

404 Page


) : (
<>
{on('index') &&

Index Page

}

{on('about') &&

About Page

}

{on('blog') && (
<>

Blog Page


redirect('blogDetail', {
params: {
id: 123
}
})
}
>
Blog Detail

>
)}

{on('blogDetail') &&

Blog Detail Page

}
>
)}


Current route detail:


{JSON.stringify(restStates, null, 2)}



>
)
}

const App: FC = () => (



)
```

### Vue bindings: via `shallowRef`/`computed`

```ts
// router.(ts|js)

import { computed, shallowRef } from 'vue'
import { CreateHistory } from 'rutter'

import { mapValues } from 'lodash-es'

const router = new CreateHistory({
routes: {
index: {
pathname: ''
},
about: {
pathname: '/about'
},
blog: {
pathname: '/blog'
},
blogDetail: {
pathname: '/blog/:id'
}
}
})

const {
//
summaryState,
routeState,
watchSummaryState,
watchRouteState,
on
} = router

export const { redirect } = router

export const routerState = shallowRef(summaryState)
export const route = shallowRef(routeState)

export const is404 = computed(() => route.value.is404)

export const matches = computed(() => {
const { details } = routerState.value

type RouteNames = keyof typeof details

return mapValues(details, (_, name) => on(name as RouteNames))
})

watchSummaryState(state => {
routerState.value = state
})

watchRouteState(state => {
route.value = state
})
```

```vue

// app.vue
import { redirect, route, matches, is404 } from './router'


redirect('index')">Index

redirect('blog')">Blog


404


Body:


404 Page


Index Page

About Page


Blog Page

redirect('blogDetail', { params: { id: 123 } })"
>
Blog Detail

Blog Detail Page





Current route detail:


{{ route }}


```

### Svelte bindings: via `readable`/`derived`

```ts
// router.(ts|js)

import { readable, derived } from 'svelte/store'
import { CreateHistory } from 'rutter'

import { mapValues } from 'lodash-es'

const router = new CreateHistory({
routes: {
index: {
pathname: ''
},
about: {
pathname: '/about'
},
blog: {
pathname: '/blog'
},
blogDetail: {
pathname: '/blog/:id'
}
}
})

const { summaryState, routeState, watchSummaryState, watchRouteState } = router

export const { redirect, on, onOneOf } = router

export const route = readable(routeState, watchRouteState)
export const routerState = readable(summaryState, watchSummaryState)

export const matches = derived(routerState, ({ details }) =>
mapValues(details, (_, name) => on(name as keyof typeof details))
)
```

```svelte

// app.svelte

import { redirect, route, matches } from './router'

$: ({ is404, ...restState } = $route)
$: data = JSON.stringify(restState, null, 2)

redirect('index')}>Index

redirect('blog')}>Blog


404

Body:


{#if is404}

404 Page


{:else}
{#if $matches.index}

Index Page


{/if}

{#if $matches.about}

About Page


{/if}

{#if $matches.blog}

Blog Page

redirect('blogDetail', { params: { id: 123 } })}
>
Blog Detail

{/if}

{#if $matches.blogDetail}

Blog Detail Page


{/if}
{/if}

Current route detail:


{data}

```

## Documentation

Type API: https://paka.dev/npm/rutter/api

## Development

```bash
pnpm i
pnpm dev
```