UNPKG

@route-weaver/core

Version:

A typesafe navigation package for creating and managing routes.

379 lines (272 loc) 13.8 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 ### `createNavigation(routes, options)` The primary function for creating a navigation instance. It takes a nested route structure and returns a fully configured `NavigationInstance`. * **`routes`**: A `NestedRouteDeclarations` object where each key is a unique route ID and the value is a `RouteDefinition` object (`{ path: string; label: string; children?: NestedRouteDeclarations; meta?: object }`). * **`options`** (optional): An object for advanced configuration. * `t`: A translator function for internationalization (i18n). * **Returns**: A `NavigationInstance` containing the resolved navigation structure and helper methods. **Example**: ```typescript import { createNavigation } from '@route-weaver/core'; const navInstance = createNavigation({ home: { path: '/', label: 'Home' }, about: { path: 'about', label: 'About' }, contact: { path: 'contact', label: 'Contact', children: { form: { path: 'form', label: 'Form' }, info: { path: 'info', label: 'Info' }, }, }, }); const homeUrl = navInstance.buildPath('home'); // => "/" const contactFormUrl = navInstance.buildPath('form'); // => "/contact/form" ``` ### Two Ways to Create Your Navigation: `createNavigation` vs. `createRouteWeaver` `@route-weaver/core` offers two primary functions for setting up your navigation: 1. **`createNavigation`**: This is the most direct and recommended method for most use cases. It allows you to define your routes and their hierarchical structure in a single, nested object. It's simple, intuitive, and gets you up and running quickly. 2. **`createRouteWeaver`**: This is a more advanced option that provides greater flexibility by separating route declaration from navigation structure. You first declare a flat map of all possible routes and then build different navigation structures from them. This is useful in complex scenarios, such as when you need to generate multiple, distinct navigation menus from the same set of underlying routes. Both methods produce a fully typesafe `NavigationInstance`, ensuring that your application benefits from the same level of compile-time safety regardless of which approach you choose. --- ### `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 For more granular control, you can use the `createRouteWeaver` function. This is useful when you need to separate the route declarations from the navigation structure. ### `createRouteWeaver(routes)` Initializes the weaver with a flat map of your application's routes. * **`routes`**: A `RouteDeclarations` object where each key is a unique route ID and the value is either a path string or an object with a `path` and optional `meta`. * **Returns**: A `RouteWeaverInstance` with a `buildNav` method. ### `weaver.buildNav(definitions, options)` Builds the final, structured navigation instance from the previously declared routes. * **`definitions`**: A `NavDefinitions` object that defines the hierarchical structure of your navigation menus. * **`options`** (optional): * `t`: A translator function for i18n. * **Returns**: A `NavigationInstance`. **Example**: ```typescript import { createRouteWeaver } from '@route-weaver/core'; const weaver = createRouteWeaver({ home: '/', about: 'about', contact: 'contact', form: 'contact/form', info: 'contact/info', }); const navInstance = weaver.buildNav({ main: { home: 'Home', about: 'About', contact: 'Contact', }, contact: { form: 'Form', info: 'Info', }, }); ``` ### 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 navInstance = createNavigation({ home: { path: '/', label: 'Home' }, dashboard: { path: 'dashboard', label: 'Dashboard', meta: { requiresAuth: true } }, admin: { path: 'admin', label: 'Admin Panel', meta: { requiresAuth: true, role: '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 { createNavigation } from '@route-weaver/core'; // Mock user object const currentUser = { isAuthenticated: true, roles: ['admin'], }; const navInstance = createNavigation({ home: { path: '/', label: 'Home' }, login: { path: 'login', label: 'Login' }, dashboard: { path: 'dashboard', label: 'Dashboard', meta: { requiresAuth: true } }, admin: { path: 'admin', label: 'Admin Panel', meta: { requiresAuth: true, roles: ['admin'] } }, settings: { path: 'settings', label: 'Settings', meta: { requiresAuth: true } }, }); // 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.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', 'login' ] ``` ### 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 { createNavigation } from '@route-weaver/core'; const navInstance = createNavigation({ home: { path: '/', label: 'Home' }, settings: { path: 'settings', label: 'Settings', children: { profile: { path: 'profile', label: 'Profile' }, account: { path: 'account', label: '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 { createNavigation } from '@route-weaver/core'; const navInstance = createNavigation({ home: { path: '/', label: 'Home' }, dashboard: { path: 'dashboard', label: 'Dashboard', meta: { requiresAuth: true } }, login: { path: 'login', label: 'Login', meta: { guestOnly: true } }, logout: { path: 'logout', label: 'Logout', meta: { requiresAuth: true } }, }); 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.map(i => i.label)); // => Guest Nav: [ 'Home', 'Login' ] // For an authenticated user const userNav = generateNavForUser(true); console.log('User Nav:', userNav.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