olova-router 1.0.19 → 1.0.21
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/LICENSE +21 -0
- package/README.md +402 -53
- package/dist/breadcrumbs.d.ts +272 -0
- package/dist/breadcrumbs.js +36 -0
- package/dist/breadcrumbs.js.map +1 -0
- package/dist/guards.d.ts +4 -0
- package/dist/guards.js +66 -0
- package/dist/guards.js.map +1 -0
- package/dist/history.d.ts +17 -0
- package/dist/history.js +51 -0
- package/dist/history.js.map +1 -0
- package/dist/index.d.ts +4 -117
- package/dist/index.js +912 -5
- package/dist/index.js.map +1 -1
- package/dist/lazy.d.ts +9 -0
- package/dist/lazy.js +127 -0
- package/dist/lazy.js.map +1 -0
- package/dist/middleware.d.ts +4 -0
- package/dist/middleware.js +54 -0
- package/dist/middleware.js.map +1 -0
- package/dist/vite.js +78 -50
- package/dist/vite.js.map +1 -1
- package/package.json +84 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Olova Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,8 +1,31 @@
|
|
|
1
1
|
# olova-router
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A powerful, feature-rich router for Olova framework with advanced routing capabilities, middleware support, and lazy loading.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🚀 **Browser History & Hash Modes** - Support for both history and hash-based routing
|
|
8
|
+
- 🛡️ **Route Guards** - Protect routes with beforeEnter guards and middleware
|
|
9
|
+
- 🎯 **Dynamic Routes** - Support for dynamic params like `/blog/:slug` and wildcards
|
|
10
|
+
- 📦 **Lazy Loading** - Code splitting with lazy component loading
|
|
11
|
+
- 🔗 **Named Routes** - Reference routes by name instead of path
|
|
12
|
+
- 🎨 **Active Link Styling** - Automatic active state detection for links
|
|
13
|
+
- 📍 **Breadcrumbs** - Built-in breadcrumb generation utilities
|
|
14
|
+
- 🔄 **Middleware Chain** - Composable middleware for cross-cutting concerns
|
|
15
|
+
- 📜 **Route History** - Track navigation history with utilities
|
|
16
|
+
- ⚡ **Type Safe** - Full TypeScript support with comprehensive types
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install olova-router
|
|
22
|
+
# or
|
|
23
|
+
pnpm add olova-router
|
|
24
|
+
# or
|
|
25
|
+
yarn add olova-router
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
6
29
|
|
|
7
30
|
```olova
|
|
8
31
|
<script>
|
|
@@ -23,7 +46,7 @@ Standalone router package for Olova.
|
|
|
23
46
|
name: "about"
|
|
24
47
|
},
|
|
25
48
|
{
|
|
26
|
-
path: "/blog/:
|
|
49
|
+
path: "/blog/:slug",
|
|
27
50
|
component: Blog,
|
|
28
51
|
name: "blog.show"
|
|
29
52
|
}
|
|
@@ -35,70 +58,396 @@ Standalone router package for Olova.
|
|
|
35
58
|
<Link name="about">About</Link>
|
|
36
59
|
<Link
|
|
37
60
|
name="blog.show"
|
|
38
|
-
params={{
|
|
61
|
+
params={{ slug: "getting-started" }}
|
|
39
62
|
activeClass="text-emerald-300"
|
|
40
63
|
>
|
|
41
|
-
|
|
64
|
+
Blog
|
|
42
65
|
</Link>
|
|
43
66
|
</nav>
|
|
44
67
|
|
|
45
68
|
<BrowserRouter routes={routes} />
|
|
46
69
|
```
|
|
47
70
|
|
|
48
|
-
##
|
|
49
|
-
|
|
50
|
-
- `Router`
|
|
51
|
-
- `BrowserRouter`
|
|
52
|
-
- `HashRouter`
|
|
53
|
-
- `Link`
|
|
54
|
-
- `Outlet`
|
|
55
|
-
- `useRouter()`
|
|
56
|
-
- `useRoute()`
|
|
57
|
-
- `useParams()`
|
|
58
|
-
- `useQuery()`
|
|
59
|
-
- `navigate()`
|
|
60
|
-
- `matchPath()`
|
|
61
|
-
|
|
62
|
-
## Route Features
|
|
63
|
-
|
|
64
|
-
- browser history mode without `#`
|
|
65
|
-
- hash mode for static hosting
|
|
66
|
-
- dynamic params like `/blog/:slug`
|
|
67
|
-
- wildcard routes like `/docs/*`
|
|
68
|
-
- named routes
|
|
69
|
-
- redirects
|
|
70
|
-
- route guards with `beforeEnter`
|
|
71
|
-
- nested routes with `children`
|
|
72
|
-
- layout routes with `Outlet`
|
|
73
|
-
- active link styling
|
|
74
|
-
- object targets with `params`, `query`, and `hash`
|
|
75
|
-
|
|
76
|
-
## Nested Routes
|
|
71
|
+
## Core API
|
|
77
72
|
|
|
78
|
-
|
|
79
|
-
<script>
|
|
80
|
-
import { BrowserRouter, Outlet } from "olova-router";
|
|
73
|
+
### Router Components
|
|
81
74
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
75
|
+
- `Router` - Base router component
|
|
76
|
+
- `BrowserRouter` - History mode router (recommended)
|
|
77
|
+
- `HashRouter` - Hash mode router (for static hosting)
|
|
78
|
+
- `Link` - Navigation link component
|
|
79
|
+
- `Outlet` - Nested route outlet
|
|
80
|
+
|
|
81
|
+
### Router Hooks
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { useRouter, useRoute, useParams, useQuery } from "olova-router";
|
|
85
|
+
|
|
86
|
+
// Get full router API
|
|
87
|
+
const router = useRouter();
|
|
88
|
+
|
|
89
|
+
// Get current route location
|
|
90
|
+
const route = useRoute();
|
|
91
|
+
|
|
92
|
+
// Get route parameters
|
|
93
|
+
const params = useParams();
|
|
94
|
+
|
|
95
|
+
// Get query parameters
|
|
96
|
+
const query = useQuery();
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Navigation
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { navigate, link } from "olova-router";
|
|
103
|
+
|
|
104
|
+
// Programmatic navigation
|
|
105
|
+
navigate("/about");
|
|
106
|
+
navigate({ name: "blog.show", params: { slug: "hello" } });
|
|
107
|
+
|
|
108
|
+
// With options
|
|
109
|
+
navigate("/home", { replace: true, scroll: true });
|
|
110
|
+
|
|
111
|
+
// Link handler
|
|
112
|
+
const handleClick = link("/about");
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Advanced Features
|
|
116
|
+
|
|
117
|
+
### Route Guards
|
|
118
|
+
|
|
119
|
+
Protect routes with guards and middleware:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import { createConditionalGuard, createParamGuard } from "olova-router/guards";
|
|
123
|
+
|
|
124
|
+
const routes = [
|
|
125
|
+
{
|
|
126
|
+
path: "/admin",
|
|
127
|
+
component: AdminPanel,
|
|
128
|
+
beforeEnter: createConditionalGuard(
|
|
129
|
+
() => isUserAdmin(),
|
|
130
|
+
"/unauthorized"
|
|
131
|
+
)
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
path: "/user/:id",
|
|
135
|
+
component: UserProfile,
|
|
136
|
+
beforeEnter: createParamGuard(
|
|
137
|
+
"id",
|
|
138
|
+
(id) => /^\d+$/.test(id),
|
|
139
|
+
"/not-found"
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
];
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Middleware Chain
|
|
146
|
+
|
|
147
|
+
Compose multiple middleware for route transitions:
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import {
|
|
151
|
+
createMiddlewareChain,
|
|
152
|
+
createAuthMiddleware,
|
|
153
|
+
createAnalyticsMiddleware,
|
|
154
|
+
createScrollMiddleware
|
|
155
|
+
} from "olova-router/middleware";
|
|
156
|
+
|
|
157
|
+
const middleware = createMiddlewareChain()
|
|
158
|
+
.use(createAuthMiddleware(() => isAuthenticated(), "/login"))
|
|
159
|
+
.use(createAnalyticsMiddleware((path) => trackPageView(path)))
|
|
160
|
+
.use(createScrollMiddleware(true));
|
|
161
|
+
|
|
162
|
+
// Execute middleware
|
|
163
|
+
const result = await middleware.execute(context);
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Lazy Loading
|
|
167
|
+
|
|
168
|
+
Code split components with lazy loading:
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
import { lazyComponent, lazyComponentWithFallback } from "olova-router/lazy";
|
|
172
|
+
|
|
173
|
+
const routes = [
|
|
174
|
+
{
|
|
175
|
+
path: "/dashboard",
|
|
176
|
+
component: lazyComponent(() => import("./Dashboard.olova"))
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
path: "/settings",
|
|
180
|
+
component: lazyComponentWithFallback(
|
|
181
|
+
() => import("./Settings.olova"),
|
|
182
|
+
LoadingSpinner,
|
|
183
|
+
ErrorFallback
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
];
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Breadcrumbs
|
|
190
|
+
|
|
191
|
+
Generate breadcrumbs from route location:
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
import { generateBreadcrumbs, generateBreadcrumbsFromMatches } from "olova-router/breadcrumbs";
|
|
195
|
+
|
|
196
|
+
const route = useRoute();
|
|
197
|
+
const breadcrumbs = generateBreadcrumbs(route, {
|
|
198
|
+
home: "Home",
|
|
199
|
+
blog: "Blog",
|
|
200
|
+
settings: "Settings"
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Or from route matches
|
|
204
|
+
const breadcrumbs = generateBreadcrumbsFromMatches(route.matches);
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Route History
|
|
208
|
+
|
|
209
|
+
Track navigation history:
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
import { createRouterHistory } from "olova-router/history";
|
|
213
|
+
|
|
214
|
+
const history = createRouterHistory(50); // max 50 entries
|
|
215
|
+
|
|
216
|
+
history.push("/home");
|
|
217
|
+
history.push("/about");
|
|
218
|
+
|
|
219
|
+
const prev = history.back(); // { path: "/home", timestamp: ... }
|
|
220
|
+
const next = history.forward(); // { path: "/about", timestamp: ... }
|
|
221
|
+
|
|
222
|
+
const entries = history.getHistory();
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Route Configuration
|
|
226
|
+
|
|
227
|
+
### Basic Route
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
{
|
|
231
|
+
path: "/about",
|
|
232
|
+
component: About,
|
|
233
|
+
name: "about"
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Dynamic Route
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
{
|
|
241
|
+
path: "/blog/:slug",
|
|
242
|
+
component: BlogPost,
|
|
243
|
+
name: "blog.post"
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Wildcard Route
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
{
|
|
251
|
+
path: "/docs/*",
|
|
252
|
+
component: DocsLayout,
|
|
253
|
+
name: "docs"
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Nested Routes
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
{
|
|
261
|
+
path: "/dashboard",
|
|
262
|
+
component: DashboardLayout,
|
|
263
|
+
children: [
|
|
264
|
+
{ index: true, component: DashboardHome },
|
|
265
|
+
{ path: "settings", component: DashboardSettings },
|
|
266
|
+
{ path: "profile", component: DashboardProfile }
|
|
91
267
|
]
|
|
92
|
-
|
|
268
|
+
}
|
|
269
|
+
```
|
|
93
270
|
|
|
94
|
-
|
|
271
|
+
### Route with Guards
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
{
|
|
275
|
+
path: "/admin",
|
|
276
|
+
component: AdminPanel,
|
|
277
|
+
beforeEnter: async (context) => {
|
|
278
|
+
if (!isAdmin()) {
|
|
279
|
+
return "/unauthorized";
|
|
280
|
+
}
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
95
284
|
```
|
|
96
285
|
|
|
97
|
-
|
|
286
|
+
### Route with Props
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
{
|
|
290
|
+
path: "/user/:id",
|
|
291
|
+
component: UserProfile,
|
|
292
|
+
props: (context) => ({
|
|
293
|
+
userId: context.params.id,
|
|
294
|
+
isAdmin: context.query.admin === "true"
|
|
295
|
+
})
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Route Redirect
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
{
|
|
303
|
+
path: "/old-path",
|
|
304
|
+
redirect: "/new-path"
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Link Component
|
|
98
309
|
|
|
99
310
|
```olova
|
|
100
|
-
<
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
311
|
+
<script>
|
|
312
|
+
import { Link } from "olova-router";
|
|
313
|
+
</script>
|
|
314
|
+
|
|
315
|
+
<!-- Simple href -->
|
|
316
|
+
<Link href="/about">About</Link>
|
|
317
|
+
|
|
318
|
+
<!-- Named route -->
|
|
319
|
+
<Link name="blog.post" params={{ slug: "hello" }}>Read Post</Link>
|
|
320
|
+
|
|
321
|
+
<!-- With query and hash -->
|
|
322
|
+
<Link
|
|
323
|
+
name="search"
|
|
324
|
+
query={{ q: "olova" }}
|
|
325
|
+
hash="results"
|
|
326
|
+
>
|
|
327
|
+
Search
|
|
328
|
+
</Link>
|
|
329
|
+
|
|
330
|
+
<!-- Active styling -->
|
|
331
|
+
<Link
|
|
332
|
+
href="/about"
|
|
333
|
+
class="nav-link"
|
|
334
|
+
activeClass="active"
|
|
335
|
+
inactiveClass="inactive"
|
|
336
|
+
exact
|
|
337
|
+
>
|
|
338
|
+
About
|
|
339
|
+
</Link>
|
|
340
|
+
|
|
341
|
+
<!-- Custom attributes -->
|
|
342
|
+
<Link
|
|
343
|
+
href="/external"
|
|
344
|
+
target="_blank"
|
|
345
|
+
rel="noopener noreferrer"
|
|
346
|
+
title="External Link"
|
|
347
|
+
>
|
|
348
|
+
External
|
|
349
|
+
</Link>
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
## Router Props
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
type RouterProps = {
|
|
356
|
+
routes: RouterRoutes;
|
|
357
|
+
mode?: "auto" | "history" | "hash"; // default: "auto"
|
|
358
|
+
base?: string; // default: "/"
|
|
359
|
+
scroll?: boolean; // default: true
|
|
360
|
+
};
|
|
104
361
|
```
|
|
362
|
+
|
|
363
|
+
## TypeScript Support
|
|
364
|
+
|
|
365
|
+
Full type safety with comprehensive types:
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
import type {
|
|
369
|
+
RouteLocation,
|
|
370
|
+
RouteParams,
|
|
371
|
+
RouteQuery,
|
|
372
|
+
RouteTarget,
|
|
373
|
+
RouterApi,
|
|
374
|
+
RouteGuardContext
|
|
375
|
+
} from "olova-router";
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
## Vite Plugin
|
|
379
|
+
|
|
380
|
+
Auto-generate routes from file structure:
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
// vite.config.ts
|
|
384
|
+
import { olovaRouter } from "olova-router/vite";
|
|
385
|
+
|
|
386
|
+
export default {
|
|
387
|
+
plugins: [olovaRouter()]
|
|
388
|
+
};
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
## Best Practices
|
|
392
|
+
|
|
393
|
+
1. **Use Named Routes** - More maintainable than hardcoded paths
|
|
394
|
+
2. **Lazy Load Heavy Components** - Improve initial load time
|
|
395
|
+
3. **Protect Sensitive Routes** - Use guards for auth/permissions
|
|
396
|
+
4. **Handle 404s** - Always include a wildcard route
|
|
397
|
+
5. **Scroll Management** - Enable scroll reset on navigation
|
|
398
|
+
6. **Type Your Routes** - Leverage TypeScript for safety
|
|
399
|
+
|
|
400
|
+
## Examples
|
|
401
|
+
|
|
402
|
+
### Authentication Guard
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
import { createConditionalGuard } from "olova-router/guards";
|
|
406
|
+
|
|
407
|
+
const authGuard = createConditionalGuard(
|
|
408
|
+
async (context) => {
|
|
409
|
+
const token = localStorage.getItem("auth_token");
|
|
410
|
+
if (!token) return false;
|
|
411
|
+
|
|
412
|
+
const valid = await validateToken(token);
|
|
413
|
+
return valid;
|
|
414
|
+
},
|
|
415
|
+
"/login"
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
const routes = [
|
|
419
|
+
{
|
|
420
|
+
path: "/dashboard",
|
|
421
|
+
component: Dashboard,
|
|
422
|
+
beforeEnter: authGuard
|
|
423
|
+
}
|
|
424
|
+
];
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### Analytics Tracking
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
import { createAnalyticsMiddleware } from "olova-router/middleware";
|
|
431
|
+
|
|
432
|
+
const analyticsMiddleware = createAnalyticsMiddleware((path) => {
|
|
433
|
+
gtag.pageview({
|
|
434
|
+
page_path: path,
|
|
435
|
+
page_title: document.title
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### Composite Guards
|
|
441
|
+
|
|
442
|
+
```typescript
|
|
443
|
+
import { createCompositeGuard } from "olova-router/guards";
|
|
444
|
+
|
|
445
|
+
const protectedGuard = createCompositeGuard([
|
|
446
|
+
createAuthMiddleware(() => isAuthenticated()),
|
|
447
|
+
createRoleMiddleware(() => userRole(), ["admin", "moderator"])
|
|
448
|
+
]);
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
## License
|
|
452
|
+
|
|
453
|
+
MIT
|