Update Readme.md

This commit is contained in:
Natxo
2026-03-16 22:07:49 +01:00
committed by GitHub
parent 2f38c9c455
commit c6881528cf

490
Readme.md
View File

@@ -752,33 +752,495 @@ const tempData = $.storage('temp', {}, sessionStorage);
---
### `$.router(routes)` - Router
## 🧭 `$.router(routes)` - Hash-Based Router
Hash-based router for SPAs with automatic page cleanup.
Creates a simple, powerful hash-based router for Single Page Applications (SPAs) with **automatic page cleanup** and **zero configuration**. Built on native browser APIs - no dependencies, no complex setup.
### Why Hash-Based?
Hash routing (`#/about`) works **everywhere** - no server configuration needed. It's perfect for:
- Static sites and SPAs
- GitHub Pages, Netlify, any static hosting
- Local development without a server
- Projects that need to work immediately
### Basic Usage
```javascript
import { $, html } from 'sigpro';
import HomePage from './pages/index.js';
import AboutPage from './pages/about.js';
import UserPage from './pages/user.js';
import HomePage from './pages/HomePage.js';
import AboutPage from './pages/AboutPage.js';
import UserPage from './pages/UserPage.js';
import NotFound from './pages/NotFound.js';
// Define your routes
const routes = [
{ path: '/', component: (params) => HomePage(params) },
{ path: '/about', component: (params) => AboutPage(params) },
{ path: /^\/user\/(?<id>\d+)$/, component: (params) => UserPage(params) },
{ path: '/', component: () => HomePage() },
{ path: '/about', component: () => AboutPage() },
{ path: '/users/:id', component: (params) => UserPage(params) },
{ path: /^\/posts\/(?<id>\d+)$/, component: (params) => PostPage(params) },
];
// Create and mount the router
const router = $.router(routes);
document.body.appendChild(router);
// Navigate programmatically
$.router.go('/about');
```
**Parameters:**
- `routes`: Array of route objects with `path` (string or RegExp) and `component` function
---
**Returns:** Container element with the current page
### 📋 Route Definition
Each route is an object with two properties:
| Property | Type | Description |
|----------|------|-------------|
| `path` | `string` or `RegExp` | Route pattern to match |
| `component` | `Function` | Function that returns page content (receives `params`) |
#### String Paths (Simple Routes)
```javascript
{ path: '/', component: () => HomePage() }
{ path: '/about', component: () => AboutPage() }
{ path: '/contact', component: () => ContactPage() }
{ path: '/users/:id', component: (params) => UserPage(params) } // With parameter
```
String paths support:
- **Static segments**: `/about`, `/contact`, `/products`
- **Named parameters**: `:id`, `:slug`, `:username` (captured in `params`)
#### RegExp Paths (Advanced Routing)
```javascript
// Match numeric IDs only
{ path: /^\/users\/(?<id>\d+)$/, component: (params) => UserPage(params) }
// Match product slugs (letters, numbers, hyphens)
{ path: /^\/products\/(?<slug>[a-z0-9-]+)$/, component: (params) => ProductPage(params) }
// Match blog posts by year/month
{ path: /^\/blog\/(?<year>\d{4})\/(?<month>\d{2})$/, component: (params) => BlogArchive(params) }
// Match optional language prefix
{ path: /^\/(?<lang>en|es|fr)?\/?about$/, component: (params) => AboutPage(params) }
```
RegExp gives you **full control** over route matching with named capture groups.
---
### 🎯 Route Parameters
Parameters are automatically extracted and passed to your component:
```javascript
// Route: '/users/:id'
// URL: '#/users/42'
{ path: '/users/:id', component: (params) => {
console.log(params.id); // "42"
return UserPage(params);
}}
// 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);
}}
```
---
### 🔄 Automatic Page Cleanup
The router **automatically cleans up** pages when navigating away:
```javascript
// pages/UserPage.js
import { $, html } from 'sigpro';
export default (params) => $.page(({ onUnmount }) => {
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`
<div>
${loading() ? 'Loading...' : html`<h1>${userData().name}</h1>`}
</div>
`;
});
```
All `$.effect`, `setInterval`, event listeners, and subscriptions are automatically cleaned up.
---
### 🧭 Navigation
#### Programmatic Navigation
```javascript
import { $ } from 'sigpro';
// 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`
<nav>
<a href="#/">Home</a>
<a href="#/about">About</a>
<a href="#/users/42">User 42</a>
<!-- Or use @click for programmatic -->
<button @click=${() => $.router.go('/contact')}>
Contact
</button>
</nav>
`;
```
#### Current Route Info
```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);
});
```
---
### 📑 Complete Examples
#### Basic SPA with Layout
```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';
// Layout component with navigation
const AppLayout = () => html`
<div class="app">
<header>
<nav>
<a href="#/">Home</a>
<a href="#/about">About</a>
<a href="#/contact">Contact</a>
</nav>
</header>
<main>
<!-- Router will inject pages here -->
<div id="router-outlet"></div>
</main>
<footer>
<p>© 2024 My App</p>
</footer>
</div>
`;
// 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);
```
#### 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
#### `$.router(routes)`
Creates a router instance.
| Parameter | Type | Description |
|-----------|------|-------------|
| `routes` | `Array<Route>` | Array of route objects |
**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 | ✅ | ❌ | ❌ |
### 💡 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
]
```
2. **Use RegExp for validation**
```javascript
// Only numeric IDs
{ path: /^\/users\/(?<id>\d+)$/, component: UserPage }
// Only lowercase slugs
{ 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`
---