- TypeScript 100%
|
|
||
|---|---|---|
| .forgejo/workflows | ||
| lib | ||
| tests | ||
| .gitignore | ||
| .oxfmtrc.json | ||
| .oxlintrc.json | ||
| bun.lock | ||
| jsr.json | ||
| LICENSE.md | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
| vite.config.ts | ||
Routier
Fluent, function-based route definitions for Vue Router. Inspired by Laravel's routing.
Yes, yes - file-based routing is all the hype. This package is for those of us who don't want that, but still want a better DX than raw Vue Router config.
Installation
Routier requires Vue Router 4+.
npm install @rockett/routier vue-router
# or
pnpm add @rockett/routier vue-router
# or
bun add @rockett/routier vue-router
Quick Start
import { createRouteFactory, Guard } from '@rockett/routier'
import { createRouter, createWebHistory } from 'vue-router'
import Home from './views/Home.vue'
import Blog from './views/Blog.vue'
import BlogPost from './views/BlogPost.vue'
import Dashboard from './views/Dashboard.vue'
class AuthGuard extends Guard {
handle(resolve, reject) {
if (isAuthenticated()) resolve()
else reject({ name: 'home' })
}
}
const { route, redirect, fallback, group, compileRoutes } = createRouteFactory({
guards: { auth: AuthGuard },
})
route('/', Home).name('home')
group('blog')
.name('blog')
.routes(() => {
route('/', Blog).name('index')
route('{slug}', BlogPost).name('post')
})
group('dashboard')
.name('dashboard')
.guard('auth')
.routes(() => {
route('/', Dashboard).name('home')
})
redirect('/old-blog', '/blog')
fallback(NotFound).name('not-found')
const router = createRouter({
history: createWebHistory(),
routes: compileRoutes(),
})
API
createRouteFactory(opts?)
Creates a route factory. Returns { route, redirect, group, compileRoutes, dump }.
const { route, redirect, group, compileRoutes } = createRouteFactory({
separateNamesUsing: '.', // name separator: '.' | '-' | '_' | ':' | '>'
resolveComponentsUsing: (name) => import(`./views/${name}.vue`),
guards: { auth: AuthGuard },
})
The resolveComponentsUsing option lets you pass strings instead of imported components:
route('dashboard', 'Dashboard') // resolved via the callback above
Routes
route(path, component, additionalComponents?)
Routes are the primary building block. The fluent API lets you chain configuration:
route('users/{id}', UserProfile)
.name('users.profile')
.alias('/people/{id}')
.alias('/u/{id}')
.meta('requiresAuth', true)
.props(true)
.guard('auth')
.sensitive()
.strict()
Props support three modes matching Vue Router's options:
route('/', Home).props(true) // boolean mode
route('/', Home).props({ defaultTab: 'overview' }) // object mode
route('/', Home).props((to) => ({ q: to.query.q })) // function mode
route('/', Home).prop('key', 'value') // single key-value
Children nest routes via a callback:
route('account', Account)
.name('account')
.children(() => {
route('/', AccountOverview).name('overview')
route('settings', AccountSettings).name('settings')
})
Aliases can be chained to register multiple alternative paths for a single route:
route('/users', Users).alias('/people').alias('/utilisateurs')
A single alias compiles to a string; multiple aliases compile to an array, matching Vue Router's format.
Matching options
sensitive makes path matching case-sensitive (by default /Users and /users match the same route). strict enforces trailing-slash matching (by default /users/ and /users are equivalent).
Both can be set at three levels:
// factory-level: applies to all routes
const { route, group } = createRouteFactory({ sensitive: true, strict: true })
// group-level: applies to all routes in the group
group('api').sensitive().strict().routes(() => { ... })
// route-level
route('/users', Users).sensitive().strict()
Path parameters
Routier supports Vue Router's :param syntax directly, and also provides a curly-brace syntax with optional type hints:
| Syntax | Compiles to | Description |
|---|---|---|
:id |
:id |
Standard Vue Router param |
{id} |
:id |
Curly-brace shorthand |
{id}(number) |
:id(\d+) |
Numeric constraint |
{name}(string) |
:name(\w+) |
Word-character constraint |
{all} |
(.*) |
Wildcard / catch-all |
{a}{b} |
:a/:b |
Consecutive params expand to segments |
Groups
Groups let you share a path prefix, name prefix, and guards across a set of routes - a concept borrowed from Laravel that Vue Router doesn't have natively. Any combination of prefix, name, and guards is optional.
group(prefix?)
.name(name)
.guard(...names)
.routes(() => { ... })
Prefix only - shared path segment:
group('api/v2').routes(() => {
route('users', Users) // /api/v2/users
route('posts', Posts) // /api/v2/posts
})
Name only - shared name prefix without affecting paths:
group()
.name('admin')
.routes(() => {
route('dashboard', Dashboard).name('home') // name: admin.home
})
Guards - protect all routes in the group:
group('admin')
.name('admin')
.guard('auth')
.routes(() => {
route('/', AdminDashboard).name('dashboard')
route('users', AdminUsers).name('users')
})
Groups nest freely, including inside children():
group('app')
.name('app')
.routes(() => {
route('dashboard', Dashboard)
.name('dashboard')
.children(() => {
group('widgets')
.name('widgets')
.routes(() => {
route('chart', Chart).name('chart') // name: app.dashboard.widgets.chart
route('table', Table).name('table') // path: widgets/table
})
})
})
Redirects
redirect(path, target).name(name)
Redirect routes compile to Vue Router's redirect record (no component). The target can be a path string, a route location object, or a function:
redirect('/old', '/new')
redirect('/old', { name: 'new-route' })
redirect('/old/{slug}', (to) => `/new/${to.params.slug}`)
Redirects work inside groups and as children, inheriting prefixes and names:
group('legacy').routes(() => {
redirect('old-page', '/new-page') // /legacy/old-page -> /new-page
})
Fallback
fallback(component).name(name)
Sugar for a catch-all route (/:pathMatch(.*)*). At the top level, it's always compiled last regardless of declaration order. Supports .name() and .meta():
fallback(NotFound).name('not-found').meta('status', 404)
Fallbacks also work inside groups and children for scoped catch-alls:
route('/app', App).children(() => {
route('dashboard', Dashboard)
fallback(SectionNotFound) // catches unmatched /app/* paths
})
Guards
Guards are class-based navigation guards using a promise-style resolve/reject pattern. Define a guard by extending the Guard class:
import { Guard } from '@rockett/routier'
class AuthGuard extends Guard {
handle(resolve, reject, { from, to }) {
if (isAuthenticated()) resolve()
else reject({ name: 'login' })
}
}
Register guards in the factory, then reference them by name:
const { route, group } = createRouteFactory({
guards: { auth: AuthGuard, admin: AdminGuard },
})
route('/dashboard', Dashboard).guard('auth')
group('admin').guard('auth', 'admin').routes(() => { ... })
The reject() value is passed to Vue Router's next() - typically a route location to redirect to. If a rejection would redirect to the same route being navigated to, Routier throws a GuardError to prevent infinite loops.
Set loggable = true on a guard to log resolve/reject events to the console during development.
Full Example
route('blog', Blog)
.name('blog')
.children(() => {
route('/', BlogPosts).name('posts')
route('{post}', BlogPost)
.name('single-post')
.children(() => {
route('/', BlogPostView).name('view')
route('comments', BlogPostComments).name('comments')
group('admin')
.guard('admin')
.name('admin')
.routes(() => {
route('edit', BlogPostEdit).name('edit')
route('stats', BlogPostStats).name('stats')
})
})
})
This produces:
| Path | Name | Guard |
|---|---|---|
| /blog | blog.posts | |
| /blog/:post | blog.single-post.view | |
| /blog/:post/comments | blog.single-post.comments | |
| /blog/:post/admin/edit | blog.single-post.admin.edit | admin |
| /blog/:post/admin/stats | blog.single-post.admin.stats | admin |
License
Licensed under ISC, Routier is an open-source project, and is free to use.