UNPKG

@route-weaver/core

Version:

A typesafe navigation package for creating and managing routes.

342 lines (241 loc) 12.6 kB
# @route-weaver/core [![npm version](https://badge.fury.io/js/%40route-weaver%2Fcore.svg)](https://badge.fury.io/js/%40route-weaver%2Fcore) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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. [![Edit on CodeSandbox](https://codesandbox.io/static/img/play-codesandbox.svg)](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