Skip to content

unjs/unrouting

Repository files navigation

πŸ“ unrouting

npm version npm downloads bundle Codecov License JSDocs

Making filesystem routing universal

🚧 In development

This library is a work in progress and in active development.

  • generic route parsing function with options to cover major filesystem routing patterns
  • export capability for framework routers
  • support scanning FS (with optional watch mode)
  • and more

Usage

Install package:

# npm
npm install unrouting

# pnpm
pnpm install unrouting

Basic Parsing

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)

Mode Detection

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' }]]

Named Views

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' }]
// ]

Convert to Router Formats

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'])

Advanced Examples

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)

API

parsePath(filePaths, options?)

Parse file paths into route segments with mode detection.

Parameters:

  • filePaths (string[]): Array of file paths to parse
  • options (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
  }
}

toVueRouter4(filePaths)

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 }>

toRou3(filePaths)

Convert parsed segments or file paths to Rou3/Nitro format.

Parameters:

  • filePaths (string[] | ParsedPath[]): Array of file paths or parsed path objects

Returns: string[]

toRegExp(filePaths)

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[] }>

πŸ’» Development

  • Clone this repository
  • Enable Corepack using corepack enable (use npm i -g corepack for Node.js < 16.10)
  • Install dependencies using pnpm install
  • Run interactive tests using pnpm dev

License

Made with ❀️

Published under MIT License.

About

Making filesystem routing universal

Resources

License

Code of conduct

Stars

Watchers

Forks

Sponsor this project

 

Contributors 3

  •  
  •  
  •