olova-router 1.0.15 → 1.0.17

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 CHANGED
@@ -1,422 +1,104 @@
1
- # Olova Router
2
-
3
- <p align="center">
4
- <img src="https://img.shields.io/npm/v/olova-router?style=flat-square&color=blue" alt="npm version" />
5
- <img src="https://img.shields.io/npm/dm/olova-router?style=flat-square&color=green" alt="downloads" />
6
- <img src="https://img.shields.io/npm/l/olova-router?style=flat-square" alt="license" />
7
- </p>
8
-
9
- **Next.js-style file-based routing for React + Vite applications** — with nested layouts, dynamic routes, and zero configuration.
10
-
11
- ---
12
-
13
- ## ✨ Features
14
-
15
- | Feature | Description |
16
- | ------------------------- | ---------------------------------------------------------- |
17
- | 📁 **File-based routing** | Create routes by adding folders with `index.tsx` |
18
- | 🏗️ **Nested layouts** | Use `_layout.tsx` with `<Outlet />` for persistent layouts |
19
- | 🎯 **Dynamic routes** | Use `$id` or `[id]` syntax for dynamic segments |
20
- | 🌟 **Catch-all routes** | Use `$` or `[...slug]` for wildcard matching |
21
- | 📦 **Route groups** | Use `(group)` folders to organize without affecting URLs |
22
- | 🔍 **Search params** | Built-in `useSearchParams` hook with merge support |
23
- | 🚫 **Custom 404 pages** | Global and route-specific 404 error pages |
24
- | 🔄 **Hot reload** | Auto-regenerates routes when files change |
25
- | 📝 **Type-safe** | Full TypeScript support with typed Link paths |
26
-
27
- ---
28
-
29
- ## 📦 Installation
30
-
31
- ```bash
32
- npm install olova-router
33
- ```
34
-
35
- ---
36
-
37
- ## 🚀 Quick Start
38
-
39
- ### 1. Add the Vite Plugin
40
-
41
- ```ts
42
- // vite.config.ts
43
- import { defineConfig } from "vite";
44
- import react from "@vitejs/plugin-react";
45
- import { olovaRouter } from "olova-router";
46
-
47
- export default defineConfig({
48
- plugins: [react(), olovaRouter()],
49
- });
50
- ```
51
-
52
- ### 2. Create App Entry
53
-
54
- ```tsx
55
- // src/main.tsx
56
- import { StrictMode } from "react";
57
- import { createRoot } from "react-dom/client";
58
- import { routes, layouts, notFoundPages, OlovaRouter } from "./route.tree";
59
-
60
- createRoot(document.getElementById("root")!).render(
61
- <StrictMode>
62
- <OlovaRouter
63
- routes={routes}
64
- layouts={layouts}
65
- notFoundPages={notFoundPages}
66
- />
67
- </StrictMode>
68
- );
69
- ```
70
-
71
- ### 3. Create Your Routes
72
-
73
- ```
74
- src/
75
- ├── _layout.tsx → Root layout (persistent nav/header)
76
- ├── App.tsx → / (home page)
77
- ├── 404.tsx → Global 404 page
78
- ├── about/
79
- │ └── index.tsx → /about
80
- ├── users/
81
- │ ├── index.tsx → /users
82
- │ └── $id/
83
- │ └── index.tsx → /users/:id (dynamic)
84
- ├── blog/
85
- │ └── $/
86
- │ └── index.tsx → /blog/* (catch-all)
87
- └── (auth)/ → Route group (not in URL)
88
- ├── login/
89
- │ └── index.tsx → /login
90
- └── register/
91
- └── index.tsx → /register
92
- ```
93
-
94
- ---
95
-
96
- ## 🏗️ Nested Layouts
97
-
98
- Create persistent layouts that don't re-mount on navigation — headers, sidebars, and navs stay stable while only page content changes.
99
-
100
- ### Create a Layout
101
-
102
- ```tsx
103
- // src/_layout.tsx
104
- import { Link, Outlet } from "./route.tree";
105
-
106
- export default function RootLayout() {
107
- return (
108
- <div>
109
- <nav>
110
- <Link href="/">Home</Link>
111
- <Link href="/about">About</Link>
112
- <Link href="/users">Users</Link>
113
- </nav>
114
-
115
- <main>
116
- <Outlet /> {/* Page content renders here */}
117
- </main>
118
- </div>
119
- );
120
- }
121
- ```
122
-
123
- ### How It Works
124
-
125
- - `_layout.tsx` in any folder creates a layout for that route scope
126
- - `<Outlet />` renders the matched child route
127
- - Layouts stay mounted while navigating between child routes
128
- - No more flickering navbars!
129
-
130
- ---
131
-
132
- ## 🧭 Navigation
133
-
134
- ### Using Link Component
135
-
136
- ```tsx
137
- import { Link } from "./route.tree";
138
-
139
- function MyComponent() {
140
- return (
141
- <div>
142
- <Link href="/">Home</Link>
143
- <Link href="/about">About</Link>
144
- <Link href="/users/123">User 123</Link> {/* Dynamic paths work! */}
145
- </div>
146
- );
147
- }
148
- ```
149
-
150
- ### Programmatic Navigation
151
-
152
- ```tsx
153
- import { useRouter } from "./route.tree";
154
-
155
- function MyComponent() {
156
- const { navigate, currentPath } = useRouter();
157
-
158
- return (
159
- <div>
160
- <p>Current path: {currentPath}</p>
161
- <button onClick={() => navigate("/users/456")}>Go to User 456</button>
162
- </div>
163
- );
164
- }
165
- ```
166
-
167
- ---
168
-
169
- ## 🎯 Dynamic Routes
170
-
171
- Use `$param` or `[param]` folder names for dynamic segments.
172
-
173
- ### Folder Structure
174
-
175
- ```
176
- src/users/$id/index.tsx → /users/:id
177
- src/posts/[postId]/index.tsx → /posts/:postId
178
- ```
179
-
180
- ### Access Params
181
-
182
- ```tsx
183
- // src/users/$id/index.tsx
184
- import { useParams } from "./route.tree";
185
-
186
- export default function UserPage() {
187
- const { id } = useParams<{ id: string }>();
188
-
189
- return <div>User ID: {id}</div>;
190
- }
191
- ```
192
-
193
- ---
194
-
195
- ## 🌟 Catch-All Routes
196
-
197
- Use `$` or `[...slug]` for catch-all segments that match multiple path levels.
198
-
199
- ### Folder Structure
200
-
201
- ```
202
- src/blog/$/index.tsx → /blog/*
203
- src/docs/[...slug]/index.tsx → /docs/*
204
- ```
205
-
206
- ### Access Slug
207
-
208
- ```tsx
209
- // src/blog/$/index.tsx
210
- import { useParams } from "./route.tree";
211
-
212
- export default function BlogPost() {
213
- const { slug } = useParams<{ slug: string }>();
214
-
215
- // /blog/2024/hello-world → slug = "2024/hello-world"
216
- return <div>Blog path: {slug}</div>;
217
- }
218
- ```
219
-
220
- ---
221
-
222
- ## 🔍 Search Params
223
-
224
- Read and update URL query parameters.
225
-
226
- ```tsx
227
- import { useSearchParams } from "./route.tree";
228
-
229
- function SearchPage() {
230
- const [searchParams, setSearchParams] = useSearchParams();
231
-
232
- // Read params - /search?q=react&page=2
233
- const query = searchParams.q; // "react"
234
- const page = searchParams.page; // "2"
235
-
236
- // Update params (merge with existing)
237
- setSearchParams({ page: "3" }, { merge: true });
238
-
239
- // Replace all params
240
- setSearchParams({ q: "vue", page: "1" });
241
-
242
- // Remove a param
243
- setSearchParams({ page: null }, { merge: true });
244
-
245
- // Use replaceState instead of pushState
246
- setSearchParams({ page: "5" }, { replace: true });
247
- }
248
- ```
249
-
250
- ---
251
-
252
- ## 📦 Route Groups
253
-
254
- Organize routes without affecting the URL using `(parentheses)`.
255
-
256
- ### Folder Structure
257
-
258
- ```
259
- src/
260
- ├── (auth)/
261
- │ ├── login/index.tsx → /login
262
- │ └── register/index.tsx → /register
263
- ├── (marketing)/
264
- │ ├── pricing/index.tsx → /pricing
265
- │ └── features/index.tsx → /features
266
- ```
267
-
268
- The group folder name `(auth)` is excluded from the URL.
269
-
270
- ---
271
-
272
- ## 🚫 Custom 404 Pages
273
-
274
- ### Global 404
275
-
276
- ```tsx
277
- // src/404.tsx
278
- import { Link, useRouter } from "./route.tree";
279
-
280
- export default function NotFound() {
281
- const { currentPath } = useRouter();
282
-
283
- return (
284
- <div>
285
- <h1>404 - Page Not Found</h1>
286
- <p>Path "{currentPath}" doesn't exist.</p>
287
- <Link href="/">Go Home</Link>
288
- </div>
289
- );
290
- }
291
- ```
292
-
293
- ### Route-Specific 404
294
-
295
- ```tsx
296
- // src/dashboard/404.tsx
297
- // Matches: /dashboard/anything-that-doesnt-exist
298
-
299
- export default function DashboardNotFound() {
300
- return <div>Dashboard page not found</div>;
301
- }
302
- ```
303
-
304
- The most specific 404 page is used based on path prefix.
305
-
306
- ---
307
-
308
- ## 📋 Route Pattern Reference
309
-
310
- | File Path | URL Pattern |
311
- | ------------------------------ | --------------------- |
312
- | `src/App.tsx` | `/` |
313
- | `src/about/index.tsx` | `/about` |
314
- | `src/users/$id/index.tsx` | `/users/:id` |
315
- | `src/users/[id]/index.tsx` | `/users/:id` |
316
- | `src/blog/$/index.tsx` | `/blog/*` |
317
- | `src/blog/[...slug]/index.tsx` | `/blog/*` |
318
- | `src/(auth)/login/index.tsx` | `/login` |
319
- | `src/users/_layout.tsx` | Layout for `/users/*` |
320
- | `src/404.tsx` | Global 404 |
321
- | `src/users/404.tsx` | 404 for `/users/*` |
322
-
323
- ---
324
-
325
- ## ⚙️ API Reference
326
-
327
- ### Plugin Options
328
-
329
- ```ts
330
- olovaRouter({
331
- rootDir: "src", // Directory to scan (default: 'src')
332
- extensions: [".tsx", ".ts"], // File extensions (default: ['.tsx', '.ts'])
333
- });
334
- ```
335
-
336
- ### Hooks
337
-
338
- | Hook | Returns | Description |
339
- | ------------------- | ------------------------------------------------------------------ | ----------------------- |
340
- | `useRouter()` | `{ currentPath, params, navigate, searchParams, setSearchParams }` | Full router access |
341
- | `useParams<T>()` | `T` | Route parameters object |
342
- | `useSearchParams()` | `[params, setParams]` | Query string params |
343
-
344
- ### Components
345
-
346
- | Component | Props | Description |
347
- | ------------- | --------------------------------------------------- | ---------------------------- |
348
- | `OlovaRouter` | `routes`, `layouts?`, `notFoundPages?`, `notFound?` | Main router |
349
- | `Link` | `href`, `children`, `className?` | Type-safe navigation |
350
- | `Outlet` | — | Renders nested route content |
351
-
352
- ### Types
353
-
354
- ```ts
355
- import type {
356
- RoutePaths, // Union of all route paths
357
- SearchParams, // Search params object type
358
- SetSearchParamsOptions,
359
- LayoutRoute,
360
- NotFoundPageConfig,
361
- } from "./route.tree";
362
- ```
363
-
364
- ---
365
-
366
- ## 🔄 Auto-Generated Files
367
-
368
- The plugin automatically generates `src/route.tree.ts` containing:
369
-
370
- - All route imports and configurations
371
- - Layout configurations
372
- - 404 page configurations
373
- - Type-safe `Link` component
374
- - All hooks re-exported
375
-
376
- **Do not edit this file manually** — it's regenerated when routes change.
377
-
378
- ---
379
-
380
- ## 💡 Tips
381
-
382
- ### Path Aliases
383
-
384
- Add a path alias for cleaner imports:
385
-
386
- ```json
387
- // tsconfig.json
388
- {
389
- "compilerOptions": {
390
- "paths": {
391
- "@/*": ["./src/*"]
392
- }
393
- }
394
- }
395
- ```
396
-
397
- ```ts
398
- // vite.config.ts
399
- import path from "path";
400
-
401
- export default defineConfig({
402
- resolve: {
403
- alias: {
404
- "@": path.resolve(__dirname, "./src"),
405
- },
406
- },
407
- });
408
- ```
409
-
410
- Then import from `@/route.tree` anywhere.
411
-
412
- ---
413
-
414
- ## 📄 License
415
-
416
- MIT © 2026
417
-
418
- ---
419
-
420
- <p align="center">
421
- Made with ❤️ for the React community
422
- </p>
1
+ # olova-router
2
+
3
+ Standalone router package for Olova.
4
+
5
+ ## Main API
6
+
7
+ ```olova
8
+ <script>
9
+ import { BrowserRouter, Link } from "olova-router";
10
+ import Home from "./Home.olova";
11
+ import About from "./About.olova";
12
+ import Blog from "./Blog.olova";
13
+
14
+ const routes = [
15
+ {
16
+ path: "/",
17
+ component: Home,
18
+ name: "home"
19
+ },
20
+ {
21
+ path: "/about",
22
+ component: About,
23
+ name: "about"
24
+ },
25
+ {
26
+ path: "/blog/:blogName",
27
+ component: Blog,
28
+ name: "blog.show"
29
+ }
30
+ ]
31
+ </script>
32
+
33
+ <nav>
34
+ <Link href="/">Home</Link>
35
+ <Link name="about">About</Link>
36
+ <Link
37
+ name="blog.show"
38
+ params={{ blogName: "colors" }}
39
+ activeClass="text-emerald-300"
40
+ >
41
+ Colors
42
+ </Link>
43
+ </nav>
44
+
45
+ <BrowserRouter routes={routes} />
46
+ ```
47
+
48
+ ## Exports
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
77
+
78
+ ```olova
79
+ <script>
80
+ import { BrowserRouter, Outlet } from "olova-router";
81
+
82
+ const routes = [
83
+ {
84
+ path: "/dashboard",
85
+ component: DashboardLayout,
86
+ children: [
87
+ { index: true, component: DashboardHome },
88
+ { path: "settings", component: DashboardSettings }
89
+ ]
90
+ }
91
+ ]
92
+ </script>
93
+
94
+ <BrowserRouter routes={routes} />
95
+ ```
96
+
97
+ Then inside `DashboardLayout.olova`:
98
+
99
+ ```olova
100
+ <section>
101
+ <h1>Dashboard</h1>
102
+ <Outlet />
103
+ </section>
104
+ ```
package/dist/index.d.ts CHANGED
@@ -1,9 +1,117 @@
1
- import { O as OlovaRouterOptions } from './router-Dfr1QOwE.js';
2
- export { L as LayoutRoute, M as Metadata, N as NotFoundEntry, h as NotFoundPageConfig, b as OlovaRouter, g as Outlet, k as OutletContextType, a as RouteConfig, R as RouteEntry, j as RouterContextType, S as SearchParams, i as SetSearchParamsOptions, f as createLink, c as useParams, e as usePathname, u as useRouter, d as useSearchParams } from './router-Dfr1QOwE.js';
3
- import { PluginOption } from 'vite';
4
- import 'react';
5
- import 'react/jsx-runtime';
1
+ import { OlovaComponent, OlovaProps } from 'olova/runtime';
6
2
 
