@iyulab/router 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -361
- package/dist/main.cjs.js +495 -404
- package/dist/main.d.ts +103 -139
- package/dist/main.es.js +492 -402
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,15 +5,15 @@ A modern, lightweight client-side router for web applications with support for b
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- 🚀 **Modern URLPattern-based routing** - Uses native URLPattern API for powerful path matching
|
|
8
|
-
- 🔧 **
|
|
8
|
+
- 🔧 **Unified Framework Support** - Works with both Lit and React components using render functions
|
|
9
9
|
- 📱 **Client-side Navigation** - History API integration with browser back/forward support
|
|
10
|
-
- 🎯 **Nested Routing** - Support for deeply nested route hierarchies
|
|
11
|
-
- ⚡ **Async Data Loading** - Built-in loader functions for data fetching
|
|
10
|
+
- 🎯 **Nested Routing** - Support for deeply nested route hierarchies with index and path routes
|
|
12
11
|
- 🔗 **Smart Link Component** - Automatic external link detection and handling
|
|
13
|
-
- 📊 **Route Events** - Track navigation progress and
|
|
14
|
-
- 🎨 **Flexible Outlet System** -
|
|
12
|
+
- 📊 **Route Events** - Track navigation progress with route-begin, route-done, and route-error events
|
|
13
|
+
- 🎨 **Flexible Outlet System** - Unified rendering with renderContent method
|
|
15
14
|
- 🌐 **Global Route Access** - Access current route information anywhere via `window.route`
|
|
16
15
|
- 🔄 **Force Re-rendering** - Control component re-rendering on route changes
|
|
16
|
+
- ⚠️ **Enhanced Error Handling** - Built-in ErrorPage component with improved styling
|
|
17
17
|
|
|
18
18
|
## Installation
|
|
19
19
|
|
|
@@ -27,22 +27,19 @@ npm install @iyulab/router
|
|
|
27
27
|
|
|
28
28
|
```typescript
|
|
29
29
|
import { Router } from '@iyulab/router';
|
|
30
|
+
import { html } from 'lit';
|
|
30
31
|
|
|
31
32
|
const router = new Router({
|
|
32
33
|
root: document.getElementById('app')!,
|
|
33
34
|
basepath: '/app',
|
|
34
35
|
routes: [
|
|
35
36
|
{
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
index: true,
|
|
38
|
+
render: () => html`<home-page></home-page>`
|
|
38
39
|
},
|
|
39
40
|
{
|
|
40
41
|
path: '/user/:id',
|
|
41
|
-
|
|
42
|
-
loader: async (routeInfo) => {
|
|
43
|
-
const response = await fetch(`/api/user/${routeInfo.params.id}`);
|
|
44
|
-
return response.json();
|
|
45
|
-
}
|
|
42
|
+
render: (routeInfo) => html`<user-page .userId=${routeInfo.params.id}></user-page>`
|
|
46
43
|
}
|
|
47
44
|
]
|
|
48
45
|
});
|
|
@@ -51,411 +48,108 @@ const router = new Router({
|
|
|
51
48
|
router.go(window.location.href);
|
|
52
49
|
```
|
|
53
50
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
### Using with Lit Components
|
|
57
|
-
|
|
58
|
-
```typescript
|
|
59
|
-
import { LitElement, html } from 'lit';
|
|
60
|
-
import { customElement } from 'lit/decorators.js';
|
|
61
|
-
|
|
62
|
-
@customElement('app-root')
|
|
63
|
-
export class AppRoot extends LitElement {
|
|
64
|
-
render() {
|
|
65
|
-
return html`
|
|
66
|
-
<nav>
|
|
67
|
-
<u-link href="/">Home</u-link>
|
|
68
|
-
<u-link href="/about">About</u-link>
|
|
69
|
-
<u-link href="/user/123">User Profile</u-link>
|
|
70
|
-
</nav>
|
|
71
|
-
<main>
|
|
72
|
-
<u-outlet></u-outlet>
|
|
73
|
-
</main>
|
|
74
|
-
`;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### Using with React Components
|
|
80
|
-
|
|
81
|
-
```tsx
|
|
82
|
-
import React from 'react';
|
|
83
|
-
import { Outlet, Link } from '@iyulab/router';
|
|
84
|
-
|
|
85
|
-
export function AppRoot() {
|
|
86
|
-
return (
|
|
87
|
-
<div>
|
|
88
|
-
<nav>
|
|
89
|
-
<Link href="/">Home</Link>
|
|
90
|
-
<Link href="/about">About</Link>
|
|
91
|
-
<Link href="/user/123">User Profile</Link>
|
|
92
|
-
</nav>
|
|
93
|
-
<main>
|
|
94
|
-
<Outlet />
|
|
95
|
-
</main>
|
|
96
|
-
</div>
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
### Advanced Routing Features
|
|
102
|
-
|
|
103
|
-
#### Nested Routes with Layouts
|
|
51
|
+
### Nested Routes
|
|
104
52
|
|
|
105
53
|
```typescript
|
|
106
54
|
const routes = [
|
|
107
55
|
{
|
|
108
56
|
path: '/dashboard',
|
|
109
|
-
|
|
57
|
+
render: () => html`<dashboard-layout><u-outlet></u-outlet></dashboard-layout>`,
|
|
110
58
|
children: [
|
|
111
59
|
{
|
|
112
60
|
index: true, // Matches /dashboard exactly
|
|
113
|
-
|
|
61
|
+
render: () => html`<dashboard-home></dashboard-home>`
|
|
114
62
|
},
|
|
115
63
|
{
|
|
116
64
|
path: 'settings',
|
|
117
|
-
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
path: 'profile',
|
|
121
|
-
component: ProfileComponent
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
path: 'users/:id',
|
|
125
|
-
component: UserDetailComponent,
|
|
126
|
-
loader: async (routeInfo) => {
|
|
127
|
-
const userId = routeInfo.params.id;
|
|
128
|
-
return await fetchUserData(userId);
|
|
129
|
-
}
|
|
65
|
+
render: () => html`<dashboard-settings></dashboard-settings>`
|
|
130
66
|
}
|
|
131
67
|
]
|
|
132
68
|
}
|
|
133
69
|
];
|
|
134
70
|
```
|
|
135
71
|
|
|
136
|
-
|
|
72
|
+
### Mixed Framework Support
|
|
137
73
|
|
|
138
74
|
```typescript
|
|
139
|
-
|
|
140
|
-
{
|
|
141
|
-
path: '/posts/:slug',
|
|
142
|
-
component: PostComponent,
|
|
143
|
-
loader: async (routeInfo) => {
|
|
144
|
-
const { slug } = routeInfo.params;
|
|
145
|
-
const post = await fetch(`/api/posts/${slug}`).then(r => r.json());
|
|
146
|
-
return { post };
|
|
147
|
-
},
|
|
148
|
-
title: 'Blog Post' // Sets document title
|
|
149
|
-
}
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
#### Force Re-rendering Control
|
|
75
|
+
import React from 'react';
|
|
153
76
|
|
|
154
|
-
```typescript
|
|
155
77
|
const routes = [
|
|
78
|
+
// Lit component
|
|
79
|
+
{
|
|
80
|
+
path: '/lit-page',
|
|
81
|
+
render: (routeInfo) => html`<my-lit-component .routeInfo=${routeInfo}></my-lit-component>`
|
|
82
|
+
},
|
|
83
|
+
// React component
|
|
156
84
|
{
|
|
157
|
-
path: '/
|
|
158
|
-
|
|
159
|
-
force: true // Always re-render on navigation
|
|
85
|
+
path: '/react-page',
|
|
86
|
+
render: (routeInfo) => React.createElement(MyReactComponent, { routeInfo })
|
|
160
87
|
},
|
|
88
|
+
// HTML element
|
|
161
89
|
{
|
|
162
|
-
path: '/
|
|
163
|
-
|
|
164
|
-
|
|
90
|
+
path: '/element-page',
|
|
91
|
+
render: (routeInfo) => {
|
|
92
|
+
const element = document.createElement('my-element');
|
|
93
|
+
element.data = routeInfo.params;
|
|
94
|
+
return element;
|
|
95
|
+
}
|
|
165
96
|
}
|
|
166
97
|
];
|
|
167
98
|
```
|
|
168
99
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
Access current route information anywhere in your application:
|
|
172
|
-
|
|
173
|
-
```typescript
|
|
174
|
-
// Available globally on window.route
|
|
175
|
-
console.log('Current path:', window.route.pathname);
|
|
176
|
-
console.log('Route params:', window.route.params);
|
|
177
|
-
console.log('Query params:', window.route.search);
|
|
178
|
-
console.log('Loaded data:', window.route.data);
|
|
179
|
-
```
|
|
100
|
+
## Usage Examples
|
|
180
101
|
|
|
181
|
-
|
|
102
|
+
### Using with Lit Components
|
|
182
103
|
|
|
183
104
|
```typescript
|
|
184
105
|
import { LitElement, html } from 'lit';
|
|
185
|
-
import { customElement
|
|
186
|
-
|
|
187
|
-
@customElement('route-aware-component')
|
|
188
|
-
export class RouteAwareComponent extends LitElement {
|
|
189
|
-
@state() private routeInfo = window.route;
|
|
190
|
-
|
|
191
|
-
connectedCallback() {
|
|
192
|
-
super.connectedCallback();
|
|
193
|
-
// Listen for route changes
|
|
194
|
-
window.addEventListener('route-end', this.handleRouteChange);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
disconnectedCallback() {
|
|
198
|
-
window.removeEventListener('route-end', this.handleRouteChange);
|
|
199
|
-
super.disconnectedCallback();
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
private handleRouteChange = (event: CustomEvent) => {
|
|
203
|
-
this.routeInfo = event.detail;
|
|
204
|
-
this.requestUpdate();
|
|
205
|
-
};
|
|
106
|
+
import { customElement } from 'lit/decorators.js';
|
|
206
107
|
|
|
108
|
+
@customElement('app-root')
|
|
109
|
+
export class AppRoot extends LitElement {
|
|
207
110
|
render() {
|
|
208
111
|
return html`
|
|
209
|
-
<
|
|
210
|
-
|
|
211
|
-
|
|
112
|
+
<nav>
|
|
113
|
+
<u-link href="/">Home</u-link>
|
|
114
|
+
<u-link href="/about">About</u-link>
|
|
115
|
+
<u-link href="/user/123">User Profile</u-link>
|
|
116
|
+
</nav>
|
|
117
|
+
<main>
|
|
118
|
+
<u-outlet></u-outlet>
|
|
119
|
+
</main>
|
|
212
120
|
`;
|
|
213
121
|
}
|
|
214
122
|
}
|
|
215
123
|
```
|
|
216
124
|
|
|
217
|
-
|
|
125
|
+
### Using with React Components
|
|
218
126
|
|
|
219
127
|
```tsx
|
|
220
|
-
import React
|
|
221
|
-
|
|
222
|
-
export function RouteAwareComponent() {
|
|
223
|
-
const [routeInfo, setRouteInfo] = useState(window.route);
|
|
224
|
-
|
|
225
|
-
useEffect(() => {
|
|
226
|
-
const handleRouteChange = (event) => {
|
|
227
|
-
setRouteInfo(event.detail);
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
window.addEventListener('route-end', handleRouteChange);
|
|
231
|
-
return () => window.removeEventListener('route-end', handleRouteChange);
|
|
232
|
-
}, []);
|
|
128
|
+
import React from 'react';
|
|
129
|
+
import { Outlet, Link } from '@iyulab/router';
|
|
233
130
|
|
|
131
|
+
export function AppRoot() {
|
|
234
132
|
return (
|
|
235
133
|
<div>
|
|
236
|
-
<
|
|
237
|
-
|
|
238
|
-
|
|
134
|
+
<nav>
|
|
135
|
+
<Link href="/">Home</Link>
|
|
136
|
+
<Link href="/about">About</Link>
|
|
137
|
+
<Link href="/user/123">User Profile</Link>
|
|
138
|
+
</nav>
|
|
139
|
+
<main>
|
|
140
|
+
<Outlet />
|
|
141
|
+
</main>
|
|
239
142
|
</div>
|
|
240
143
|
);
|
|
241
144
|
}
|
|
242
145
|
```
|
|
243
146
|
|
|
244
|
-
## API Reference
|
|
245
|
-
|
|
246
|
-
### Router Class
|
|
247
|
-
|
|
248
|
-
#### Constructor
|
|
249
|
-
|
|
250
|
-
```typescript
|
|
251
|
-
new Router(config: RouterConfig)
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
**RouterConfig:**
|
|
255
|
-
- `root: HTMLElement` - Root element where router renders components
|
|
256
|
-
- `basepath?: string` - Base path for all routes (default: '/')
|
|
257
|
-
- `routes: RouteConfig[]` - Array of route configurations
|
|
258
|
-
|
|
259
|
-
#### Methods
|
|
260
|
-
|
|
261
|
-
- `go(href: string): Promise<void>` - Navigate to specified path
|
|
262
|
-
- `get basepath(): string` - Get current base path
|
|
263
|
-
- `get routeInfo(): RouteInfo | undefined` - Get current route information
|
|
264
|
-
|
|
265
|
-
### Route Configuration
|
|
266
|
-
|
|
267
|
-
```typescript
|
|
268
|
-
interface RouteConfig {
|
|
269
|
-
id?: string; // Unique route identifier (auto-generated)
|
|
270
|
-
path?: string; // URL pattern (URLPattern syntax)
|
|
271
|
-
index?: boolean; // Index route (matches parent exactly)
|
|
272
|
-
title?: string; // Document title
|
|
273
|
-
element?: typeof LitElement | string; // Lit element class or tag name
|
|
274
|
-
component?: ComponentType; // React component
|
|
275
|
-
loader?: (info: RouteInfo) => Promise<any>; // Async data loader
|
|
276
|
-
children?: RouteConfig[]; // Nested routes
|
|
277
|
-
force?: boolean; // Force re-render on navigation
|
|
278
|
-
}
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
#### URLPattern Examples
|
|
282
|
-
|
|
283
|
-
```typescript
|
|
284
|
-
// Static paths
|
|
285
|
-
{ path: '/about' }
|
|
286
|
-
{ path: '/contact' }
|
|
287
|
-
|
|
288
|
-
// Dynamic parameters
|
|
289
|
-
{ path: '/user/:id' }
|
|
290
|
-
{ path: '/blog/:year/:month/:slug' }
|
|
291
|
-
|
|
292
|
-
// Optional parameters
|
|
293
|
-
{ path: '/posts/:id?' }
|
|
294
|
-
|
|
295
|
-
// Wildcard matching
|
|
296
|
-
{ path: '/files/*' }
|
|
297
|
-
{ path: '/api/**' }
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
### Components
|
|
301
|
-
|
|
302
|
-
#### u-outlet / Outlet
|
|
303
|
-
|
|
304
|
-
Renders the matched route component.
|
|
305
|
-
|
|
306
|
-
```html
|
|
307
|
-
<!-- Lit usage -->
|
|
308
|
-
<u-outlet></u-outlet>
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
```jsx
|
|
312
|
-
// React usage
|
|
313
|
-
<Outlet />
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
#### u-link / Link
|
|
317
|
-
|
|
318
|
-
Smart navigation link with automatic external link detection.
|
|
319
|
-
|
|
320
|
-
```html
|
|
321
|
-
<!-- Lit usage -->
|
|
322
|
-
<u-link href="/about">About</u-link>
|
|
323
|
-
<u-link href="https://example.com">External</u-link>
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
```jsx
|
|
327
|
-
// React usage
|
|
328
|
-
<Link href="/about">About</Link>
|
|
329
|
-
<Link href="https://example.com">External</Link>
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
### Route Information
|
|
333
|
-
|
|
334
|
-
```typescript
|
|
335
|
-
interface RouteInfo {
|
|
336
|
-
pathname: string; // Current pathname
|
|
337
|
-
search: string; // Query string
|
|
338
|
-
hash: string; // URL hash
|
|
339
|
-
href: string; // Full URL
|
|
340
|
-
basepath: string; // Router base path
|
|
341
|
-
params: Record<string, string>; // Route parameters
|
|
342
|
-
data?: any; // Data from loader function
|
|
343
|
-
}
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
### Events
|
|
347
|
-
|
|
348
|
-
The router dispatches these events on the `window` object:
|
|
349
|
-
|
|
350
|
-
- **`route-start`** - Navigation begins
|
|
351
|
-
```typescript
|
|
352
|
-
window.addEventListener('route-start', (event) => {
|
|
353
|
-
console.log('Navigating to:', event.detail.pathname);
|
|
354
|
-
});
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
- **`route-end`** - Navigation completes successfully
|
|
358
|
-
```typescript
|
|
359
|
-
window.addEventListener('route-end', (event) => {
|
|
360
|
-
console.log('Navigation complete:', event.detail);
|
|
361
|
-
});
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
- **`route-error`** - Navigation fails
|
|
365
|
-
```typescript
|
|
366
|
-
window.addEventListener('route-error', (event) => {
|
|
367
|
-
console.error('Route error:', event.detail);
|
|
368
|
-
});
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
## Advanced Examples
|
|
372
|
-
|
|
373
|
-
### Error Handling
|
|
374
|
-
|
|
375
|
-
```typescript
|
|
376
|
-
// Custom error handling
|
|
377
|
-
window.addEventListener('route-error', (event) => {
|
|
378
|
-
const error = event.detail;
|
|
379
|
-
console.error(`Route error (${error.code}):`, error.message);
|
|
380
|
-
|
|
381
|
-
// Show user-friendly error message
|
|
382
|
-
if (error.code === '404') {
|
|
383
|
-
showNotification('Page not found');
|
|
384
|
-
} else {
|
|
385
|
-
showNotification('Navigation failed');
|
|
386
|
-
}
|
|
387
|
-
});
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
### Dynamic Route Registration
|
|
391
|
-
|
|
392
|
-
```typescript
|
|
393
|
-
const router = new Router({
|
|
394
|
-
root: document.getElementById('app')!,
|
|
395
|
-
routes: [
|
|
396
|
-
{
|
|
397
|
-
path: '/',
|
|
398
|
-
element: 'home-page'
|
|
399
|
-
}
|
|
400
|
-
]
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
// Add routes dynamically by reconstructing the router
|
|
404
|
-
function addRoute(newRoute: RouteConfig) {
|
|
405
|
-
const newRoutes = [...router.routes, newRoute];
|
|
406
|
-
return new Router({
|
|
407
|
-
root: router.root,
|
|
408
|
-
basepath: router.basepath,
|
|
409
|
-
routes: newRoutes
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
### Progressive Enhancement
|
|
415
|
-
|
|
416
|
-
```typescript
|
|
417
|
-
// Check if URLPattern is supported
|
|
418
|
-
if ('URLPattern' in window) {
|
|
419
|
-
// Use the router
|
|
420
|
-
const router = new Router({ /* config */ });
|
|
421
|
-
router.go(window.location.href);
|
|
422
|
-
} else {
|
|
423
|
-
// Fallback to traditional navigation
|
|
424
|
-
console.warn('URLPattern not supported, using traditional navigation');
|
|
425
|
-
}
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
### Performance Optimization
|
|
429
|
-
|
|
430
|
-
```typescript
|
|
431
|
-
// Lazy load route components
|
|
432
|
-
const routes = [
|
|
433
|
-
{
|
|
434
|
-
path: '/dashboard',
|
|
435
|
-
loader: async () => {
|
|
436
|
-
// Dynamically import the component
|
|
437
|
-
const module = await import('./pages/DashboardPage.js');
|
|
438
|
-
return { component: module.DashboardPage };
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
];
|
|
442
|
-
```
|
|
443
|
-
|
|
444
147
|
## Browser Support
|
|
445
148
|
|
|
446
149
|
- **URLPattern API**: Required for routing functionality
|
|
447
150
|
- **Modern browsers**: Chrome 95+, Firefox 106+, Safari 16.4+
|
|
448
151
|
- **Polyfill**: Consider using [urlpattern-polyfill](https://www.npmjs.com/package/urlpattern-polyfill) for older browsers
|
|
449
152
|
|
|
450
|
-
## Migration Guide
|
|
451
|
-
|
|
452
|
-
### From v0.1.x to v0.2.x
|
|
453
|
-
|
|
454
|
-
- Removed `connect()` and `disconnect()` methods
|
|
455
|
-
- Navigation now starts with `router.go()` instead of `router.connect()`
|
|
456
|
-
- Event names changed from `route-change` to `route-start`/`route-end`
|
|
457
|
-
- Simplified component exports - use `UOutlet`/`Outlet` and `ULink`/`Link`
|
|
458
|
-
|
|
459
153
|
## Contributing
|
|
460
154
|
|
|
461
155
|
1. Fork the repository
|