Making filesystem routing universal
This library is a work in progress and in active development.
- generic route parsing function with options to cover major filesystem routing patterns
- Nuxt
- unplugin-vue-router (does not include dot-syntax nesting support)
- export capability for framework routers
- RegExp patterns
-
vue-router
routes - rou3/Nitro routes
- SolidStart
- SvelteKit routes
- support scanning FS (with optional watch mode)
- and more
Install package:
# npm
npm install unrouting
# pnpm
pnpm install unrouting
import { parsePath } from 'unrouting'
// Parse file paths into segments with mode detection
const [result] = parsePath(['users/[id]/profile.vue'])
console.log(result.segments)
// [
// [{ type: 'static', value: 'users' }],
// [{ type: 'dynamic', value: 'id' }],
// [{ type: 'static', value: 'profile' }]
// ]
console.log(result.meta) // undefined (no metadata detected)
import { parsePath } from 'unrouting'
// Configure mode detection for .server, .client suffixes
const [result] = parsePath(['app.server.vue'], {
modes: ['server', 'client']
})
console.log(result.meta?.modes) // ['server']
console.log(result.segments) // [[{ type: 'static', value: 'app' }]]
// Multiple modes
const [result2] = parsePath(['api.server.edge.js'], {
modes: ['server', 'client', 'edge']
})
console.log(result2.meta?.modes) // ['server', 'edge']
console.log(result2.segments) // [[{ type: 'static', value: 'api' }]]
import { parsePath } from 'unrouting'
// Named views with @ suffix (for Vue Router named views)
const [result] = parsePath(['[email protected]'])
console.log(result.meta?.name) // 'sidebar'
console.log(result.segments) // [[{ type: 'static', value: 'dashboard' }]]
// Named views with modes
const [result2] = parsePath(['[email protected]'], {
modes: ['client', 'server']
})
console.log(result2.meta) // { name: 'main', modes: ['client'] }
// Nested named views
const [result3] = parsePath(['users/[id]@profile.vue'])
console.log(result3.meta?.name) // 'profile'
console.log(result3.segments)
// [
// [{ type: 'static', value: 'users' }],
// [{ type: 'dynamic', value: 'id' }]
// ]
import { parsePath, toRegExp, toRou3, toVueRouter4 } from 'unrouting'
const [result] = parsePath(['users/[id]/posts/[slug].vue'])
// Vue Router 4 format
const [vueRoute] = toVueRouter4([result])
console.log(vueRoute.path) // '/users/:id()/posts/:slug()'
// Rou3/Nitro format
const [nitroRoute] = toRou3([result])
console.log(nitroRoute) // '/users/:id/posts/:slug'
// RegExp pattern
const [regexpRoute] = toRegExp([result])
console.log(regexpRoute.pattern) // /^\/users\/([^\/]+)\/posts\/([^\/]+)\/?$/
console.log(regexpRoute.keys) // ['id', 'slug']
// Or pass file paths directly to converters
const [vueRoute2] = toVueRouter4(['users/[id]/posts/[slug].vue'])
const [nitroRoute2] = toRou3(['users/[id]/posts/[slug].vue'])
const [regexpRoute2] = toRegExp(['users/[id]/posts/[slug].vue'])
import { parsePath, toRegExp, toVueRouter4 } from 'unrouting'
// Repeatable parameters ([slug]+.vue -> one or more segments)
const [repeatable] = parsePath(['posts/[slug]+.vue'])
const [vueRoute1] = toVueRouter4([repeatable])
console.log(vueRoute1.path) // '/posts/:slug+'
// Optional repeatable parameters ([[slug]]+.vue -> zero or more segments)
const [optionalRepeatable] = parsePath(['articles/[[slug]]+.vue'])
const [vueRoute2] = toVueRouter4([optionalRepeatable])
console.log(vueRoute2.path) // '/articles/:slug*'
// Group segments (ignored in final path, useful for organization)
const [grouped] = parsePath(['(admin)/(dashboard)/users/[id].vue'])
const [vueRoute3] = toVueRouter4([grouped])
console.log(vueRoute3.path) // '/users/:id()'
// Groups are parsed but excluded from path generation
// Catchall routes ([...slug].vue -> captures remaining path)
const [catchall] = parsePath(['docs/[...slug].vue'])
const [vueRoute4] = toVueRouter4([catchall])
console.log(vueRoute4.path) // '/docs/:slug(.*)*'
// Optional parameters ([[param]].vue -> parameter is optional)
const [optional] = parsePath(['products/[[category]]/[[id]].vue'])
const [vueRoute5] = toVueRouter4([optional])
console.log(vueRoute5.path) // '/products/:category?/:id?'
// Complex mixed patterns
const [complex] = parsePath(['shop/[category]/product-[id]-[[variant]].vue'])
const [vueRoute6] = toVueRouter4([complex])
console.log(vueRoute6.path)
// '/shop/:category()/product-:id()-:variant?'
// Proper regex matching with anchoring (fixes partial match issues)
const [pattern] = toRegExp(['[slug].vue'])
console.log(pattern.pattern) // /^\/(?<slug>[^/]+)\/?$/
console.log('/file'.match(pattern.pattern)) // β
matches
console.log('/test/thing'.match(pattern.pattern)) // β null (properly rejected)
Parse file paths into route segments with mode detection.
Parameters:
filePaths
(string[]): Array of file paths to parseoptions
(object, optional):extensions
(string[]): File extensions to strip (default: all extensions)modes
(string[]): Mode suffixes to detect (e.g.,['server', 'client']
)warn
(function): Warning callback for invalid characters
Returns: ParsedPath[]
interface ParsedPath {
segments: ParsedPathSegment[]
meta?: {
modes?: string[] // Detected mode suffixes (e.g., ['client', 'server'])
name?: string // Named view from @name suffix
}
}
Convert parsed segments or file paths to Vue Router 4 format.
Parameters:
filePaths
(string[] | ParsedPath[]): Array of file paths or parsed path objects
Returns: Array<{ path: string }>
Convert parsed segments or file paths to Rou3/Nitro format.
Parameters:
filePaths
(string[] | ParsedPath[]): Array of file paths or parsed path objects
Returns: string[]
Convert parsed segments or file paths to RegExp patterns.
Parameters:
filePaths
(string[] | ParsedPath[]): Array of file paths or parsed path objects
Returns: Array<{ pattern: RegExp, keys: string[] }>
- Clone this repository
- Enable Corepack using
corepack enable
(usenpm i -g corepack
for Node.js < 16.10) - Install dependencies using
pnpm install
- Run interactive tests using
pnpm dev
Made with β€οΈ
Published under MIT License.