Skip to content

Commit 03503ff

Browse files
committed
update router doc
1 parent cbd7a12 commit 03503ff

File tree

1 file changed

+284
-0
lines changed

1 file changed

+284
-0
lines changed

docs/documentation/capabilities/router.md

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -717,3 +717,287 @@ The `parsePathname` function allows you to provide a location pathname pattern a
717717
parsePathname('/projects/:projectId/:projectId/summary')
718718
// /projects/my-project/283478brxedy7d87w84yr8/summary
719719
```
720+
721+
## Advanced Features
722+
723+
### Route Guards
724+
725+
Route guards allow you to protect routes with authentication checks, authorization, or any custom logic. Guards can be global (run for all routes) or route-specific.
726+
727+
#### registerRouteGuard
728+
729+
Register a guard for a specific route. The guard function receives the pathname, search params, and page data, and can return:
730+
731+
- `true` - Allow navigation
732+
- `false` - Block navigation (stay on current page)
733+
- `string` - Redirect to the specified pathname
734+
- `Promise<boolean | string>` - Async guards are fully supported
735+
736+
```javascript
737+
import { registerRouteGuard } from '@beforesemicolon/router'
738+
739+
// Block access to admin routes
740+
registerRouteGuard('/admin', (pathname, query, data) => {
741+
if (!userIsAdmin()) {
742+
return '/unauthorized' // Redirect
743+
}
744+
return true // Allow
745+
})
746+
747+
// Async guard example
748+
registerRouteGuard('/protected', async (pathname, query, data) => {
749+
const isAuth = await checkAuthentication()
750+
return isAuth
751+
})
752+
```
753+
754+
#### registerGlobalGuard
755+
756+
Register a guard that runs for all routes. Global guards execute before route-specific guards.
757+
758+
```javascript
759+
import { registerGlobalGuard } from '@beforesemicolon/router'
760+
761+
// Check authentication for all routes
762+
registerGlobalGuard((pathname, query, data) => {
763+
const publicRoutes = ['/login', '/register']
764+
765+
if (!publicRoutes.includes(pathname) && !isAuthenticated()) {
766+
return '/login'
767+
}
768+
769+
return true
770+
})
771+
```
772+
773+
**Guard execution order:**
774+
775+
1. Global guards (in registration order)
776+
2. Route-specific guards (in registration order)
777+
3. First guard that blocks or redirects stops execution
778+
779+
### Hash Routing
780+
781+
The router supports both history API and hash-based routing. Hash routing is perfect for static hosting (GitHub Pages, Netlify) and requires no server configuration.
782+
783+
#### setRoutingMode
784+
785+
Set the routing mode to either `'history'` (default) or `'hash'`.
786+
787+
```javascript
788+
import { setRoutingMode } from '@beforesemicolon/router'
789+
790+
// Enable hash routing
791+
setRoutingMode('hash')
792+
793+
// Routes will use hash format: #/path
794+
// Links will automatically render as: <a href="#/path">...</a>
795+
796+
// Switch back to history mode
797+
setRoutingMode('history')
798+
```
799+
800+
#### getRoutingMode
801+
802+
Get the current routing mode.
803+
804+
```javascript
805+
import { getRoutingMode } from '@beforesemicolon/router'
806+
807+
const mode = getRoutingMode() // 'history' or 'hash'
808+
```
809+
810+
**With hash routing:**
811+
812+
- All navigation uses hash format: `#/path?query=value`
813+
- Works without server-side routing configuration
814+
- Perfect for static hosting
815+
- Backward compatible with older browsers
816+
817+
### Module Registry
818+
819+
Register route modules for build-time optimization with bundlers like Vite or Webpack. This allows bundlers to analyze dependencies while using the convenient `src` attribute.
820+
821+
#### registerRouteModules
822+
823+
Register a map of module paths to their loader functions.
824+
825+
```javascript
826+
import { registerRouteModules } from '@beforesemicolon/router'
827+
828+
// With Vite - use import.meta.glob
829+
const modules = import.meta.glob('./pages/**/*.{ts,js}', { eager: false })
830+
registerRouteModules(modules)
831+
832+
// With Webpack - use require.context
833+
const context = require.context('./pages', true, /\.(ts|js)$/)
834+
const moduleMap = {}
835+
context.keys().forEach((key) => {
836+
moduleMap[key] = () => context(key)
837+
})
838+
registerRouteModules(moduleMap)
839+
840+
// Manual registration
841+
registerRouteModules({
842+
'./pages/home.ts': () => import('./pages/home'),
843+
'./pages/about.ts': () => import('./pages/about'),
844+
})
845+
```
846+
847+
Then use `src` attribute as normal:
848+
849+
```html
850+
<page-route path="/home" src="./pages/home.ts"></page-route>
851+
```
852+
853+
The router will:
854+
855+
1. Check if the module is registered
856+
2. Use the registered loader if available
857+
3. Fall back to dynamic `import()` if not registered
858+
4. Cache the loaded module for reuse
859+
860+
#### getRouteModule
861+
862+
Get a registered module loader.
863+
864+
```javascript
865+
import { getRouteModule } from '@beforesemicolon/router'
866+
867+
const loader = getRouteModule('./pages/home.ts')
868+
if (loader) {
869+
const module = await loader()
870+
// Use module.default
871+
}
872+
```
873+
874+
### Route Metadata
875+
876+
Attach metadata to routes for titles, breadcrumbs, permissions, or any custom data.
877+
878+
#### registerRoute with metadata
879+
880+
Register a route with metadata using the `meta` option:
881+
882+
```javascript
883+
import { registerRoute } from '@beforesemicolon/router'
884+
885+
registerRoute('/dashboard', {
886+
exact: true,
887+
meta: {
888+
title: 'Dashboard',
889+
breadcrumb: 'Home > Dashboard',
890+
requiresAuth: true,
891+
layout: 'admin',
892+
permissions: ['dashboard:read'],
893+
},
894+
})
895+
```
896+
897+
#### getRouteMeta
898+
899+
Retrieve metadata for a registered route.
900+
901+
```javascript
902+
import { getRouteMeta } from '@beforesemicolon/router'
903+
904+
const meta = getRouteMeta('/dashboard')
905+
console.log(meta.title) // 'Dashboard'
906+
console.log(meta.requiresAuth) // true
907+
```
908+
909+
**Common use cases:**
910+
911+
Update document title based on route:
912+
913+
```javascript
914+
import { onPageChange, getRouteMeta } from '@beforesemicolon/router'
915+
916+
onPageChange((pathname) => {
917+
const meta = getRouteMeta(pathname)
918+
if (meta?.title) {
919+
document.title = `${meta.title} | My App`
920+
}
921+
})
922+
```
923+
924+
Check permissions in guards:
925+
926+
```javascript
927+
import { registerGlobalGuard, getRouteMeta } from '@beforesemicolon/router'
928+
929+
registerGlobalGuard((pathname) => {
930+
const meta = getRouteMeta(pathname)
931+
932+
if (meta?.requiresAuth && !isAuthenticated()) {
933+
return '/login'
934+
}
935+
936+
if (meta?.permissions && !hasPermissions(meta.permissions)) {
937+
return '/unauthorized'
938+
}
939+
940+
return true
941+
})
942+
```
943+
944+
Build breadcrumbs:
945+
946+
```javascript
947+
function getBreadcrumbs(pathname) {
948+
const meta = getRouteMeta(pathname)
949+
return meta?.breadcrumb?.split(' > ') || []
950+
}
951+
```
952+
953+
### Component Prop
954+
955+
Use the `component` attribute with explicit imports for build-time optimization and type safety.
956+
957+
Instead of using the `src` attribute with a file path string, you can pass component references directly:
958+
959+
```typescript
960+
import { html } from '@beforesemicolon/web-component'
961+
import { HomePage } from './pages/HomePage'
962+
import { AboutPage } from './pages/AboutPage'
963+
964+
html`
965+
<page-route path="/" component="${HomePage}"></page-route>
966+
<page-route path="/about" component="${AboutPage}"></page-route>
967+
`
968+
```
969+
970+
**Benefits:**
971+
972+
- TypeScript type checking at compile time
973+
- Better IDE autocomplete and refactoring
974+
- Bundler can analyze dependencies
975+
- Code splitting and tree shaking
976+
- Faster Hot Module Replacement (HMR)
977+
978+
**Component format:**
979+
980+
Components can be:
981+
982+
```typescript
983+
// 1. HTML string
984+
export default `<h1>Welcome</h1>`
985+
986+
// 2. Markup template
987+
import { html } from '@beforesemicolon/web-component'
988+
export default html`<h1>Welcome</h1>`
989+
990+
// 3. DOM Node
991+
const div = document.createElement('div')
992+
div.textContent = 'Welcome'
993+
export default div
994+
995+
// 4. Function receiving route context
996+
export default (pageData, pathParams, searchParams) => {
997+
return html`
998+
<h1>Welcome ${pageData.user?.name}</h1>
999+
<p>User ID: ${pathParams.userId}</p>
1000+
<p>Tab: ${searchParams.tab}</p>
1001+
`
1002+
}
1003+
```

0 commit comments

Comments
 (0)