From 9a865f1765d7e26d1cc959af2e5aac8e03aaae4b Mon Sep 17 00:00:00 2001 From: Natxo <1172351+natxocc@users.noreply.github.com> Date: Mon, 16 Mar 2026 23:01:46 +0100 Subject: [PATCH] Update Readme.md --- Readme.md | 427 ++++++++++-------------------------------------------- 1 file changed, 74 insertions(+), 353 deletions(-) diff --git a/Readme.md b/Readme.md index 2ce03ff..bd340a0 100644 --- a/Readme.md +++ b/Readme.md @@ -830,417 +830,138 @@ RegExp gives you **full control** over route matching with named capture groups. --- -### 🎯 Route Parameters +## 🧭 `$.router(routes)` - Simple Router with Parameters -Parameters are automatically extracted and passed to your component: +Creates a hash-based router with support for `:param` parameters. Automatically cleans up pages when navigating away. + +### 📋 Route Parameters (Human-Friendly) ```javascript -// Route: '/users/:id' -// URL: '#/users/42' -{ path: '/users/:id', component: (params) => { - console.log(params.id); // "42" - return UserPage(params); -}} - -// Route: /^\/posts\/(?\d+)$/ -// URL: '#/posts/123' -{ path: /^\/posts\/(?\d+)$/, component: (params) => { - console.log(params.id); // "123" - return PostPage(params); -}} - -// Multiple parameters -// Route: '/products/:category/:id' -// URL: '#/products/electronics/42' -{ path: '/products/:category/:id', component: (params) => { - console.log(params.category); // "electronics" - console.log(params.id); // "42" - return ProductPage(params); -}} +const routes = [ + { path: '/', component: HomePage }, + { path: '/about', component: AboutPage }, + { path: '/user/:id', component: UserPage }, // /user/42 → { id: '42' } + { path: '/user/:id/posts', component: UserPostsPage }, // /user/42/posts → { id: '42' } + { path: '/user/:id/posts/:pid', component: PostPage }, // /user/42/posts/123 → { id: '42', pid: '123' } + { path: '/search/:query/page/:num', component: SearchPage }, // /search/js/page/2 → { query: 'js', num: '2' } +]; ``` ---- +### 🎯 Accessing Parameters in Pages -### 🔄 Automatic Page Cleanup - -The router **automatically cleans up** pages when navigating away: +Parameters are automatically extracted and passed to your page component: ```javascript // pages/UserPage.js import { $, html } from 'sigpro'; -export default (params) => $.page(({ onUnmount }) => { +export default (params) => $.page(() => { + // /user/42 → params = { id: '42' } + const userId = params.id; const userData = $(null); - const loading = $(true); - - // Set up polling - const interval = setInterval(() => { - fetchUserData(params.id); - }, 5000); - - // Register cleanup - onUnmount(() => { - clearInterval(interval); // ✅ Auto-cleaned on navigation - console.log('UserPage cleaned up'); - }); return html`
- ${loading() ? 'Loading...' : html`

${userData().name}

`} +

User Profile: ${userId}

+

Loading user data...

+
+ `; +}); + +// pages/PostPage.js +export default (params) => $.page(() => { + // /user/42/posts/123 → params = { id: '42', pid: '123' } + const { id, pid } = params; + + return html` +
+

Post ${pid} from user ${id}

