@epicabdou/linkr 0.0.2 → 0.0.4
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 +171 -38
- package/dist/index.d.ts +48 -1
- package/dist/index.js +119 -27
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# linkr
|
|
1
|
+
# @epicabdou/linkr
|
|
2
2
|
|
|
3
|
-
File-based routing for **React Router v7** in Vite + React SPA apps. Generates a route tree from a `src/pages/` directory using `import.meta.glob`
|
|
3
|
+
File-based routing for **React Router v7** in Vite + React SPA apps. Generates a route tree from a `src/pages/` directory using `import.meta.glob` — the consumer passes the glob result; the library does not call it.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -8,10 +8,12 @@ File-based routing for **React Router v7** in Vite + React SPA apps. Generates a
|
|
|
8
8
|
pnpm add @epicabdou/linkr react react-dom react-router
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Peer
|
|
11
|
+
**Peer dependencies:** `react` (≥18), `react-dom` (≥18), `react-router` (≥7).
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
15
|
+
### Basic setup
|
|
16
|
+
|
|
15
17
|
```tsx
|
|
16
18
|
import { createRoutes } from "@epicabdou/linkr";
|
|
17
19
|
import { createBrowserRouter, RouterProvider } from "react-router";
|
|
@@ -23,36 +25,140 @@ const router = createBrowserRouter(routes);
|
|
|
23
25
|
// Then render <RouterProvider router={router} />
|
|
24
26
|
```
|
|
25
27
|
|
|
26
|
-
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
28
|
+
### One-line app bootstrap
|
|
29
|
+
|
|
30
|
+
Use `createRootWithLinkr` to create the root, build routes, and render in one call:
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import { createRootWithLinkr } from "@epicabdou/linkr";
|
|
34
|
+
|
|
35
|
+
createRootWithLinkr(document.getElementById("root")!, {
|
|
36
|
+
pagesGlob: import.meta.glob("./pages/**/*.tsx"),
|
|
37
|
+
pagesDir: "pages",
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
This uses `createBrowserRouter`, `StrictMode`, and `RouterProvider` under the hood. No need to import React Router or call `createRoutes` yourself.
|
|
42
|
+
|
|
43
|
+
### Using `<LinkrApp>` (with custom providers)
|
|
44
|
+
|
|
45
|
+
When you need to wrap the app with your own providers, use the `<LinkrApp>` component and pass the same options as `createRoutes`:
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import { LinkrApp } from "@epicabdou/linkr";
|
|
49
|
+
|
|
50
|
+
const pages = import.meta.glob("./pages/**/*.tsx");
|
|
51
|
+
|
|
52
|
+
function App() {
|
|
53
|
+
return (
|
|
54
|
+
<YourProvider>
|
|
55
|
+
<LinkrApp
|
|
56
|
+
pagesGlob={pages}
|
|
57
|
+
pagesDir="pages"
|
|
58
|
+
defaultRedirectTo="/login"
|
|
59
|
+
/>
|
|
60
|
+
</YourProvider>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
createRoot(document.getElementById("root")!).render(<App />);
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Options are read once on mount; the router is created with `useMemo`.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## `createRoutes` options
|
|
72
|
+
|
|
73
|
+
| Option | Type | Default | Description |
|
|
74
|
+
|--------|------|---------|-------------|
|
|
75
|
+
| **`pagesGlob`** | `Record<string, () => Promise<unknown>>` | *required* | Result of `import.meta.glob("...")` from your app. |
|
|
76
|
+
| **`pagesDir`** | `string` | `"src/pages"` | Base path used to normalize file paths. Must match the prefix you strip from glob keys (e.g. `"pages"` when glob is `./pages/**/*.tsx` from `src/`). |
|
|
77
|
+
| **`layoutFileName`** | `string` | `"_layout"` | Filename (without extension) for layout routes. |
|
|
78
|
+
| **`notFoundFileName`** | `string` | `"404"` | Filename for the catch-all not-found route. |
|
|
79
|
+
| **`routeExtensions`** | `string[]` | `[".tsx", ".ts", ".jsx", ".js"]` | Extensions that define route files. |
|
|
80
|
+
| **`defaultRedirectTo`** | `string` | `"/"` | Default redirect path when a route's `protect` check fails and no `redirectTo` is set. |
|
|
81
|
+
| **`layoutsGlob`** | `Record<string, () => Promise<unknown>>` | — | When set with **`layoutMap`**, layouts are loaded from this glob and `_layout` files in pages are ignored. Example: `import.meta.glob("./layouts/**/*.tsx")`. |
|
|
82
|
+
| **`layoutsDir`** | `string` | `"src/layouts"` | Base path for layout files when using **`layoutsGlob`**. |
|
|
83
|
+
| **`layoutMap`** | `Record<string, string>` | — | Path prefix → layout name. Use `""` or `"/"` for root; segment name (e.g. `blog`) for nested. Requires **`layoutsGlob`**. |
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## File conventions
|
|
88
|
+
|
|
89
|
+
| File / pattern | Route |
|
|
90
|
+
|----------------|-------|
|
|
91
|
+
| `index.tsx` | `/` (index route) |
|
|
92
|
+
| `about.tsx` | `/about` |
|
|
93
|
+
| `blog/index.tsx` | `/blog` (index under blog) |
|
|
94
|
+
| `blog/[id].tsx` | `/blog/:id` |
|
|
95
|
+
| `docs/[...slug].tsx` | `/docs/*` (splat) |
|
|
96
|
+
| `404.tsx` | `path: "*"` (catch-all, last) |
|
|
97
|
+
| `_layout.tsx` (in any folder) | Layout route; children render in `<Outlet />`. Ignored when using **layoutsGlob** + **layoutMap**. |
|
|
98
|
+
|
|
99
|
+
- **Nested routes:** folder nesting = nested routes.
|
|
100
|
+
- **Layouts:** Either put `_layout.tsx` in a folder (folder-based), or use a **separate layouts folder** with **layoutsGlob** and **layoutMap** so layouts are reusable components.
|
|
101
|
+
- **Sort order:** among siblings — static segments first, then dynamic (`:id`), then splat (`*`).
|
|
102
|
+
- **Lazy loading:** every route uses React Router's `lazy()` for code splitting.
|
|
103
|
+
|
|
104
|
+
---
|
|
49
105
|
|
|
50
106
|
## Page module exports
|
|
51
107
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
108
|
+
| Export | Description |
|
|
109
|
+
|--------|-------------|
|
|
110
|
+
| **`default`** | React component used as the route element (required). |
|
|
111
|
+
| **`ErrorBoundary`** | Optional; used as the route's `errorElement`. |
|
|
112
|
+
| **`handle`** | Optional; attached to the route's `handle`. |
|
|
113
|
+
| **`protect`** | Optional; runs before rendering. If it returns `false`, the user is redirected. See [Route protection](#route-protection). |
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Layouts in a separate folder
|
|
118
|
+
|
|
119
|
+
Keep layouts as reusable components in a dedicated folder and wire them by path with **layoutMap** (no `_layout.tsx` in pages).
|
|
120
|
+
|
|
121
|
+
**Example layouts:**
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
// src/layouts/Root.tsx
|
|
125
|
+
import { Link, Outlet } from "react-router";
|
|
126
|
+
export default function RootLayout() {
|
|
127
|
+
return (
|
|
128
|
+
<div>
|
|
129
|
+
<nav><Link to="/">Home</Link> <Link to="/blog">Blog</Link></nav>
|
|
130
|
+
<Outlet />
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
// src/layouts/Blog.tsx
|
|
138
|
+
import { Outlet } from "react-router";
|
|
139
|
+
export default function BlogLayout() {
|
|
140
|
+
return <div><h2>Blog</h2><Outlet /></div>;
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Wire in router setup:**
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
const pages = import.meta.glob("./pages/**/*.tsx");
|
|
148
|
+
const layouts = import.meta.glob("./layouts/**/*.tsx");
|
|
149
|
+
const routes = createRoutes({
|
|
150
|
+
pagesGlob: pages,
|
|
151
|
+
pagesDir: "pages",
|
|
152
|
+
layoutsGlob: layouts,
|
|
153
|
+
layoutsDir: "layouts",
|
|
154
|
+
layoutMap: { "/": "Root", "": "Root", blog: "Blog" },
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
- **layoutMap keys:** `""` or `"/"` = root layout; `"blog"` = layout for `/blog` and its children. Value = layout filename without extension (e.g. `Root`, `Blog`).
|
|
159
|
+
- The same layout component can be reused by mapping multiple keys to the same name.
|
|
160
|
+
|
|
161
|
+
---
|
|
56
162
|
|
|
57
163
|
## Route protection
|
|
58
164
|
|
|
@@ -92,7 +198,7 @@ export default function SettingsLayout() {
|
|
|
92
198
|
}
|
|
93
199
|
```
|
|
94
200
|
|
|
95
|
-
|
|
201
|
+
Set a default redirect when creating routes:
|
|
96
202
|
|
|
97
203
|
```tsx
|
|
98
204
|
createRoutes({
|
|
@@ -102,14 +208,14 @@ createRoutes({
|
|
|
102
208
|
});
|
|
103
209
|
```
|
|
104
210
|
|
|
105
|
-
### 2. `<Protect>` component
|
|
211
|
+
### 2. `<Protect>` component
|
|
106
212
|
|
|
107
|
-
Use
|
|
213
|
+
Use `<Protect>` when you want to guard content inside a layout (e.g. wrap `<Outlet />`) or need a stable condition with a fallback UI.
|
|
108
214
|
|
|
109
|
-
**Predefined config (recommended):** define `condition`, `redirectTo`, and `fallback` once
|
|
215
|
+
**Predefined config (recommended):** define `condition`, `redirectTo`, and `fallback` once, then reuse with `<Protect {...config}>`.
|
|
110
216
|
|
|
111
217
|
```tsx
|
|
112
|
-
// src/config/protect.tsx
|
|
218
|
+
// src/config/protect.tsx
|
|
113
219
|
import type { ProtectConfig } from "@epicabdou/linkr";
|
|
114
220
|
|
|
115
221
|
export const authProtect: ProtectConfig = {
|
|
@@ -134,13 +240,31 @@ export default function DashboardLayout() {
|
|
|
134
240
|
}
|
|
135
241
|
```
|
|
136
242
|
|
|
137
|
-
|
|
243
|
+
**Props:**
|
|
138
244
|
|
|
139
245
|
- **`condition`**: sync or async function; return `true` to allow, `false` to redirect.
|
|
140
246
|
- **`redirectTo`**: path to redirect to when the condition fails.
|
|
141
247
|
- **`fallback`**: optional React node shown while an async condition is pending.
|
|
142
248
|
- **`children`**: content to render when the condition is true.
|
|
143
249
|
|
|
250
|
+
You can define multiple configs (e.g. `authProtect`, `adminProtect`) and import the one you need.
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## API exports
|
|
255
|
+
|
|
256
|
+
| Export | Description |
|
|
257
|
+
|--------|-------------|
|
|
258
|
+
| `createRoutes` | Builds React Router route array from pages glob and options. |
|
|
259
|
+
| `LinkrApp` | Component that renders `RouterProvider` with routes from options. |
|
|
260
|
+
| `createRootWithLinkr` | One-shot: `createRoot`, `createRoutes`, `StrictMode` + `RouterProvider`. |
|
|
261
|
+
| `Protect` | Component for conditional redirect with fallback UI. |
|
|
262
|
+
| `normalizePath`, `parseSegment`, `compareSegments` | Path utilities (for advanced use). |
|
|
263
|
+
|
|
264
|
+
**Types:** `CreateRoutesOptions`, `LayoutMap`, `LayoutsGlob`, `PagesGlob`, `RouteObject`, `RouteProtect`, `ProtectProps`, `ProtectConfig`, `LinkrAppOptions`, `ParsedSegment`, `SegmentKind`.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
144
268
|
## Quick start with CLI
|
|
145
269
|
|
|
146
270
|
```bash
|
|
@@ -150,7 +274,16 @@ cd my-app && pnpm dev
|
|
|
150
274
|
|
|
151
275
|
## Build & test
|
|
152
276
|
|
|
277
|
+
From the monorepo root:
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
pnpm --filter @epicabdou/linkr build
|
|
281
|
+
pnpm --filter @epicabdou/linkr test
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Or from `packages/linkr`:
|
|
285
|
+
|
|
153
286
|
```bash
|
|
154
|
-
pnpm
|
|
155
|
-
pnpm
|
|
287
|
+
pnpm build
|
|
288
|
+
pnpm test
|
|
156
289
|
```
|
package/dist/index.d.ts
CHANGED
|
@@ -2,11 +2,19 @@ import { RouteObject } from 'react-router';
|
|
|
2
2
|
export { RouteObject } from 'react-router';
|
|
3
3
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
4
4
|
import { ReactNode } from 'react';
|
|
5
|
+
import { createRoot } from 'react-dom/client';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Result of import.meta.glob("...") - keys are file paths, values are import functions.
|
|
8
9
|
*/
|
|
9
10
|
type PagesGlob = Record<string, () => Promise<unknown>>;
|
|
11
|
+
/** Same shape as PagesGlob; use for layouts, e.g. import.meta.glob from your layouts directory. */
|
|
12
|
+
type LayoutsGlob = Record<string, () => Promise<unknown>>;
|
|
13
|
+
/**
|
|
14
|
+
* Maps a path prefix to a layout name (filename without ext from layouts dir).
|
|
15
|
+
* Use "" or "/" for root layout; use segment name for nested (e.g. "blog" for /blog).
|
|
16
|
+
*/
|
|
17
|
+
type LayoutMap = Record<string, string>;
|
|
10
18
|
/**
|
|
11
19
|
* Protection check: sync or async function that returns true to allow access, false to redirect.
|
|
12
20
|
* Can be a plain function or an object with check, redirectTo, and optional fallback.
|
|
@@ -35,6 +43,18 @@ interface CreateRoutesOptions {
|
|
|
35
43
|
routeExtensions?: string[];
|
|
36
44
|
/** Default redirect path when a route's protect check fails and redirectTo is not set (default: "/"). */
|
|
37
45
|
defaultRedirectTo?: string;
|
|
46
|
+
/**
|
|
47
|
+
* When set, layouts are loaded from this glob and assigned by layoutMap.
|
|
48
|
+
* _layout files in pages are ignored. Example: import.meta.glob("./layouts/*.tsx")
|
|
49
|
+
*/
|
|
50
|
+
layoutsGlob?: LayoutsGlob;
|
|
51
|
+
/** Base directory for layout files, used to derive layout names (default: "src/layouts"). */
|
|
52
|
+
layoutsDir?: string;
|
|
53
|
+
/**
|
|
54
|
+
* Path prefix -> layout name. "" or "/" = root layout; "blog" = layout for /blog.
|
|
55
|
+
* Layout name = filename without extension in layoutsDir.
|
|
56
|
+
*/
|
|
57
|
+
layoutMap?: LayoutMap;
|
|
38
58
|
}
|
|
39
59
|
|
|
40
60
|
declare function createRoutes(options: CreateRoutesOptions): RouteObject[];
|
|
@@ -82,4 +102,31 @@ type ProtectConfig = Pick<ProtectProps, "condition" | "redirectTo" | "fallback">
|
|
|
82
102
|
*/
|
|
83
103
|
declare function Protect({ condition, redirectTo, fallback, children }: ProtectProps): react_jsx_runtime.JSX.Element | null;
|
|
84
104
|
|
|
85
|
-
|
|
105
|
+
/**
|
|
106
|
+
* Options for LinkrApp and createRootWithLinkr.
|
|
107
|
+
* Same as CreateRoutesOptions (pagesGlob required; rest optional).
|
|
108
|
+
*/
|
|
109
|
+
type LinkrAppOptions = {
|
|
110
|
+
pagesGlob: PagesGlob;
|
|
111
|
+
} & Omit<CreateRoutesOptions, "pagesGlob">;
|
|
112
|
+
/**
|
|
113
|
+
* Renders the file-based router. Options are read once on mount.
|
|
114
|
+
* Use this when you need to wrap the app with your own providers.
|
|
115
|
+
*/
|
|
116
|
+
declare function LinkrApp(options: LinkrAppOptions): react_jsx_runtime.JSX.Element;
|
|
117
|
+
/**
|
|
118
|
+
* One-shot setup: creates the root, builds routes from pagesGlob, and renders
|
|
119
|
+
* with StrictMode + RouterProvider. Minimizes app entry to a single call.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```tsx
|
|
123
|
+
* import { createRootWithLinkr } from "@epicabdou/linkr";
|
|
124
|
+
* createRootWithLinkr(document.getElementById("root")!, {
|
|
125
|
+
* pagesGlob: import.meta.glob("./pages/**\/*.tsx"),
|
|
126
|
+
* pagesDir: "pages",
|
|
127
|
+
* });
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
declare function createRootWithLinkr(rootElement: HTMLElement, options: LinkrAppOptions): ReturnType<typeof createRoot>;
|
|
131
|
+
|
|
132
|
+
export { type CreateRoutesOptions, type LayoutMap, type LayoutsGlob, LinkrApp, type LinkrAppOptions, type PagesGlob, type ParsedSegment, Protect, type ProtectConfig, type ProtectProps, type RouteProtect, type SegmentKind, compareSegments, createRootWithLinkr, createRoutes, normalizePath, parseSegment };
|
package/dist/index.js
CHANGED
|
@@ -32,9 +32,28 @@ function compareSegments(a, b) {
|
|
|
32
32
|
|
|
33
33
|
// src/createRoutes.ts
|
|
34
34
|
var DEFAULT_PAGES_DIR = "src/pages";
|
|
35
|
+
var DEFAULT_LAYOUTS_DIR = "src/layouts";
|
|
35
36
|
var DEFAULT_LAYOUT_FILE = "_layout";
|
|
36
37
|
var DEFAULT_NOT_FOUND_FILE = "404";
|
|
37
38
|
var DEFAULT_EXTENSIONS = [".tsx", ".ts", ".jsx", ".js"];
|
|
39
|
+
function parseLayoutsGlob(layoutsGlob, layoutsDir, extensions) {
|
|
40
|
+
const map = /* @__PURE__ */ new Map();
|
|
41
|
+
const dir = normalizePath(layoutsDir).replace(/\/$/, "");
|
|
42
|
+
for (const key of Object.keys(layoutsGlob)) {
|
|
43
|
+
const normalized = normalizePath(key).replace(/^\.\//, "");
|
|
44
|
+
let relative = normalized;
|
|
45
|
+
if (relative.startsWith(dir + "/")) relative = relative.slice(dir.length + 1);
|
|
46
|
+
else if (relative.startsWith(dir)) relative = relative.slice(dir.length).replace(/^\//, "");
|
|
47
|
+
const ext = getRouteExtension(relative, extensions);
|
|
48
|
+
if (!ext) continue;
|
|
49
|
+
const withoutExt = relative.slice(0, -ext.length);
|
|
50
|
+
const parts = withoutExt.split("/").filter(Boolean);
|
|
51
|
+
if (parts.length === 0) continue;
|
|
52
|
+
const name = parts[parts.length - 1];
|
|
53
|
+
map.set(name, layoutsGlob[key]);
|
|
54
|
+
}
|
|
55
|
+
return map;
|
|
56
|
+
}
|
|
38
57
|
function stripExtension(filename, extensions) {
|
|
39
58
|
const lower = filename.toLowerCase();
|
|
40
59
|
for (const ext of extensions) {
|
|
@@ -204,11 +223,12 @@ function makeLazyRoute(loader, filePath, defaultRedirectTo) {
|
|
|
204
223
|
if (!ok) throw redirect(redirectTo);
|
|
205
224
|
return null;
|
|
206
225
|
};
|
|
226
|
+
result.HydrateFallback = () => null;
|
|
207
227
|
}
|
|
208
228
|
return result;
|
|
209
229
|
};
|
|
210
230
|
}
|
|
211
|
-
function nodeToRoutes(node, pathPrefix, pagesGlob, defaultRedirectTo) {
|
|
231
|
+
function nodeToRoutes(node, pathPrefix, pagesGlob, defaultRedirectTo, layoutMap, layoutLoaders) {
|
|
212
232
|
const childRouteObjects = [];
|
|
213
233
|
if (node.indexRoute) {
|
|
214
234
|
const loader = pagesGlob[node.indexRoute.path];
|
|
@@ -246,30 +266,41 @@ function nodeToRoutes(node, pathPrefix, pagesGlob, defaultRedirectTo) {
|
|
|
246
266
|
const firstSeg = key.split("/")[0];
|
|
247
267
|
const seg = parseSegment(firstSeg);
|
|
248
268
|
const pathSeg = segmentToPath(seg);
|
|
249
|
-
const nested = nodeToRoutes(
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
269
|
+
const nested = nodeToRoutes(
|
|
270
|
+
childNode,
|
|
271
|
+
pathPrefix ? pathPrefix + "/" + pathSeg : pathSeg,
|
|
272
|
+
pagesGlob,
|
|
273
|
+
defaultRedirectTo,
|
|
274
|
+
layoutMap,
|
|
275
|
+
layoutLoaders
|
|
276
|
+
);
|
|
277
|
+
const layoutName = layoutMap && layoutLoaders ? layoutMap[key] ?? layoutMap["/" + key] : void 0;
|
|
278
|
+
const layoutLoader = layoutName ? layoutLoaders.get(layoutName) : void 0;
|
|
279
|
+
if (layoutLoader) {
|
|
280
|
+
childRouteObjects.push({
|
|
281
|
+
path: pathSeg,
|
|
282
|
+
lazy: makeLazyRoute(layoutLoader, "layout:" + layoutName, defaultRedirectTo),
|
|
283
|
+
children: nested
|
|
284
|
+
});
|
|
285
|
+
} else if (childNode.layout && childNode.layout.path in pagesGlob) {
|
|
286
|
+
childRouteObjects.push(...nested);
|
|
287
|
+
} else if (childNode.layout) {
|
|
288
|
+
childRouteObjects.push({ path: pathSeg, children: nested });
|
|
256
289
|
} else if (nested.length > 0) {
|
|
257
290
|
childRouteObjects.push({ path: pathSeg, children: nested });
|
|
258
291
|
}
|
|
259
292
|
}
|
|
260
|
-
if (node.layout) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}];
|
|
268
|
-
}
|
|
293
|
+
if (node.layout && node.layout.path in pagesGlob) {
|
|
294
|
+
const layoutLoader = pagesGlob[node.layout.path];
|
|
295
|
+
return [{
|
|
296
|
+
path: pathPrefix === "" ? "/" : pathPrefix,
|
|
297
|
+
lazy: makeLazyRoute(layoutLoader, node.layout.path, defaultRedirectTo),
|
|
298
|
+
children: childRouteObjects.length ? childRouteObjects : void 0
|
|
299
|
+
}];
|
|
269
300
|
}
|
|
270
301
|
return childRouteObjects;
|
|
271
302
|
}
|
|
272
|
-
function flattenRootChildren(root, pagesGlob, defaultRedirectTo) {
|
|
303
|
+
function flattenRootChildren(root, pagesGlob, defaultRedirectTo, layoutMap, layoutLoaders) {
|
|
273
304
|
const result = [];
|
|
274
305
|
if (root.indexRoute) {
|
|
275
306
|
const loader = pagesGlob[root.indexRoute.path];
|
|
@@ -307,17 +338,39 @@ function flattenRootChildren(root, pagesGlob, defaultRedirectTo) {
|
|
|
307
338
|
const firstSeg = key.split("/")[0];
|
|
308
339
|
const seg = parseSegment(firstSeg);
|
|
309
340
|
const pathSeg = segmentToPath(seg);
|
|
310
|
-
const nested = nodeToRoutes(
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
341
|
+
const nested = nodeToRoutes(
|
|
342
|
+
childNode,
|
|
343
|
+
pathSeg,
|
|
344
|
+
pagesGlob,
|
|
345
|
+
defaultRedirectTo,
|
|
346
|
+
layoutMap,
|
|
347
|
+
layoutLoaders
|
|
348
|
+
);
|
|
349
|
+
const layoutName = layoutMap && layoutLoaders ? layoutMap[key] ?? layoutMap["/" + key] : void 0;
|
|
350
|
+
const layoutLoader = layoutName ? layoutLoaders.get(layoutName) : void 0;
|
|
351
|
+
if (layoutLoader) {
|
|
352
|
+
result.push({
|
|
353
|
+
path: pathSeg,
|
|
354
|
+
lazy: makeLazyRoute(layoutLoader, "layout:" + layoutName, defaultRedirectTo),
|
|
355
|
+
children: nested
|
|
356
|
+
});
|
|
357
|
+
} else if (childNode.layout && childNode.layout.path in pagesGlob) {
|
|
358
|
+
result.push(...nested);
|
|
359
|
+
} else if (childNode.layout) {
|
|
360
|
+
result.push({ path: pathSeg, children: nested });
|
|
317
361
|
} else if (nested.length > 0) {
|
|
318
362
|
result.push({ path: pathSeg, children: nested });
|
|
319
363
|
}
|
|
320
364
|
}
|
|
365
|
+
const rootLayoutName = layoutMap && layoutLoaders ? layoutMap[""] ?? layoutMap["/"] : void 0;
|
|
366
|
+
const rootLayoutLoader = rootLayoutName ? layoutLoaders.get(rootLayoutName) : void 0;
|
|
367
|
+
if (rootLayoutLoader && result.length > 0) {
|
|
368
|
+
return [{
|
|
369
|
+
path: "/",
|
|
370
|
+
lazy: makeLazyRoute(rootLayoutLoader, "layout:" + rootLayoutName, defaultRedirectTo),
|
|
371
|
+
children: result
|
|
372
|
+
}];
|
|
373
|
+
}
|
|
321
374
|
return result;
|
|
322
375
|
}
|
|
323
376
|
function createRoutes(options) {
|
|
@@ -334,10 +387,24 @@ function createRoutes(options) {
|
|
|
334
387
|
if (entry) entries.push(entry);
|
|
335
388
|
}
|
|
336
389
|
const notFoundEntry = entries.find((e) => e.is404);
|
|
337
|
-
const
|
|
390
|
+
const useLayoutsFolder = options.layoutsGlob != null && options.layoutMap != null;
|
|
391
|
+
const rest = useLayoutsFolder ? entries.filter((e) => !e.is404 && !e.isLayout) : entries.filter((e) => !e.is404);
|
|
338
392
|
const root = buildTree(rest);
|
|
393
|
+
const layoutLoaders = options.layoutsGlob && options.layoutMap ? parseLayoutsGlob(
|
|
394
|
+
options.layoutsGlob,
|
|
395
|
+
options.layoutsDir ?? DEFAULT_LAYOUTS_DIR,
|
|
396
|
+
opts.routeExtensions
|
|
397
|
+
) : void 0;
|
|
339
398
|
let topLevelRoutes;
|
|
340
|
-
if (
|
|
399
|
+
if (layoutLoaders && options.layoutMap) {
|
|
400
|
+
topLevelRoutes = flattenRootChildren(
|
|
401
|
+
root,
|
|
402
|
+
options.pagesGlob,
|
|
403
|
+
options.defaultRedirectTo,
|
|
404
|
+
options.layoutMap,
|
|
405
|
+
layoutLoaders
|
|
406
|
+
);
|
|
407
|
+
} else if (root.layout && root.layout.path in options.pagesGlob) {
|
|
341
408
|
const layoutLoader = options.pagesGlob[root.layout.path];
|
|
342
409
|
topLevelRoutes = [{
|
|
343
410
|
path: "/",
|
|
@@ -384,9 +451,34 @@ function Protect({ condition, redirectTo, fallback = null, children }) {
|
|
|
384
451
|
if (!allowed) return null;
|
|
385
452
|
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
386
453
|
}
|
|
454
|
+
|
|
455
|
+
// src/LinkrApp.tsx
|
|
456
|
+
import { StrictMode, useMemo, useRef } from "react";
|
|
457
|
+
import { createRoot } from "react-dom/client";
|
|
458
|
+
import { createBrowserRouter, RouterProvider } from "react-router";
|
|
459
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
460
|
+
function LinkrApp(options) {
|
|
461
|
+
const optionsRef = useRef(options);
|
|
462
|
+
optionsRef.current = options;
|
|
463
|
+
const router = useMemo(
|
|
464
|
+
() => createBrowserRouter(createRoutes(optionsRef.current)),
|
|
465
|
+
[]
|
|
466
|
+
);
|
|
467
|
+
return /* @__PURE__ */ jsx2(RouterProvider, { router });
|
|
468
|
+
}
|
|
469
|
+
function createRootWithLinkr(rootElement, options) {
|
|
470
|
+
const router = createBrowserRouter(createRoutes(options));
|
|
471
|
+
const root = createRoot(rootElement);
|
|
472
|
+
root.render(
|
|
473
|
+
/* @__PURE__ */ jsx2(StrictMode, { children: /* @__PURE__ */ jsx2(RouterProvider, { router }) })
|
|
474
|
+
);
|
|
475
|
+
return root;
|
|
476
|
+
}
|
|
387
477
|
export {
|
|
478
|
+
LinkrApp,
|
|
388
479
|
Protect,
|
|
389
480
|
compareSegments,
|
|
481
|
+
createRootWithLinkr,
|
|
390
482
|
createRoutes,
|
|
391
483
|
normalizePath,
|
|
392
484
|
parseSegment
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/createRoutes.ts","../src/pathUtils.ts","../src/Protect.tsx"],"sourcesContent":["import type { RouteObject } from \"react-router\";\r\nimport { redirect } from \"react-router\";\r\nimport type { CreateRoutesOptions, PagesGlob, RouteProtect } from \"./types\";\r\nimport {\r\n normalizePath,\r\n getRouteExtension,\r\n parseSegment,\r\n compareSegments,\r\n type ParsedSegment,\r\n} from \"./pathUtils\";\r\n\r\nconst DEFAULT_PAGES_DIR = \"src/pages\";\r\nconst DEFAULT_LAYOUT_FILE = \"_layout\";\r\nconst DEFAULT_NOT_FOUND_FILE = \"404\";\r\nconst DEFAULT_EXTENSIONS = [\".tsx\", \".ts\", \".jsx\", \".js\"];\r\n\r\ntype LazyModule = {\r\n default?: React.ComponentType;\r\n ErrorBoundary?: React.ComponentType;\r\n handle?: unknown;\r\n protect?: RouteProtect;\r\n};\r\n\r\n/**\r\n * Strip extension from filename.\r\n */\r\nfunction stripExtension(filename: string, extensions: string[]): string {\r\n const lower = filename.toLowerCase();\r\n for (const ext of extensions) {\r\n if (lower.endsWith(ext)) return filename.slice(0, -ext.length);\r\n }\r\n return filename;\r\n}\r\n\r\n/**\r\n * Check if segment is a valid splat (exactly \"[...name]\").\r\n */\r\nfunction isValidSplatSegment(segment: string): boolean {\r\n return /^\\[\\.\\.\\.[^\\]]*\\]$/.test(segment);\r\n}\r\n\r\n/**\r\n * Warn in dev when a module has no default export (called from lazy).\r\n */\r\nfunction warnNoDefault(path: string): void {\r\n if (typeof process !== \"undefined\" && process.env?.NODE_ENV !== \"production\") {\r\n console.warn(`[linkr] Module has no default export, skipping route: ${path}`);\r\n }\r\n}\r\n\r\n/**\r\n * Warn when [...slug] is used with other segments in the same filename.\r\n */\r\nfunction warnInvalidSplat(path: string): void {\r\n if (typeof process !== \"undefined\" && process.env?.NODE_ENV !== \"production\") {\r\n console.warn(\r\n `[linkr] Invalid splat usage (e.g. foo[...slug].tsx); use only [...slug].tsx in filename. Skipping: ${path}`\r\n );\r\n }\r\n}\r\n\r\ninterface FileEntry {\r\n path: string;\r\n segments: string[];\r\n isLayout: boolean;\r\n is404: boolean;\r\n isIndex: boolean;\r\n segmentKinds: ReturnType<typeof parseSegment>[];\r\n}\r\n\r\nfunction parseGlobKey(\r\n key: string,\r\n opts: Required<Pick<CreateRoutesOptions, \"pagesDir\" | \"layoutFileName\" | \"notFoundFileName\" | \"routeExtensions\">>\r\n): FileEntry | null {\r\n const normalized = normalizePath(key);\r\n const withoutLeadingSlash = normalized.replace(/^\\.\\//, \"\");\r\n const dir = normalizePath(opts.pagesDir).replace(/\\/$/, \"\");\r\n let relative = withoutLeadingSlash;\r\n if (relative.startsWith(dir + \"/\")) {\r\n relative = relative.slice(dir.length + 1);\r\n } else if (relative.startsWith(dir)) {\r\n relative = relative.slice(dir.length).replace(/^\\//, \"\");\r\n } else if (dir.includes(\"/\")) {\r\n const lastDirSegment = dir.split(\"/\").pop()!;\r\n if (relative.startsWith(lastDirSegment + \"/\")) {\r\n relative = relative.slice(lastDirSegment.length + 1);\r\n }\r\n }\r\n\r\n const ext = getRouteExtension(relative, opts.routeExtensions);\r\n if (!ext) return null;\r\n\r\n const withoutExt = relative.slice(0, -ext.length);\r\n const parts = withoutExt.split(\"/\").filter(Boolean);\r\n if (parts.length === 0) return null;\r\n\r\n const lastPart = parts[parts.length - 1];\r\n const isLayout = stripExtension(lastPart, opts.routeExtensions) === opts.layoutFileName;\r\n const is404 = stripExtension(lastPart, opts.routeExtensions) === opts.notFoundFileName;\r\n const isIndex = lastPart.toLowerCase().replace(new RegExp(\"\\\\\" + ext + \"$\", \"i\"), \"\") === \"index\";\r\n const segmentParts = isIndex ? parts.slice(0, -1) : parts;\r\n const segmentStrings = segmentParts.map((p, i) => {\r\n const isLast = i === segmentParts.length - 1;\r\n return isLast ? stripExtension(p, opts.routeExtensions) : p;\r\n });\r\n\r\n const segmentKinds = segmentStrings.map(parseSegment);\r\n for (const s of segmentStrings) {\r\n if (s.includes(\"[...\") && !isValidSplatSegment(s)) {\r\n warnInvalidSplat(key);\r\n return null;\r\n }\r\n }\r\n\r\n return {\r\n path: key,\r\n segments: segmentStrings,\r\n isLayout,\r\n is404,\r\n isIndex,\r\n segmentKinds,\r\n };\r\n}\r\n\r\ninterface RouteNode {\r\n path: string;\r\n layout: FileEntry | null;\r\n indexRoute: FileEntry | null;\r\n children: Map<string, { entry: FileEntry }>;\r\n childLayouts: Map<string, RouteNode>;\r\n}\r\n\r\nfunction buildTree(entries: FileEntry[]): RouteNode {\r\n const root: RouteNode = {\r\n path: \"\",\r\n layout: null,\r\n indexRoute: null,\r\n children: new Map(),\r\n childLayouts: new Map(),\r\n };\r\n\r\n for (const entry of entries) {\r\n if (entry.is404) continue;\r\n if (entry.isLayout) {\r\n const layoutSegments = entry.segments.slice(0, -1);\r\n let node = root;\r\n for (let i = 0; i < layoutSegments.length; i++) {\r\n const key = layoutSegments.slice(0, i + 1).join(\"/\");\r\n if (!node.childLayouts.has(key)) {\r\n node.childLayouts.set(key, {\r\n path: key,\r\n layout: null,\r\n indexRoute: null,\r\n children: new Map(),\r\n childLayouts: new Map(),\r\n });\r\n }\r\n node = node.childLayouts.get(key)!;\r\n }\r\n node.layout = entry;\r\n continue;\r\n }\r\n\r\n if (entry.isIndex && entry.segments.length === 0) {\r\n root.indexRoute = entry;\r\n continue;\r\n }\r\n\r\n if (entry.isIndex) {\r\n let node = root;\r\n const segs = entry.segments;\r\n for (let i = 0; i < segs.length; i++) {\r\n const key = segs.slice(0, i + 1).join(\"/\");\r\n if (node.childLayouts.has(key)) {\r\n node = node.childLayouts.get(key)!;\r\n } else {\r\n const next: RouteNode = {\r\n path: key,\r\n layout: null,\r\n indexRoute: null,\r\n children: new Map(),\r\n childLayouts: new Map(),\r\n };\r\n node.childLayouts.set(key, next);\r\n node = next;\r\n }\r\n }\r\n node.indexRoute = entry;\r\n continue;\r\n }\r\n\r\n const segs = entry.segments;\r\n let node = root;\r\n for (let i = 0; i < segs.length - 1; i++) {\r\n const key = segs.slice(0, i + 1).join(\"/\");\r\n if (node.childLayouts.has(key)) {\r\n node = node.childLayouts.get(key)!;\r\n } else {\r\n const next: RouteNode = {\r\n path: key,\r\n layout: null,\r\n indexRoute: null,\r\n children: new Map(),\r\n childLayouts: new Map(),\r\n };\r\n node.childLayouts.set(key, next);\r\n node = next;\r\n }\r\n }\r\n const lastSeg = segs[segs.length - 1];\r\n node.children.set(lastSeg, { entry });\r\n }\r\n\r\n return root;\r\n}\r\n\r\nfunction segmentToPath(segment: ParsedSegment): string {\r\n if (segment.kind === \"static\") return segment.raw;\r\n if (segment.kind === \"dynamic\") return \":\" + (segment.paramName ?? \"param\");\r\n if (segment.kind === \"splat\") return \"*\";\r\n return segment.raw;\r\n}\r\n\r\nfunction makeLazyRoute(\r\n loader: () => Promise<unknown>,\r\n filePath: string,\r\n defaultRedirectTo?: string\r\n): RouteObject[\"lazy\"] {\r\n return async () => {\r\n const mod = (await loader()) as LazyModule;\r\n if (!mod.default) {\r\n warnNoDefault(filePath);\r\n return { Component: () => null };\r\n }\r\n const result: Record<string, unknown> = { Component: mod.default };\r\n if (mod.ErrorBoundary) result.ErrorBoundary = mod.ErrorBoundary;\r\n if (mod.handle !== undefined) result.handle = mod.handle;\r\n\r\n if (mod.protect) {\r\n const check = typeof mod.protect === \"function\" ? mod.protect : mod.protect.check;\r\n const redirectTo =\r\n (typeof mod.protect === \"object\" && mod.protect.redirectTo != null\r\n ? mod.protect.redirectTo\r\n : defaultRedirectTo) ?? \"/\";\r\n result.loader = async () => {\r\n const ok = await Promise.resolve(check());\r\n if (!ok) throw redirect(redirectTo);\r\n return null;\r\n };\r\n }\r\n\r\n return result as {\r\n Component: React.ComponentType;\r\n ErrorBoundary?: React.ComponentType;\r\n handle?: unknown;\r\n loader?: RouteObject[\"loader\"];\r\n };\r\n };\r\n}\r\n\r\nfunction nodeToRoutes(\r\n node: RouteNode,\r\n pathPrefix: string,\r\n pagesGlob: PagesGlob,\r\n defaultRedirectTo?: string\r\n): RouteObject[] {\r\n const childRouteObjects: RouteObject[] = [];\r\n\r\n if (node.indexRoute) {\r\n const loader = pagesGlob[node.indexRoute.path];\r\n if (loader) {\r\n childRouteObjects.push({\r\n index: true,\r\n lazy: makeLazyRoute(loader, node.indexRoute.path, defaultRedirectTo),\r\n });\r\n }\r\n }\r\n\r\n const childSegments = Array.from(node.children.entries());\r\n childSegments.sort(([a], [b]) => compareSegments(parseSegment(a), parseSegment(b)));\r\n\r\n for (const [segStr, { entry }] of childSegments) {\r\n const seg = parseSegment(segStr);\r\n const pathSeg = segmentToPath(seg);\r\n const loader = pagesGlob[entry.path];\r\n if (!loader) continue;\r\n childRouteObjects.push({\r\n path: pathSeg,\r\n lazy: makeLazyRoute(loader, entry.path, defaultRedirectTo),\r\n });\r\n }\r\n\r\n const sortedLayoutKeys = Array.from(node.childLayouts.keys()).sort((a, b) => {\r\n const segsA = a.split(\"/\").map(parseSegment);\r\n const segsB = b.split(\"/\").map(parseSegment);\r\n const len = Math.min(segsA.length, segsB.length);\r\n for (let i = 0; i < len; i++) {\r\n const c = compareSegments(segsA[i], segsB[i]);\r\n if (c !== 0) return c;\r\n }\r\n return segsA.length - segsB.length;\r\n });\r\n\r\n for (const key of sortedLayoutKeys) {\r\n const childNode = node.childLayouts.get(key)!;\r\n const firstSeg = key.split(\"/\")[0];\r\n const seg = parseSegment(firstSeg);\r\n const pathSeg = segmentToPath(seg);\r\n const nested = nodeToRoutes(childNode, pathPrefix ? pathPrefix + \"/\" + pathSeg : pathSeg, pagesGlob, defaultRedirectTo);\r\n if (childNode.layout) {\r\n if (childNode.layout.path in pagesGlob) {\r\n childRouteObjects.push(...nested);\r\n } else {\r\n childRouteObjects.push({ path: pathSeg, children: nested });\r\n }\r\n } else if (nested.length > 0) {\r\n childRouteObjects.push({ path: pathSeg, children: nested });\r\n }\r\n }\r\n\r\n if (node.layout) {\r\n if (node.layout.path in pagesGlob) {\r\n const layoutLoader = pagesGlob[node.layout.path];\r\n return [{\r\n path: pathPrefix === \"\" ? \"/\" : pathPrefix,\r\n lazy: makeLazyRoute(layoutLoader, node.layout.path, defaultRedirectTo),\r\n children: childRouteObjects.length ? childRouteObjects : undefined,\r\n }];\r\n }\r\n }\r\n\r\n return childRouteObjects;\r\n}\r\n\r\nfunction flattenRootChildren(root: RouteNode, pagesGlob: PagesGlob, defaultRedirectTo?: string): RouteObject[] {\r\n const result: RouteObject[] = [];\r\n\r\n if (root.indexRoute) {\r\n const loader = pagesGlob[root.indexRoute.path];\r\n if (loader) {\r\n result.push({\r\n index: true,\r\n lazy: makeLazyRoute(loader, root.indexRoute.path, defaultRedirectTo),\r\n });\r\n }\r\n }\r\n\r\n const childSegments = Array.from(root.children.entries());\r\n childSegments.sort(([a], [b]) => compareSegments(parseSegment(a), parseSegment(b)));\r\n\r\n for (const [segStr, { entry }] of childSegments) {\r\n const seg = parseSegment(segStr);\r\n const pathSeg = segmentToPath(seg);\r\n const loader = pagesGlob[entry.path];\r\n if (!loader) continue;\r\n result.push({\r\n path: pathSeg,\r\n lazy: makeLazyRoute(loader, entry.path, defaultRedirectTo),\r\n });\r\n }\r\n\r\n const layoutKeys = Array.from(root.childLayouts.keys()).sort((a, b) => {\r\n const segsA = a.split(\"/\").map(parseSegment);\r\n const segsB = b.split(\"/\").map(parseSegment);\r\n const len = Math.min(segsA.length, segsB.length);\r\n for (let i = 0; i < len; i++) {\r\n const c = compareSegments(segsA[i], segsB[i]);\r\n if (c !== 0) return c;\r\n }\r\n return segsA.length - segsB.length;\r\n });\r\n\r\n for (const key of layoutKeys) {\r\n const childNode = root.childLayouts.get(key)!;\r\n const firstSeg = key.split(\"/\")[0];\r\n const seg = parseSegment(firstSeg);\r\n const pathSeg = segmentToPath(seg);\r\n const nested = nodeToRoutes(childNode, pathSeg, pagesGlob, defaultRedirectTo);\r\n if (childNode.layout) {\r\n if (childNode.layout.path in pagesGlob) {\r\n result.push(...nested);\r\n } else {\r\n result.push({ path: pathSeg, children: nested });\r\n }\r\n } else if (nested.length > 0) {\r\n result.push({ path: pathSeg, children: nested });\r\n }\r\n }\r\n\r\n return result;\r\n}\r\n\r\nexport function createRoutes(options: CreateRoutesOptions): RouteObject[] {\r\n const pagesDir = normalizePath(options.pagesDir ?? DEFAULT_PAGES_DIR).replace(/\\/$/, \"\");\r\n const opts = {\r\n pagesDir,\r\n layoutFileName: options.layoutFileName ?? DEFAULT_LAYOUT_FILE,\r\n notFoundFileName: options.notFoundFileName ?? DEFAULT_NOT_FOUND_FILE,\r\n routeExtensions: options.routeExtensions ?? DEFAULT_EXTENSIONS,\r\n };\r\n\r\n const entries: FileEntry[] = [];\r\n for (const key of Object.keys(options.pagesGlob)) {\r\n const entry = parseGlobKey(key, opts);\r\n if (entry) entries.push(entry);\r\n }\r\n\r\n const notFoundEntry = entries.find((e) => e.is404);\r\n const rest = entries.filter((e) => !e.is404);\r\n const root = buildTree(rest);\r\n\r\n let topLevelRoutes: RouteObject[];\r\n\r\n if (root.layout && root.layout.path in options.pagesGlob) {\r\n const layoutLoader = options.pagesGlob[root.layout.path];\r\n topLevelRoutes = [{\r\n path: \"/\",\r\n lazy: makeLazyRoute(layoutLoader, root.layout.path, options.defaultRedirectTo),\r\n children: flattenRootChildren(root, options.pagesGlob, options.defaultRedirectTo),\r\n }];\r\n } else {\r\n topLevelRoutes = flattenRootChildren(root, options.pagesGlob, options.defaultRedirectTo);\r\n }\r\n\r\n if (notFoundEntry && notFoundEntry.path in options.pagesGlob) {\r\n const loader = options.pagesGlob[notFoundEntry.path];\r\n topLevelRoutes.push({\r\n path: \"*\",\r\n lazy: makeLazyRoute(loader, notFoundEntry.path, options.defaultRedirectTo),\r\n });\r\n }\r\n\r\n return topLevelRoutes;\r\n}\r\n","/**\r\n * Normalize path to use forward slashes (Windows-safe).\r\n */\r\nexport function normalizePath(p: string): string {\r\n return p.replace(/\\\\/g, \"/\");\r\n}\r\n\r\n/**\r\n * Get route extension from filename (e.g. \".tsx\") or null if not a route file.\r\n */\r\nexport function getRouteExtension(\r\n filename: string,\r\n extensions: string[]\r\n): string | null {\r\n const lower = filename.toLowerCase();\r\n for (const ext of extensions) {\r\n if (lower.endsWith(ext)) return ext;\r\n }\r\n return null;\r\n}\r\n\r\nexport type SegmentKind = \"static\" | \"dynamic\" | \"splat\";\r\n\r\nexport interface ParsedSegment {\r\n /** Original segment string, e.g. \"blog\", \"[id]\", \"[...slug]\" */\r\n raw: string;\r\n kind: SegmentKind;\r\n /** For dynamic: \"id\"; for splat: \"slug\"; for static: same as raw */\r\n paramName?: string;\r\n}\r\n\r\n/**\r\n * Parse a single path segment into kind and param name.\r\n */\r\nexport function parseSegment(segment: string): ParsedSegment {\r\n if (segment.startsWith(\"[...\") && segment.endsWith(\"]\")) {\r\n const param = segment.slice(4, -1);\r\n return { raw: segment, kind: \"splat\", paramName: param || \"splat\" };\r\n }\r\n if (segment.startsWith(\"[\") && segment.endsWith(\"]\")) {\r\n const param = segment.slice(1, -1);\r\n return { raw: segment, kind: \"dynamic\", paramName: param || \"param\" };\r\n }\r\n return { raw: segment, kind: \"static\" };\r\n}\r\n\r\n/**\r\n * Compare two segments for sort order: static < dynamic < splat.\r\n */\r\nexport function compareSegments(a: ParsedSegment, b: ParsedSegment): number {\r\n const order = { static: 0, dynamic: 1, splat: 2 };\r\n const diff = order[a.kind] - order[b.kind];\r\n if (diff !== 0) return diff;\r\n return a.raw.localeCompare(b.raw);\r\n}\r\n","import { useEffect, useState, type ReactNode } from \"react\";\r\nimport { useNavigate } from \"react-router\";\r\n\r\nexport interface ProtectProps {\r\n /** Sync or async function that returns true to allow access, false to redirect. */\r\n condition: () => boolean | Promise<boolean>;\r\n /** Where to redirect when condition returns false. */\r\n redirectTo: string;\r\n /** Optional content to show while an async condition is pending (e.g. a spinner). */\r\n fallback?: ReactNode;\r\n /** Content to render when condition is true. */\r\n children: ReactNode;\r\n}\r\n\r\n/**\r\n * Reusable protection config: condition, redirectTo, and optional fallback.\r\n * Define once in a config file and spread into <Protect {...config}>.\r\n */\r\nexport type ProtectConfig = Pick<ProtectProps, \"condition\" | \"redirectTo\" | \"fallback\">;\r\n\r\n/**\r\n * Protects content by checking a condition before rendering. If the condition\r\n * returns false (sync or async), redirects to redirectTo. Use this to guard\r\n * a single route's content or an entire layout's <Outlet />.\r\n */\r\nexport function Protect({ condition, redirectTo, fallback = null, children }: ProtectProps) {\r\n const navigate = useNavigate();\r\n const [allowed, setAllowed] = useState<boolean | null>(null);\r\n\r\n useEffect(() => {\r\n let cancelled = false;\r\n const result = condition();\r\n if (typeof result === \"boolean\") {\r\n if (!cancelled) setAllowed(result);\r\n if (!result) navigate(redirectTo, { replace: true });\r\n return;\r\n }\r\n result.then((ok) => {\r\n if (!cancelled) setAllowed(ok);\r\n if (!ok) navigate(redirectTo, { replace: true });\r\n });\r\n return () => {\r\n cancelled = true;\r\n };\r\n }, [condition, redirectTo, navigate]);\r\n\r\n if (allowed === null) return <>{fallback}</>;\r\n if (!allowed) return null;\r\n return <>{children}</>;\r\n}\r\n"],"mappings":"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","names":["node","segs"]}
|
|
1
|
+
{"version":3,"sources":["../src/createRoutes.ts","../src/pathUtils.ts","../src/Protect.tsx","../src/LinkrApp.tsx"],"sourcesContent":["import type { RouteObject } from \"react-router\";\r\nimport { redirect } from \"react-router\";\r\nimport type { CreateRoutesOptions, LayoutMap, LayoutsGlob, PagesGlob, RouteProtect } from \"./types\";\r\nimport {\r\n normalizePath,\r\n getRouteExtension,\r\n parseSegment,\r\n compareSegments,\r\n type ParsedSegment,\r\n} from \"./pathUtils\";\r\n\r\nconst DEFAULT_PAGES_DIR = \"src/pages\";\r\nconst DEFAULT_LAYOUTS_DIR = \"src/layouts\";\r\nconst DEFAULT_LAYOUT_FILE = \"_layout\";\r\nconst DEFAULT_NOT_FOUND_FILE = \"404\";\r\nconst DEFAULT_EXTENSIONS = [\".tsx\", \".ts\", \".jsx\", \".js\"];\r\n\r\n/** Build layout name -> loader from layoutsGlob. Layout name = filename without ext (one level). */\r\nfunction parseLayoutsGlob(\r\n layoutsGlob: LayoutsGlob,\r\n layoutsDir: string,\r\n extensions: string[]\r\n): Map<string, () => Promise<unknown>> {\r\n const map = new Map<string, () => Promise<unknown>>();\r\n const dir = normalizePath(layoutsDir).replace(/\\/$/, \"\");\r\n for (const key of Object.keys(layoutsGlob)) {\r\n const normalized = normalizePath(key).replace(/^\\.\\//, \"\");\r\n let relative = normalized;\r\n if (relative.startsWith(dir + \"/\")) relative = relative.slice(dir.length + 1);\r\n else if (relative.startsWith(dir)) relative = relative.slice(dir.length).replace(/^\\//, \"\");\r\n const ext = getRouteExtension(relative, extensions);\r\n if (!ext) continue;\r\n const withoutExt = relative.slice(0, -ext.length);\r\n const parts = withoutExt.split(\"/\").filter(Boolean);\r\n if (parts.length === 0) continue;\r\n const name = parts[parts.length - 1];\r\n map.set(name, layoutsGlob[key]);\r\n }\r\n return map;\r\n}\r\n\r\ntype LazyModule = {\r\n default?: React.ComponentType;\r\n ErrorBoundary?: React.ComponentType;\r\n handle?: unknown;\r\n protect?: RouteProtect;\r\n};\r\n\r\n/**\r\n * Strip extension from filename.\r\n */\r\nfunction stripExtension(filename: string, extensions: string[]): string {\r\n const lower = filename.toLowerCase();\r\n for (const ext of extensions) {\r\n if (lower.endsWith(ext)) return filename.slice(0, -ext.length);\r\n }\r\n return filename;\r\n}\r\n\r\n/**\r\n * Check if segment is a valid splat (exactly \"[...name]\").\r\n */\r\nfunction isValidSplatSegment(segment: string): boolean {\r\n return /^\\[\\.\\.\\.[^\\]]*\\]$/.test(segment);\r\n}\r\n\r\n/**\r\n * Warn in dev when a module has no default export (called from lazy).\r\n */\r\nfunction warnNoDefault(path: string): void {\r\n if (typeof process !== \"undefined\" && process.env?.NODE_ENV !== \"production\") {\r\n console.warn(`[linkr] Module has no default export, skipping route: ${path}`);\r\n }\r\n}\r\n\r\n/**\r\n * Warn when [...slug] is used with other segments in the same filename.\r\n */\r\nfunction warnInvalidSplat(path: string): void {\r\n if (typeof process !== \"undefined\" && process.env?.NODE_ENV !== \"production\") {\r\n console.warn(\r\n `[linkr] Invalid splat usage (e.g. foo[...slug].tsx); use only [...slug].tsx in filename. Skipping: ${path}`\r\n );\r\n }\r\n}\r\n\r\ninterface FileEntry {\r\n path: string;\r\n segments: string[];\r\n isLayout: boolean;\r\n is404: boolean;\r\n isIndex: boolean;\r\n segmentKinds: ReturnType<typeof parseSegment>[];\r\n}\r\n\r\nfunction parseGlobKey(\r\n key: string,\r\n opts: Required<Pick<CreateRoutesOptions, \"pagesDir\" | \"layoutFileName\" | \"notFoundFileName\" | \"routeExtensions\">>\r\n): FileEntry | null {\r\n const normalized = normalizePath(key);\r\n const withoutLeadingSlash = normalized.replace(/^\\.\\//, \"\");\r\n const dir = normalizePath(opts.pagesDir).replace(/\\/$/, \"\");\r\n let relative = withoutLeadingSlash;\r\n if (relative.startsWith(dir + \"/\")) {\r\n relative = relative.slice(dir.length + 1);\r\n } else if (relative.startsWith(dir)) {\r\n relative = relative.slice(dir.length).replace(/^\\//, \"\");\r\n } else if (dir.includes(\"/\")) {\r\n const lastDirSegment = dir.split(\"/\").pop()!;\r\n if (relative.startsWith(lastDirSegment + \"/\")) {\r\n relative = relative.slice(lastDirSegment.length + 1);\r\n }\r\n }\r\n\r\n const ext = getRouteExtension(relative, opts.routeExtensions);\r\n if (!ext) return null;\r\n\r\n const withoutExt = relative.slice(0, -ext.length);\r\n const parts = withoutExt.split(\"/\").filter(Boolean);\r\n if (parts.length === 0) return null;\r\n\r\n const lastPart = parts[parts.length - 1];\r\n const isLayout = stripExtension(lastPart, opts.routeExtensions) === opts.layoutFileName;\r\n const is404 = stripExtension(lastPart, opts.routeExtensions) === opts.notFoundFileName;\r\n const isIndex = lastPart.toLowerCase().replace(new RegExp(\"\\\\\" + ext + \"$\", \"i\"), \"\") === \"index\";\r\n const segmentParts = isIndex ? parts.slice(0, -1) : parts;\r\n const segmentStrings = segmentParts.map((p, i) => {\r\n const isLast = i === segmentParts.length - 1;\r\n return isLast ? stripExtension(p, opts.routeExtensions) : p;\r\n });\r\n\r\n const segmentKinds = segmentStrings.map(parseSegment);\r\n for (const s of segmentStrings) {\r\n if (s.includes(\"[...\") && !isValidSplatSegment(s)) {\r\n warnInvalidSplat(key);\r\n return null;\r\n }\r\n }\r\n\r\n return {\r\n path: key,\r\n segments: segmentStrings,\r\n isLayout,\r\n is404,\r\n isIndex,\r\n segmentKinds,\r\n };\r\n}\r\n\r\ninterface RouteNode {\r\n path: string;\r\n layout: FileEntry | null;\r\n indexRoute: FileEntry | null;\r\n children: Map<string, { entry: FileEntry }>;\r\n childLayouts: Map<string, RouteNode>;\r\n}\r\n\r\nfunction buildTree(entries: FileEntry[]): RouteNode {\r\n const root: RouteNode = {\r\n path: \"\",\r\n layout: null,\r\n indexRoute: null,\r\n children: new Map(),\r\n childLayouts: new Map(),\r\n };\r\n\r\n for (const entry of entries) {\r\n if (entry.is404) continue;\r\n if (entry.isLayout) {\r\n const layoutSegments = entry.segments.slice(0, -1);\r\n let node = root;\r\n for (let i = 0; i < layoutSegments.length; i++) {\r\n const key = layoutSegments.slice(0, i + 1).join(\"/\");\r\n if (!node.childLayouts.has(key)) {\r\n node.childLayouts.set(key, {\r\n path: key,\r\n layout: null,\r\n indexRoute: null,\r\n children: new Map(),\r\n childLayouts: new Map(),\r\n });\r\n }\r\n node = node.childLayouts.get(key)!;\r\n }\r\n node.layout = entry;\r\n continue;\r\n }\r\n\r\n if (entry.isIndex && entry.segments.length === 0) {\r\n root.indexRoute = entry;\r\n continue;\r\n }\r\n\r\n if (entry.isIndex) {\r\n let node = root;\r\n const segs = entry.segments;\r\n for (let i = 0; i < segs.length; i++) {\r\n const key = segs.slice(0, i + 1).join(\"/\");\r\n if (node.childLayouts.has(key)) {\r\n node = node.childLayouts.get(key)!;\r\n } else {\r\n const next: RouteNode = {\r\n path: key,\r\n layout: null,\r\n indexRoute: null,\r\n children: new Map(),\r\n childLayouts: new Map(),\r\n };\r\n node.childLayouts.set(key, next);\r\n node = next;\r\n }\r\n }\r\n node.indexRoute = entry;\r\n continue;\r\n }\r\n\r\n const segs = entry.segments;\r\n let node = root;\r\n for (let i = 0; i < segs.length - 1; i++) {\r\n const key = segs.slice(0, i + 1).join(\"/\");\r\n if (node.childLayouts.has(key)) {\r\n node = node.childLayouts.get(key)!;\r\n } else {\r\n const next: RouteNode = {\r\n path: key,\r\n layout: null,\r\n indexRoute: null,\r\n children: new Map(),\r\n childLayouts: new Map(),\r\n };\r\n node.childLayouts.set(key, next);\r\n node = next;\r\n }\r\n }\r\n const lastSeg = segs[segs.length - 1];\r\n node.children.set(lastSeg, { entry });\r\n }\r\n\r\n return root;\r\n}\r\n\r\nfunction segmentToPath(segment: ParsedSegment): string {\r\n if (segment.kind === \"static\") return segment.raw;\r\n if (segment.kind === \"dynamic\") return \":\" + (segment.paramName ?? \"param\");\r\n if (segment.kind === \"splat\") return \"*\";\r\n return segment.raw;\r\n}\r\n\r\nfunction makeLazyRoute(\r\n loader: () => Promise<unknown>,\r\n filePath: string,\r\n defaultRedirectTo?: string\r\n): RouteObject[\"lazy\"] {\r\n return async () => {\r\n const mod = (await loader()) as LazyModule;\r\n if (!mod.default) {\r\n warnNoDefault(filePath);\r\n return { Component: () => null };\r\n }\r\n const result: Record<string, unknown> = { Component: mod.default };\r\n if (mod.ErrorBoundary) result.ErrorBoundary = mod.ErrorBoundary;\r\n if (mod.handle !== undefined) result.handle = mod.handle;\r\n\r\n if (mod.protect) {\r\n const check = typeof mod.protect === \"function\" ? mod.protect : mod.protect.check;\r\n const redirectTo =\r\n (typeof mod.protect === \"object\" && mod.protect.redirectTo != null\r\n ? mod.protect.redirectTo\r\n : defaultRedirectTo) ?? \"/\";\r\n result.loader = async () => {\r\n const ok = await Promise.resolve(check());\r\n if (!ok) throw redirect(redirectTo);\r\n return null;\r\n };\r\n // React Router v7 requires HydrateFallback when a route has a loader and no hydration data.\r\n result.HydrateFallback = () => null;\r\n }\r\n\r\n return result as {\r\n Component: React.ComponentType;\r\n ErrorBoundary?: React.ComponentType;\r\n handle?: unknown;\r\n loader?: RouteObject[\"loader\"];\r\n HydrateFallback?: React.ComponentType;\r\n };\r\n };\r\n}\r\n\r\nfunction nodeToRoutes(\r\n node: RouteNode,\r\n pathPrefix: string,\r\n pagesGlob: PagesGlob,\r\n defaultRedirectTo?: string,\r\n layoutMap?: LayoutMap,\r\n layoutLoaders?: Map<string, () => Promise<unknown>>\r\n): RouteObject[] {\r\n const childRouteObjects: RouteObject[] = [];\r\n\r\n if (node.indexRoute) {\r\n const loader = pagesGlob[node.indexRoute.path];\r\n if (loader) {\r\n childRouteObjects.push({\r\n index: true,\r\n lazy: makeLazyRoute(loader, node.indexRoute.path, defaultRedirectTo),\r\n });\r\n }\r\n }\r\n\r\n const childSegments = Array.from(node.children.entries());\r\n childSegments.sort(([a], [b]) => compareSegments(parseSegment(a), parseSegment(b)));\r\n\r\n for (const [segStr, { entry }] of childSegments) {\r\n const seg = parseSegment(segStr);\r\n const pathSeg = segmentToPath(seg);\r\n const loader = pagesGlob[entry.path];\r\n if (!loader) continue;\r\n childRouteObjects.push({\r\n path: pathSeg,\r\n lazy: makeLazyRoute(loader, entry.path, defaultRedirectTo),\r\n });\r\n }\r\n\r\n const sortedLayoutKeys = Array.from(node.childLayouts.keys()).sort((a, b) => {\r\n const segsA = a.split(\"/\").map(parseSegment);\r\n const segsB = b.split(\"/\").map(parseSegment);\r\n const len = Math.min(segsA.length, segsB.length);\r\n for (let i = 0; i < len; i++) {\r\n const c = compareSegments(segsA[i], segsB[i]);\r\n if (c !== 0) return c;\r\n }\r\n return segsA.length - segsB.length;\r\n });\r\n\r\n for (const key of sortedLayoutKeys) {\r\n const childNode = node.childLayouts.get(key)!;\r\n const firstSeg = key.split(\"/\")[0];\r\n const seg = parseSegment(firstSeg);\r\n const pathSeg = segmentToPath(seg);\r\n const nested = nodeToRoutes(\r\n childNode,\r\n pathPrefix ? pathPrefix + \"/\" + pathSeg : pathSeg,\r\n pagesGlob,\r\n defaultRedirectTo,\r\n layoutMap,\r\n layoutLoaders\r\n );\r\n const layoutName = layoutMap && layoutLoaders ? layoutMap[key] ?? layoutMap[\"/\" + key] : undefined;\r\n const layoutLoader = layoutName ? layoutLoaders!.get(layoutName) : undefined;\r\n if (layoutLoader) {\r\n childRouteObjects.push({\r\n path: pathSeg,\r\n lazy: makeLazyRoute(layoutLoader, \"layout:\" + layoutName, defaultRedirectTo),\r\n children: nested,\r\n });\r\n } else if (childNode.layout && childNode.layout.path in pagesGlob) {\r\n childRouteObjects.push(...nested);\r\n } else if (childNode.layout) {\r\n childRouteObjects.push({ path: pathSeg, children: nested });\r\n } else if (nested.length > 0) {\r\n childRouteObjects.push({ path: pathSeg, children: nested });\r\n }\r\n }\r\n\r\n if (node.layout && node.layout.path in pagesGlob) {\r\n const layoutLoader = pagesGlob[node.layout.path];\r\n return [{\r\n path: pathPrefix === \"\" ? \"/\" : pathPrefix,\r\n lazy: makeLazyRoute(layoutLoader, node.layout.path, defaultRedirectTo),\r\n children: childRouteObjects.length ? childRouteObjects : undefined,\r\n }];\r\n }\r\n\r\n return childRouteObjects;\r\n}\r\n\r\nfunction flattenRootChildren(\r\n root: RouteNode,\r\n pagesGlob: PagesGlob,\r\n defaultRedirectTo?: string,\r\n layoutMap?: LayoutMap,\r\n layoutLoaders?: Map<string, () => Promise<unknown>>\r\n): RouteObject[] {\r\n const result: RouteObject[] = [];\r\n\r\n if (root.indexRoute) {\r\n const loader = pagesGlob[root.indexRoute.path];\r\n if (loader) {\r\n result.push({\r\n index: true,\r\n lazy: makeLazyRoute(loader, root.indexRoute.path, defaultRedirectTo),\r\n });\r\n }\r\n }\r\n\r\n const childSegments = Array.from(root.children.entries());\r\n childSegments.sort(([a], [b]) => compareSegments(parseSegment(a), parseSegment(b)));\r\n\r\n for (const [segStr, { entry }] of childSegments) {\r\n const seg = parseSegment(segStr);\r\n const pathSeg = segmentToPath(seg);\r\n const loader = pagesGlob[entry.path];\r\n if (!loader) continue;\r\n result.push({\r\n path: pathSeg,\r\n lazy: makeLazyRoute(loader, entry.path, defaultRedirectTo),\r\n });\r\n }\r\n\r\n const layoutKeys = Array.from(root.childLayouts.keys()).sort((a, b) => {\r\n const segsA = a.split(\"/\").map(parseSegment);\r\n const segsB = b.split(\"/\").map(parseSegment);\r\n const len = Math.min(segsA.length, segsB.length);\r\n for (let i = 0; i < len; i++) {\r\n const c = compareSegments(segsA[i], segsB[i]);\r\n if (c !== 0) return c;\r\n }\r\n return segsA.length - segsB.length;\r\n });\r\n\r\n for (const key of layoutKeys) {\r\n const childNode = root.childLayouts.get(key)!;\r\n const firstSeg = key.split(\"/\")[0];\r\n const seg = parseSegment(firstSeg);\r\n const pathSeg = segmentToPath(seg);\r\n const nested = nodeToRoutes(\r\n childNode,\r\n pathSeg,\r\n pagesGlob,\r\n defaultRedirectTo,\r\n layoutMap,\r\n layoutLoaders\r\n );\r\n const layoutName = layoutMap && layoutLoaders ? layoutMap[key] ?? layoutMap[\"/\" + key] : undefined;\r\n const layoutLoader = layoutName ? layoutLoaders!.get(layoutName) : undefined;\r\n if (layoutLoader) {\r\n result.push({\r\n path: pathSeg,\r\n lazy: makeLazyRoute(layoutLoader, \"layout:\" + layoutName, defaultRedirectTo),\r\n children: nested,\r\n });\r\n } else if (childNode.layout && childNode.layout.path in pagesGlob) {\r\n result.push(...nested);\r\n } else if (childNode.layout) {\r\n result.push({ path: pathSeg, children: nested });\r\n } else if (nested.length > 0) {\r\n result.push({ path: pathSeg, children: nested });\r\n }\r\n }\r\n\r\n const rootLayoutName = layoutMap && layoutLoaders ? layoutMap[\"\"] ?? layoutMap[\"/\"] : undefined;\r\n const rootLayoutLoader = rootLayoutName ? layoutLoaders!.get(rootLayoutName) : undefined;\r\n if (rootLayoutLoader && result.length > 0) {\r\n return [{\r\n path: \"/\",\r\n lazy: makeLazyRoute(rootLayoutLoader, \"layout:\" + rootLayoutName, defaultRedirectTo),\r\n children: result,\r\n }];\r\n }\r\n return result;\r\n}\r\n\r\nexport function createRoutes(options: CreateRoutesOptions): RouteObject[] {\r\n const pagesDir = normalizePath(options.pagesDir ?? DEFAULT_PAGES_DIR).replace(/\\/$/, \"\");\r\n const opts = {\r\n pagesDir,\r\n layoutFileName: options.layoutFileName ?? DEFAULT_LAYOUT_FILE,\r\n notFoundFileName: options.notFoundFileName ?? DEFAULT_NOT_FOUND_FILE,\r\n routeExtensions: options.routeExtensions ?? DEFAULT_EXTENSIONS,\r\n };\r\n\r\n const entries: FileEntry[] = [];\r\n for (const key of Object.keys(options.pagesGlob)) {\r\n const entry = parseGlobKey(key, opts);\r\n if (entry) entries.push(entry);\r\n }\r\n\r\n const notFoundEntry = entries.find((e) => e.is404);\r\n const useLayoutsFolder = options.layoutsGlob != null && options.layoutMap != null;\r\n const rest = useLayoutsFolder\r\n ? entries.filter((e) => !e.is404 && !e.isLayout)\r\n : entries.filter((e) => !e.is404);\r\n const root = buildTree(rest);\r\n\r\n const layoutLoaders =\r\n options.layoutsGlob && options.layoutMap\r\n ? parseLayoutsGlob(\r\n options.layoutsGlob,\r\n options.layoutsDir ?? DEFAULT_LAYOUTS_DIR,\r\n opts.routeExtensions\r\n )\r\n : undefined;\r\n\r\n let topLevelRoutes: RouteObject[];\r\n\r\n if (layoutLoaders && options.layoutMap) {\r\n topLevelRoutes = flattenRootChildren(\r\n root,\r\n options.pagesGlob,\r\n options.defaultRedirectTo,\r\n options.layoutMap,\r\n layoutLoaders\r\n );\r\n } else if (root.layout && root.layout.path in options.pagesGlob) {\r\n const layoutLoader = options.pagesGlob[root.layout.path];\r\n topLevelRoutes = [{\r\n path: \"/\",\r\n lazy: makeLazyRoute(layoutLoader, root.layout.path, options.defaultRedirectTo),\r\n children: flattenRootChildren(root, options.pagesGlob, options.defaultRedirectTo),\r\n }];\r\n } else {\r\n topLevelRoutes = flattenRootChildren(root, options.pagesGlob, options.defaultRedirectTo);\r\n }\r\n\r\n if (notFoundEntry && notFoundEntry.path in options.pagesGlob) {\r\n const loader = options.pagesGlob[notFoundEntry.path];\r\n topLevelRoutes.push({\r\n path: \"*\",\r\n lazy: makeLazyRoute(loader, notFoundEntry.path, options.defaultRedirectTo),\r\n });\r\n }\r\n\r\n return topLevelRoutes;\r\n}\r\n","/**\r\n * Normalize path to use forward slashes (Windows-safe).\r\n */\r\nexport function normalizePath(p: string): string {\r\n return p.replace(/\\\\/g, \"/\");\r\n}\r\n\r\n/**\r\n * Get route extension from filename (e.g. \".tsx\") or null if not a route file.\r\n */\r\nexport function getRouteExtension(\r\n filename: string,\r\n extensions: string[]\r\n): string | null {\r\n const lower = filename.toLowerCase();\r\n for (const ext of extensions) {\r\n if (lower.endsWith(ext)) return ext;\r\n }\r\n return null;\r\n}\r\n\r\nexport type SegmentKind = \"static\" | \"dynamic\" | \"splat\";\r\n\r\nexport interface ParsedSegment {\r\n /** Original segment string, e.g. \"blog\", \"[id]\", \"[...slug]\" */\r\n raw: string;\r\n kind: SegmentKind;\r\n /** For dynamic: \"id\"; for splat: \"slug\"; for static: same as raw */\r\n paramName?: string;\r\n}\r\n\r\n/**\r\n * Parse a single path segment into kind and param name.\r\n */\r\nexport function parseSegment(segment: string): ParsedSegment {\r\n if (segment.startsWith(\"[...\") && segment.endsWith(\"]\")) {\r\n const param = segment.slice(4, -1);\r\n return { raw: segment, kind: \"splat\", paramName: param || \"splat\" };\r\n }\r\n if (segment.startsWith(\"[\") && segment.endsWith(\"]\")) {\r\n const param = segment.slice(1, -1);\r\n return { raw: segment, kind: \"dynamic\", paramName: param || \"param\" };\r\n }\r\n return { raw: segment, kind: \"static\" };\r\n}\r\n\r\n/**\r\n * Compare two segments for sort order: static < dynamic < splat.\r\n */\r\nexport function compareSegments(a: ParsedSegment, b: ParsedSegment): number {\r\n const order = { static: 0, dynamic: 1, splat: 2 };\r\n const diff = order[a.kind] - order[b.kind];\r\n if (diff !== 0) return diff;\r\n return a.raw.localeCompare(b.raw);\r\n}\r\n","import { useEffect, useState, type ReactNode } from \"react\";\r\nimport { useNavigate } from \"react-router\";\r\n\r\nexport interface ProtectProps {\r\n /** Sync or async function that returns true to allow access, false to redirect. */\r\n condition: () => boolean | Promise<boolean>;\r\n /** Where to redirect when condition returns false. */\r\n redirectTo: string;\r\n /** Optional content to show while an async condition is pending (e.g. a spinner). */\r\n fallback?: ReactNode;\r\n /** Content to render when condition is true. */\r\n children: ReactNode;\r\n}\r\n\r\n/**\r\n * Reusable protection config: condition, redirectTo, and optional fallback.\r\n * Define once in a config file and spread into <Protect {...config}>.\r\n */\r\nexport type ProtectConfig = Pick<ProtectProps, \"condition\" | \"redirectTo\" | \"fallback\">;\r\n\r\n/**\r\n * Protects content by checking a condition before rendering. If the condition\r\n * returns false (sync or async), redirects to redirectTo. Use this to guard\r\n * a single route's content or an entire layout's <Outlet />.\r\n */\r\nexport function Protect({ condition, redirectTo, fallback = null, children }: ProtectProps) {\r\n const navigate = useNavigate();\r\n const [allowed, setAllowed] = useState<boolean | null>(null);\r\n\r\n useEffect(() => {\r\n let cancelled = false;\r\n const result = condition();\r\n if (typeof result === \"boolean\") {\r\n if (!cancelled) setAllowed(result);\r\n if (!result) navigate(redirectTo, { replace: true });\r\n return;\r\n }\r\n result.then((ok) => {\r\n if (!cancelled) setAllowed(ok);\r\n if (!ok) navigate(redirectTo, { replace: true });\r\n });\r\n return () => {\r\n cancelled = true;\r\n };\r\n }, [condition, redirectTo, navigate]);\r\n\r\n if (allowed === null) return <>{fallback}</>;\r\n if (!allowed) return null;\r\n return <>{children}</>;\r\n}\r\n","import { StrictMode, useMemo, useRef } from \"react\";\r\nimport { createRoot } from \"react-dom/client\";\r\nimport { createBrowserRouter, RouterProvider } from \"react-router\";\r\nimport { createRoutes } from \"./createRoutes\";\r\nimport type { CreateRoutesOptions, PagesGlob } from \"./types\";\r\n\r\n/**\r\n * Options for LinkrApp and createRootWithLinkr.\r\n * Same as CreateRoutesOptions (pagesGlob required; rest optional).\r\n */\r\nexport type LinkrAppOptions = { pagesGlob: PagesGlob } & Omit<CreateRoutesOptions, \"pagesGlob\">;\r\n\r\n/**\r\n * Renders the file-based router. Options are read once on mount.\r\n * Use this when you need to wrap the app with your own providers.\r\n */\r\nexport function LinkrApp(options: LinkrAppOptions) {\r\n const optionsRef = useRef(options);\r\n optionsRef.current = options;\r\n const router = useMemo(\r\n () => createBrowserRouter(createRoutes(optionsRef.current)),\r\n []\r\n );\r\n return <RouterProvider router={router} />;\r\n}\r\n\r\n/**\r\n * One-shot setup: creates the root, builds routes from pagesGlob, and renders\r\n * with StrictMode + RouterProvider. Minimizes app entry to a single call.\r\n *\r\n * @example\r\n * ```tsx\r\n * import { createRootWithLinkr } from \"@epicabdou/linkr\";\r\n * createRootWithLinkr(document.getElementById(\"root\")!, {\r\n * pagesGlob: import.meta.glob(\"./pages/**\\/*.tsx\"),\r\n * pagesDir: \"pages\",\r\n * });\r\n * ```\r\n */\r\nexport function createRootWithLinkr(\r\n rootElement: HTMLElement,\r\n options: LinkrAppOptions\r\n): ReturnType<typeof createRoot> {\r\n const router = createBrowserRouter(createRoutes(options));\r\n const root = createRoot(rootElement);\r\n root.render(\r\n <StrictMode>\r\n <RouterProvider router={router} />\r\n </StrictMode>\r\n );\r\n return root;\r\n}\r\n"],"mappings":"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","names":["node","segs","jsx"]}
|