@route-weaver/core
Version:
A typesafe navigation package for creating and managing routes.
342 lines (241 loc) • 12.6 kB
Markdown
# @route-weaver/core
[](https://badge.fury.io/js/%40route-weaver%2Fcore)
[](https://opensource.org/licenses/MIT)
The framework-agnostic core library for `route-weaver`. It provides the foundation for declaring routes, building navigation structures, and handling dynamic paths.
## Why @route-weaver/core?
In modern web development, managing URLs is often a messy and error-prone task. String-based solutions are fragile and can lead to broken links, 404 errors, and a frustrating developer experience.
`@route-weaver/core` solves these problems by providing a typesafe, framework-agnostic utility for defining and consuming routes. By leveraging the power of TypeScript, it ensures that your route paths and parameters are always correct, catching errors at compile time, not runtime.
This means you can refactor your routes with confidence, knowing that any mistakes will be caught instantly by your IDE. No more hunting for broken links after a deployment!
## Installation
```bash
npm install @route-weaver/core
```
## Expanded API Reference
### `createRouteWeaver(routes)`
Initializes the weaver with your application's routes.
* **`routes`**: An object where each key is a unique route ID and the value is a `NavItem` object (`{ label: string; path?: string; meta?: object }`).
* **Returns**: A `routeWeaver` instance with `build` and `buildPath` methods.
### `routeWeaver.build(groups, options)`
Builds a structured, self-contained navigation instance.
* **`groups`**: A `NavigationGroups` object that defines the structure of your navigation menus (e.g., `{ main: ['home', 'about'], footer: ['terms', 'privacy'] }`).
* **`options`** (optional): An object for advanced configuration.
* `translator`: A function for internationalization (i18n).
* `basePath`: A base path to prepend to all generated URLs.
* **Returns**: A `NavigationInstance` containing the resolved navigation structure, helper functions, and the original route declarations.
### `routeWeaver.buildPath(routeId, params)`
Constructs a typesafe URL for a given route.
* **Why and When to Use**: Use this method whenever you need to generate a URL for a route, especially one with dynamic parameters. It guarantees that the URL is correctly formatted and that all required parameters are provided.
* **`routeId`**: The ID of the route (e.g., `'profile'`). Must be a key from the original `routes` object.
* **`params`** (optional): An object containing the dynamic parameters for the route. TypeScript will enforce that you provide all required parameters.
* **Returns**: A fully-qualified URL string.
**Example**:
```typescript
const postUrl = routeWeaver.buildPath('post', { id: 'my-first-post' });
// => "/blog/my-first-post"
```
### `navInstance.getActiveRoute(pathname)`
Finds the active route based on the current URL pathname.
* **Why and When to Use**: Use this method to determine the currently active route, which is useful for highlighting active links, displaying route-specific information, or triggering side effects.
* **`pathname`**: The `pathname` from the current URL (e.g., `window.location.pathname`).
* **Returns**: An `ActiveRoute` object containing the matched route and any extracted URL parameters, or `undefined` if no match is found.
**Example**:
```typescript
const active = navInstance.getActiveRoute('/blog/my-first-post');
// active.route.id => 'post'
// active.params.id => 'my-first-post'
```
### `navInstance.getBreadcrumbs(pathname)`
Generates a breadcrumb trail for the active route.
* **Why and When to Use**: Use this to create breadcrumb navigation, providing users with a clear path from the root of your site to their current location.
* **`pathname`**: The `pathname` from the current URL.
* **Returns**: An array of `StructuredNavItem` objects representing the path from the root to the active route.
**Example**:
```typescript
const crumbs = navInstance.getBreadcrumbs('/settings/profile');
// => [ { id: 'settings', ... }, { id: 'profile', ... } ]
```
### `navInstance.filterNavigation(predicate)`
Creates a new, filtered navigation structure.
* **Why and When to Use**: This is a powerful tool for implementing role-based access control or feature flags. You can create different navigation menus for different users or conditions.
* **`predicate`**: A function that receives a `StructuredNavItem` and returns `true` if it should be included.
* **Returns**: A new `StructuredNavigation` object.
**Example**:
```typescript
const adminNav = navInstance.filterNavigation(item => item.meta?.requiresAuth);
```
### `navInstance.on(event, callback)`
Subscribes to events, such as when the active route changes.
* **Why and When to Use**: Use this to react to navigation events. For example, you could update analytics, fetch data, or change the document title whenever the route changes.
* **`event`**: The name of the event to subscribe to. Currently, only `'activeRouteChange'` is supported.
* **`callback`**: A function to be called when the event is triggered.
**Example**:
```typescript
navInstance.on('activeRouteChange', (activeRoute) => {
if (activeRoute) {
document.title = activeRoute.route.label;
}
});
```
## Advanced Usage
### Dynamic Navigation with `filterNavigation`
You can create dynamic, user-specific navigation by leveraging the `meta` property and `filterNavigation`. For example, you can show certain menu items only to authenticated users.
```typescript
const routeWeaver = createRouteWeaver({
home: { label: 'Home', path: '/' },
dashboard: { label: 'Dashboard', path: 'dashboard', meta: { requiresAuth: true } },
admin: { label: 'Admin Panel', path: 'admin', meta: { requiresAuth: true, role: 'admin' } },
});
const navInstance = routeWeaver.build({
main: ['home', 'dashboard', 'admin'],
});
// For a regular user
const userNav = navInstance.filterNavigation(item => !item.meta?.requiresAuth || user.isAuthenticated);
// For an admin user
const adminNav = navInstance.filterNavigation(item => item.meta?.role === 'admin');
```
### Server-Side Rendering (SSR)
`@route-weaver/core` is fully compatible with server-side rendering environments. Since it's framework-agnostic, you can use it in any Node.js environment to pre-render navigation menus, generate sitemaps, or resolve routes on the server.
```typescript
// On the server
const navInstance = routeWeaver.build({ main: ['home', 'about'] });
const html = renderToString(<App navInstance={navInstance} />);
```
## Best Practices
### Structuring Your Routes
For larger applications, it's a good practice to co-locate your route definitions with the features they belong to and then import them into a central file.
```typescript
// features/users/routes.ts
export const userRoutes = {
userList: { label: 'Users', path: 'users' },
userDetail: { label: 'User Details', path: 'users/:id' },
};
// routes.ts
import { userRoutes } from './features/users/routes';
import { productRoutes } from './features/products/routes';
export const appRoutes = { ...userRoutes, ...productRoutes };
```
### Using the `meta` Property
The `meta` property is a flexible way to add extra information to your routes. Here are some common use cases:
* **Authorization**: `meta: { requiresAuth: true, roles: ['admin'] }`
* **Layouts**: `meta: { layout: 'sidebar' }`
* **Analytics**: `meta: { analyticsId: 'user-profile-page' }`
* **Breadcrumb Labels**: `meta: { breadcrumb: 'My Profile' }`
## Recipes
Here are some practical, real-world examples of how to use `@route-weaver/core` to solve common routing challenges.
### Protected Routes
Implement role-based access control using the `meta` property and `filterNavigation`. This allows you to create navigation menus that only show routes a user is authorized to see.
**Scenario**: You have an application with public pages, a user dashboard, and an admin panel.
```typescript
import { createRouteWeaver } from '@route-weaver/core';
// Mock user object
const currentUser = {
isAuthenticated: true,
roles: ['admin'],
};
const routeWeaver = createRouteWeaver({
home: { label: 'Home', path: '/' },
login: { label: 'Login', path: '/login' },
dashboard: { label: 'Dashboard', path: '/dashboard', meta: { requiresAuth: true } },
admin: { label: 'Admin', path: '/admin', meta: { requiresAuth: true, roles: ['admin'] } },
settings: { label: 'Settings', path: '/settings', meta: { requiresAuth: true } },
});
const navInstance = routeWeaver.build({
main: ['home', 'dashboard', 'admin', 'settings'],
auth: ['login'],
});
// Predicate to filter routes based on user authentication and roles
const isRouteVisible = (item) => {
const { meta } = item;
if (!meta?.requiresAuth) {
return true; // Always show public routes
}
if (!currentUser.isAuthenticated) {
return false; // Hide protected routes if not authenticated
}
if (meta.roles) {
return meta.roles.some(role => currentUser.roles.includes(role)); // Check for role match
}
return true; // Show authenticated routes without specific roles
};
// Generate the user-specific navigation
const visibleNav = navInstance.filterNavigation(isRouteVisible);
console.log(visibleNav.main.map(item => item.id));
// If user is admin: => ['home', 'dashboard', 'admin', 'settings']
// If user is authenticated but not admin: => ['home', 'dashboard', 'settings']
// If user is a guest: => ['home']
```
### Dynamic Breadcrumbs
Build a dynamic breadcrumb component using the `getBreadcrumbs` method. This is useful for helping users understand their location within a nested navigation structure.
**Scenario**: Your application has a nested settings area, and you want to display a breadcrumb trail like `Home > Settings > Profile`.
```typescript
import { createRouteWeaver } from '@route-weaver/core';
const routeWeaver = createRouteWeaver({
home: { label: 'Home', path: '/' },
settings: { label: 'Settings', path: '/settings' },
'settings.profile': { label: 'Profile', path: '/profile' },
'settings.account': { label: 'Account', path: '/account' },
});
const navInstance = routeWeaver.build({
main: ['home', { id: 'settings', children: ['settings.profile', 'settings.account'] }],
});
// Simulate being on the profile page
const currentPath = '/settings/profile';
const breadcrumbs = navInstance.getBreadcrumbs(currentPath);
// Render the breadcrumbs in your UI
breadcrumbs.forEach((crumb, index) => {
console.log(
`${crumb.label} ${index < breadcrumbs.length - 1 ? '>' : ''}`
);
});
// => Home >
// => Settings >
// => Profile
```
### Generating a Dynamic Navigation Menu
Use `filterNavigation` to build a navigation menu that changes based on user authentication status or other conditions.
**Scenario**: You want to show a "Login" button to guests and a "Logout" button to authenticated users.
```typescript
import { createRouteWeaver } from '@route-weaver/core';
const routeWeaver = createRouteWeaver({
home: { label: 'Home', path: '/' },
dashboard: { label: 'Dashboard', path: '/dashboard', meta: { requiresAuth: true } },
login: { label: 'Login', path: '/login', meta: { guestOnly: true } },
logout: { label: 'Logout', path: '/logout', meta: { requiresAuth: true } },
});
const navInstance = routeWeaver.build({
main: ['home', 'dashboard'],
auth: ['login', 'logout'],
});
function generateNavForUser(isAuthenticated) {
return navInstance.filterNavigation(item => {
if (item.meta?.guestOnly) {
return !isAuthenticated;
}
if (item.meta?.requiresAuth) {
return isAuthenticated;
}
return true;
});
}
// For a guest user
const guestNav = generateNavForUser(false);
console.log('Guest Nav:', guestNav.main.map(i => i.label), guestNav.auth.map(i => i.label));
// => Guest Nav: [ 'Home' ] [ 'Login' ]
// For an authenticated user
const userNav = generateNavForUser(true);
console.log('User Nav:', userNav.main.map(i => i.label), userNav.auth.map(i => i.label));
// => User Nav: [ 'Home', 'Dashboard' ] [ 'Logout' ]
```
## Live Example
Explore a live example of `@route-weaver/core` on CodeSandbox.
[](https://codesandbox.io/p/sandbox/route-weaver-core-example-g9z9zq)
## Contributing
Contributions are welcome! Please see the main [CONTRIBUTING.md](../../CONTRIBUTING.md) file for details.
## License
MIT