`; }); ``` -All `$.effect`, `setInterval`, event listeners, and subscriptions are automatically cleaned up. - ---- - ### 🧭 Navigation -#### Programmatic Navigation - ```javascript -import { $ } from 'sigpro'; +// Programmatic navigation +$.router.go('/user/42'); +$.router.go('/search/javascript/page/2'); +$.router.go('about'); // Same as '/about' (auto-adds leading slash) -// Go to specific route -$.router.go('/about'); -$.router.go('/users/42'); -$.router.go('/products/electronics/123'); - -// Automatically adds leading slash if missing -$.router.go('about'); // Same as '/about' -$.router.go('users/42'); // Same as '/users/42' - -// Works with hash -$.router.go('#/about'); // Also works -``` - -#### Link Navigation - -```javascript -// In your HTML templates -const navigation = html` +// Link navigation (in templates) +html` `; ``` -#### Current Route Info +### 🔄 Automatic Page Cleanup ```javascript -// Get current path (without hash) -const currentPath = window.location.hash.replace(/^#/, '') || '/'; - -// Listen to route changes -window.addEventListener('hashchange', () => { - console.log('Route changed to:', window.location.hash); +export default (params) => $.page(({ onUnmount }) => { + // Set up interval + const interval = setInterval(() => { + fetchData(params.id); + }, 5000); + + // Auto-cleaned when navigating away + onUnmount(() => clearInterval(interval)); + + return html`
Page content
`; }); ``` ---- - -### 📑 Complete Examples - -#### Basic SPA with Layout +### 📦 Usage in Templates ```javascript -// app.js import { $, html } from 'sigpro'; import HomePage from './pages/Home.js'; -import AboutPage from './pages/About.js'; -import ContactPage from './pages/Contact.js'; +import UserPage from './pages/User.js'; -// Layout component with navigation -const AppLayout = () => html` +const routes = [ + { path: '/', component: HomePage }, + { path: '/user/:id', component: UserPage }, +]; + +// Mount router directly in your template +const App = () => html`
-
- -
- +
My App
- -
+ ${$.router(routes)}
- -
-

© 2024 My App