7
- declare function olovaRouter(options?: OlovaRouterOptions): PluginOption[];
3
+ declare const _default: OlovaComponent;
8
4
 
9
- export { OlovaRouterOptions, olovaRouter as default, olovaRouter };
5
+ type RouterMode = "auto" | "history" | "hash";
6
+ type ResolvedMode = "history" | "hash";
7
+ type Primitive = string | number | boolean;
8
+ type RouteParams = Record<string, string>;
9
+ type RouteQueryValue = string | string[];
10
+ type RouteQuery = Record<string, RouteQueryValue>;
11
+ type RouteQueryInputValue = Primitive | Primitive[] | null | undefined;
12
+ type RouteQueryInput = Record<string, RouteQueryInputValue>;
13
+ type RouteTarget = string | {
14
+ name?: string;
15
+ path?: string;
16
+ params?: Record<string, Primitive>;
17
+ query?: RouteQueryInput;
18
+ hash?: string;
19
+ };
20
+ type NavigateOptions = {
21
+ replace?: boolean;
22
+ scroll?: boolean;
23
+ };
24
+ type MatchOptions = {
25
+ exact?: boolean;
26
+ };
27
+ type RouteMatchInfo = {
28
+ name?: string;
29
+ path: string;
30
+ meta?: unknown;
31
+ };
32
+ type RouteLocation = {
33
+ name?: string;
34
+ path: string;
35
+ fullPath: string;
36
+ href: string;
37
+ params: RouteParams;
38
+ query: RouteQuery;
39
+ hash: string;
40
+ meta?: unknown;
41
+ matchedPath: string;
42
+ matches: RouteMatchInfo[];
43
+ };
44
+ type RouteGuardContext = {
45
+ to: RouteLocation;
46
+ from: RouteLocation | null;
47
+ params: RouteParams;
48
+ query: RouteQuery;
49
+ };
50
+ type RouteGuardResult = boolean | string | RouteTarget | void;
51
+ type RouteGuard = (context: RouteGuardContext) => RouteGuardResult;
52
+ type RouteRedirect = RouteTarget | ((context: RouteGuardContext) => RouteTarget | null | undefined);
53
+ type RoutePropsFactory = OlovaProps | ((context: RouteGuardContext) => OlovaProps);
54
+ type RouteObject = {
55
+ path?: string;
56
+ index?: boolean;
57
+ component?: OlovaComponent;
58
+ redirect?: RouteRedirect;
59
+ props?: RoutePropsFactory;
60
+ beforeEnter?: RouteGuard;
61
+ name?: string;
62
+ meta?: unknown;
63
+ children?: RouteObject[];
64
+ };
65
+ type RouteDefinition = OlovaComponent | (Omit<RouteObject, "path" | "index"> & {
66
+ children?: RouteObject[];
67
+ });
68
+ type RouterRoutes = Record<string, RouteDefinition> | RouteObject[];
69
+ type RouterProps = {
70
+ routes: RouterRoutes;
71
+ mode?: RouterMode;
72
+ base?: string;
73
+ scroll?: boolean;
74
+ };
75
+ type RouteResolution = {
76
+ mode: ResolvedMode;
77
+ location: RouteLocation;
78
+ component: OlovaComponent;
79
+ props: OlovaProps;
80
+ };
81
+ type RouterApi = {
82
+ current: () => RouteResolution;
83
+ readonly mode: ResolvedMode;
84
+ readonly location: RouteLocation;
85
+ readonly path: string;
86
+ readonly fullPath: string;
87
+ readonly params: RouteParams;
88
+ readonly query: RouteQuery;
89
+ readonly hash: string;
90
+ readonly matchedPath: string;
91
+ readonly matches: RouteMatchInfo[];
92
+ push: (to: RouteTarget, options?: NavigateOptions) => void;
93
+ navigate: (to: RouteTarget, options?: NavigateOptions) => void;
94
+ replace: (to: RouteTarget, options?: Omit<NavigateOptions, "replace">) => void;
95
+ back: () => void;
96
+ forward: () => void;
97
+ refresh: () => void;
98
+ resolve: (to: RouteTarget) => string;
99
+ href: (to: RouteTarget) => string;
100
+ isActive: (to: RouteTarget, options?: MatchOptions) => boolean;
101
+ };
102
+
103
+ declare function matchPath(pattern: string, path: string): RouteParams | null;
104
+ declare function navigate(to: RouteTarget, options?: NavigateOptions): void;
105
+ declare function link(toOrEvent?: RouteTarget | Event, options?: NavigateOptions): ((event?: Event) => void) | void;
106
+ declare function useRouter(): RouterApi;
107
+ declare function useRoute(): RouteLocation;
108
+ declare function useParams(): RouteParams;
109
+ declare function useQuery(): RouteQuery;
110
+ declare function resolveHref(to: RouteTarget): string;
111
+ declare function isActiveHref(to: RouteTarget, options?: MatchOptions): boolean;
112
+ declare const Outlet: OlovaComponent;
113
+ declare const Router: OlovaComponent;
114
+ declare const BrowserRouter: OlovaComponent;
115
+ declare const HashRouter: OlovaComponent;
116
+
117
+ export { BrowserRouter, HashRouter, _default as Link, type MatchOptions, type NavigateOptions, Outlet, type RouteDefinition, type RouteGuardContext, type RouteLocation, type RouteMatchInfo, type RouteObject, type RouteParams, type RouteQuery, type RouteQueryInput, type RouteTarget, type RouterMode, type RouterProps, type RouterRoutes, Router as default, isActiveHref, link, matchPath, navigate, resolveHref, useParams, useQuery, useRoute, useRouter };