1.3.6 • Published 3 months ago

rutter v1.3.6

Weekly downloads
-
License
MIT
Repository
github
Last release
3 months ago

About

Rutter is a framework-agnostic, lightweight router. Built with URLPattern & History API. Internal reactivity is powered by Signal.

This library doesn't ship polyfill for URLPattern. You may consider installing urlpattern-polyfill.

Usage

VanillaJS

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

// 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<PropsWithChildren> = ({ children }) => {
  const value = useRouterValues()

  return <context.Provider value={value}>{children}</context.Provider>
}

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

  return routeState
}
// app.(tsx|jsx)

import { FC } from 'react'

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

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

  return (
    <>
      <nav>
        <button onClick={() => redirect('index')}>Index</button>

        <button onClick={() => redirect('blog')}>Blog</button>

        <a href="/invalid-url">
          <button>404</button>
        </a>
      </nav>

      <fieldset>
        <legend>Body:</legend>

        <div>
          {is404 ? (
            <h1>404 Page</h1>
          ) : (
            <>
              {on('index') && <h1>Index Page</h1>}

              {on('about') && <h1>About Page</h1>}

              {on('blog') && (
                <>
                  <h1>Blog Page</h1>

                  <button
                    onClick={() =>
                      redirect('blogDetail', {
                        params: {
                          id: 123
                        }
                      })
                    }
                  >
                    Blog Detail
                  </button>
                </>
              )}

              {on('blogDetail') && <h1>Blog Detail Page</h1>}
            </>
          )}
        </div>
      </fieldset>

      <fieldset>
        <legend>Current route detail:</legend>

        <code>
          <pre>{JSON.stringify(restStates, null, 2)}</pre>
        </code>
      </fieldset>
    </>
  )
}

const App: FC = () => (
  <RouterProvider>
    <Routing />
  </RouterProvider>
)

Vue bindings: via shallowRef/computed

// 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
})
<script setup lang="ts">
// app.vue
import { redirect, route, matches, is404 } from './router'
</script>

<template>
  <nav>
    <button @click="() => redirect('index')">Index</button>

    <button @click="() => redirect('blog')">Blog</button>

    <a href="/invalid-url">
      <button>404</button>
    </a>
  </nav>

  <fieldset>
    <legend>Body:</legend>
    <div>
      <h1 v-if="is404">404 Page</h1>

      <template v-else>
        <h1 v-if="matches.index">Index Page</h1>

        <h1 v-if="matches.about">About Page</h1>

        <template v-if="matches.blog">
          <h1>Blog Page</h1>

          <button
            @click="() => redirect('blogDetail', { params: { id: 123 } })"
          >
            Blog Detail
          </button>
        </template>

        <h1 v-if="matches.blogDetail">Blog Detail Page</h1>
      </template>
    </div>
  </fieldset>

  <fieldset>
    <legend>Current route detail:</legend>

    <code>
      <pre>{{ route }}</pre>
    </code>
  </fieldset>
</template>

Svelte bindings: via readable/derived

// 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))
)
<script lang="ts">
  // app.svelte

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

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

<nav>
  <button on:click={() => redirect('index')}>Index</button>

  <button on:click={() => redirect('blog')}>Blog</button>

  <a href="/invalid-url">
    <button>404</button>
  </a>
</nav>

<fieldset>
  <legend>Body:</legend>

  <div>
    {#if is404}
      <h1>404 Page</h1>
    {:else}
      {#if $matches.index}
        <h1>Index Page</h1>
      {/if}

      {#if $matches.about}
        <h1>About Page</h1>
      {/if}

      {#if $matches.blog}
        <h1>Blog Page</h1>

        <button
          on:click={() => redirect('blogDetail', { params: { id: 123 } })}
        >
          Blog Detail
        </button>
      {/if}

      {#if $matches.blogDetail}
        <h1>Blog Detail Page</h1>
      {/if}
    {/if}
  </div>
</fieldset>

<fieldset>
  <legend>Current route detail:</legend>

  <code>
    <pre>{data}</pre>
  </code>
</fieldset>

Documentation

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

Development

pnpm i
pnpm dev
1.3.6

3 months ago

1.3.5

4 months ago

1.3.4

6 months ago

1.3.3

6 months ago

1.3.2

7 months ago

1.3.1

7 months ago

1.2.0

12 months ago

1.2.7

11 months ago

1.2.6

11 months ago

1.2.5

11 months ago

1.2.4

12 months ago

1.2.3

12 months ago

1.2.2

12 months ago

1.3.0

10 months ago

1.2.1

12 months ago

1.1.2

1 year ago

1.1.1

1 year ago

1.1.0

1 year ago

1.0.3

1 year ago

1.0.2

1 year ago

1.0.1

1 year ago

1.0.0

1 year ago