-
`; -// Set up layout -document.body.appendChild(AppLayout()); - -// Create and mount router -const router = $.router([ - { path: '/', component: HomePage }, - { path: '/about', component: AboutPage }, - { path: '/contact', component: ContactPage }, -]); - -document.querySelector('#router-outlet').appendChild(router); +document.body.appendChild(App()); ``` -#### Blog with Dynamic Posts - -```javascript -// pages/PostPage.js -import { $, html } from 'sigpro'; - -export default (params) => $.page(({ onUnmount }) => { - const post = $(null); - const loading = $(true); - const error = $(null); - - // Fetch post data - const loadPost = async () => { - loading(true); - error(null); - - try { - const response = await fetch(`/api/posts/${params.id}`); - const data = await response.json(); - post(data); - } catch (e) { - error('Failed to load post'); - } finally { - loading(false); - } - }; - - loadPost(); - - return html` -
- ${loading() ? html`
Loading...
` : ''} - ${error() ? html`
${error()}
` : ''} - ${post() ? html` -

${post().title}

-
${post().content}
- ← Back to blog - ` : ''} -
- `; -}); - -// routes -const routes = [ - { path: '/blog', component: BlogListPage }, - { path: '/blog/:id', component: BlogPostPage }, // Dynamic route -]; -``` - -#### E-commerce with Categories - -```javascript -// routes with multiple parameters -const routes = [ - // Products by category - { path: '/products/:category', component: (params) => - ProductListPage({ category: params.category }) - }, - - // Specific product - { path: '/products/:category/:id', component: (params) => - ProductDetailPage({ - category: params.category, - id: params.id - }) - }, - - // Search with query (using RegExp) - { - path: /^\/search\/(?[^/]+)(?:\/page\/(?\d+))?$/, - component: (params) => SearchPage({ - query: decodeURIComponent(params.query), - page: params.page || 1 - }) - }, -]; -``` - ---- - -### ⚡ Advanced Features - -#### Nested Routes - -```javascript -// Parent route -const routes = [ - { - path: '/dashboard', - component: (params) => DashboardLayout(params, (nestedParams) => { - // Nested routing inside dashboard - const nestedRoutes = [ - { path: '/dashboard', component: DashboardHome }, - { path: '/dashboard/users', component: DashboardUsers }, - { path: '/dashboard/settings', component: DashboardSettings }, - ]; - return $.router(nestedRoutes); - }) - }, -]; -``` - -#### Route Guards - -```javascript -const routes = [ - { - path: '/admin', - component: (params) => { - // Simple auth guard - if (!isAuthenticated()) { - $.router.go('/login'); - return html``; - } - return AdminPage(params); - } - }, -]; -``` - -#### 404 Handling - -```javascript -const routes = [ - { path: '/', component: HomePage }, - { path: '/about', component: AboutPage }, - // No match = automatic 404 - // The router shows a default "404" if no route matches -]; - -// Or custom 404 page -const routesWith404 = [ - { path: '/', component: HomePage }, - { path: '/about', component: AboutPage }, - // Add catch-all at the end - { path: /.*/, component: () => Custom404Page() }, -]; -``` - ---- - ### 🎯 API Reference #### `$.router(routes)` - -Creates a router instance. - -| Parameter | Type | Description | -|-----------|------|-------------| -| `routes` | `Array` | Array of route objects | - -**Returns:** `HTMLDivElement` - Container that renders the current page +- **routes**: `Array<{path: string, component: Function}>` - Route configurations with `:param` support +- **Returns**: `HTMLDivElement` - Container that renders the current page #### `$.router.go(path)` - -Navigates to a route programmatically. - -| Parameter | Type | Description | -|-----------|------|-------------| -| `path` | `string` | Route path (automatically adds leading slash if missing) | - -**Example:** -```javascript -$.router.go('/users/42'); -$.router.go('about'); // Same as '/about' -``` - -#### Route Object - -| Property | Type | Description | -|----------|------|-------------| -| `path` | `string` or `RegExp` | Route pattern to match | -| `component` | `Function` | Function that returns page content | - -The `component` receives `params` object with extracted parameters. - ---- - -### 📊 Comparison - -| Feature | SigPro Router | React Router | Vue Router | -|---------|--------------|--------------|------------| -| Bundle Size | **~0.5KB** | ~20KB | ~15KB | -| Dependencies | **0** | Many | Many | -| Hash-based | ✅ | ✅ | ✅ | -| History API | ❌ (by design) | ✅ | ✅ | -| Route params | ✅ | ✅ | ✅ | -| Nested routes | ✅ | ✅ | ✅ | -| Lazy loading | ✅ (native) | ✅ | ✅ | -| Auto cleanup | ✅ | ❌ | ❌ | -| No config | ✅ | ❌ | ❌ | +- **path**: `string` - Route path (automatically adds leading slash) ### 💡 Pro Tips -1. **Always define the most specific routes first** - ```javascript - // ✅ Good - [ - { path: '/users/:id/edit', component: EditUser }, - { path: '/users/:id', component: ViewUser }, - { path: '/users', component: UserList }, - ] - - // ❌ Bad (catch-all before specific) - [ - { path: '/users/:id', component: ViewUser }, // This matches '/users/edit' too! - { path: '/users/:id/edit', component: EditUser }, // Never reached - ] - ``` +1. **Order matters** - Define more specific routes first: +```javascript +[ + { path: '/user/:id/edit', component: EditUser }, // More specific first + { path: '/user/:id', component: ViewUser }, // Then generic +] +``` -2. **Use RegExp for validation** - ```javascript - // Only numeric IDs - { path: /^\/users\/(?\d+)$/, component: UserPage } - - // Only lowercase slugs - { path: /^\/products\/(?[a-z-]+)$/, component: ProductPage } - ``` +2. **Cleanup is automatic** - All effects, intervals, and event listeners in `$.page` are cleaned up -3. **Lazy load pages for better performance** - ```javascript - const routes = [ - { path: '/', component: () => import('./Home.js').then(m => m.default) }, - { path: '/about', component: () => import('./About.js').then(m => m.default) }, - ]; - ``` - -4. **Access route params anywhere** - ```javascript - // In any component, not just pages - const currentParams = () => { - const match = window.location.hash.match(/^#\/users\/(?\d+)$/); - return match?.groups || {}; - }; - ``` - ---- - -### 🎉 Why SigPro Router? - -- **Zero config** - Works immediately -- **No dependencies** - Just vanilla JS -- **Automatic cleanup** - No memory leaks -- **Tiny footprint** - ~0.5KB minified -- **Hash-based** - Works everywhere -- **RegExp support** - Full routing power -- **Page components** - Built for `$.page` +3. **Zero config** - Just define routes and use them ---