Update Readme.md

This commit is contained in:
Natxo
2026-03-16 23:01:46 +01:00
committed by GitHub
parent c6881528cf
commit 9a865f1765

417
Readme.md
View File

@@ -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 ```javascript
// Route: '/users/:id' const routes = [
// URL: '#/users/42' { path: '/', component: HomePage },
{ path: '/users/:id', component: (params) => { { path: '/about', component: AboutPage },
console.log(params.id); // "42" { path: '/user/:id', component: UserPage }, // /user/42 → { id: '42' }
return UserPage(params); { 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' }
// Route: /^\/posts\/(?<id>\d+)$/ ];
// URL: '#/posts/123'
{ path: /^\/posts\/(?<id>\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);
}}
``` ```
--- ### 🎯 Accessing Parameters in Pages
### 🔄 Automatic Page Cleanup Parameters are automatically extracted and passed to your page component:
The router **automatically cleans up** pages when navigating away:
```javascript ```javascript
// pages/UserPage.js // pages/UserPage.js
import { $, html } from 'sigpro'; 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 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` return html`
<div> <div>
${loading() ? 'Loading...' : html`<h1>${userData().name}</h1>`} <h1>User Profile: ${userId}</h1>
<p>Loading user data...</p>
</div>
`;
});
// pages/PostPage.js
export default (params) => $.page(() => {
// /user/42/posts/123 → params = { id: '42', pid: '123' }
const { id, pid } = params;
return html`
<div>
<h1>Post ${pid} from user ${id}</h1>
</div> </div>
`; `;
}); });
``` ```
All `$.effect`, `setInterval`, event listeners, and subscriptions are automatically cleaned up.
---
### 🧭 Navigation ### 🧭 Navigation
#### Programmatic Navigation
```javascript ```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 // Link navigation (in templates)
$.router.go('/about'); html`
$.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`
<nav> <nav>
<a href="#/">Home</a> <a href="#/">Home</a>
<a href="#/about">About</a> <a href="#/user/42">Profile</a>
<a href="#/users/42">User 42</a> <button @click=${() => $.router.go('/contact')}>Contact</button>
<!-- Or use @click for programmatic -->
<button @click=${() => $.router.go('/contact')}>
Contact
</button>
</nav> </nav>
`; `;
``` ```
#### Current Route Info ### 🔄 Automatic Page Cleanup
```javascript ```javascript
// Get current path (without hash) export default (params) => $.page(({ onUnmount }) => {
const currentPath = window.location.hash.replace(/^#/, '') || '/'; // Set up interval
const interval = setInterval(() => {
fetchData(params.id);
}, 5000);
// Listen to route changes // Auto-cleaned when navigating away
window.addEventListener('hashchange', () => { onUnmount(() => clearInterval(interval));
console.log('Route changed to:', window.location.hash);
return html`<div>Page content</div>`;
}); });
``` ```
--- ### 📦 Usage in Templates
### 📑 Complete Examples
#### Basic SPA with Layout
```javascript ```javascript
// app.js
import { $, html } from 'sigpro'; import { $, html } from 'sigpro';
import HomePage from './pages/Home.js'; import HomePage from './pages/Home.js';
import AboutPage from './pages/About.js'; import UserPage from './pages/User.js';
import ContactPage from './pages/Contact.js';
// Layout component with navigation const routes = [
const AppLayout = () => html` { path: '/', component: HomePage },
{ path: '/user/:id', component: UserPage },
];
// Mount router directly in your template
const App = () => html`
<div class="app"> <div class="app">
<header> <header>My App</header>
<nav>
<a href="#/">Home</a>
<a href="#/about">About</a>
<a href="#/contact">Contact</a>
</nav>
</header>
<main> <main>
<!-- Router will inject pages here --> ${$.router(routes)} <!-- Router renders here -->
<div id="router-outlet"></div>
</main> </main>
<footer>
<p>© 2024 My App</p>
</footer>
</div> </div>
`; `;
// Set up layout document.body.appendChild(App());
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);
``` ```
#### 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`
<article>
${loading() ? html`<div class="spinner">Loading...</div>` : ''}
${error() ? html`<div class="error">${error()}</div>` : ''}
${post() ? html`
<h1>${post().title}</h1>
<div class="content">${post().content}</div>
<a href="#/blog">← Back to blog</a>
` : ''}
</article>
`;
});
// 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\/(?<query>[^/]+)(?:\/page\/(?<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`<!-- empty -->`;
}
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 ### 🎯 API Reference
#### `$.router(routes)` #### `$.router(routes)`
- **routes**: `Array<{path: string, component: Function}>` - Route configurations with `:param` support
Creates a router instance. - **Returns**: `HTMLDivElement` - Container that renders the current page
| Parameter | Type | Description |
|-----------|------|-------------|
| `routes` | `Array<Route>` | Array of route objects |
**Returns:** `HTMLDivElement` - Container that renders the current page
#### `$.router.go(path)` #### `$.router.go(path)`
- **path**: `string` - Route path (automatically adds leading slash)
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 | ✅ | ❌ | ❌ |
### 💡 Pro Tips ### 💡 Pro Tips
1. **Always define the most specific routes first** 1. **Order matters** - Define more specific routes first:
```javascript ```javascript
// ✅ Good
[ [
{ path: '/users/:id/edit', component: EditUser }, { path: '/user/:id/edit', component: EditUser }, // More specific first
{ path: '/users/:id', component: ViewUser }, { path: '/user/:id', component: ViewUser }, // Then generic
{ 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
] ]
``` ```
2. **Use RegExp for validation** 2. **Cleanup is automatic** - All effects, intervals, and event listeners in `$.page` are cleaned up
```javascript
// Only numeric IDs
{ path: /^\/users\/(?<id>\d+)$/, component: UserPage }
// Only lowercase slugs 3. **Zero config** - Just define routes and use them
{ path: /^\/products\/(?<slug>[a-z-]+)$/, component: ProductPage }
```
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\/(?<id>\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`
--- ---