@zenithbuild/router 1.0.1
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 +127 -0
- package/index.d.ts +38 -0
- package/index.js +5 -0
- package/package.json +65 -0
- package/src/index.ts +108 -0
- package/src/lib.rs +18 -0
- package/src/manifest.rs +189 -0
- package/src/manifest.ts +208 -0
- package/src/navigation/ZenLink.zen +231 -0
- package/src/navigation/index.ts +79 -0
- package/src/navigation/zen-link.ts +585 -0
- package/src/render.rs +8 -0
- package/src/resolve.rs +53 -0
- package/src/runtime.ts +163 -0
- package/src/runtime_gen.rs +14 -0
- package/src/types.rs +46 -0
- package/src/types.ts +63 -0
- package/tsconfig.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# @zenithbuild/router
|
|
2
|
+
|
|
3
|
+
File-based SPA router for Zenith framework with **deterministic, compile-time route resolution**.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 📁 **File-based routing** — Pages in `pages/` directory become routes automatically
|
|
8
|
+
- ⚡ **Compile-time resolution** — Route manifest generated at build time, not runtime
|
|
9
|
+
- 🔗 **ZenLink component** — Declarative navigation with prefetching
|
|
10
|
+
- 🧭 **Programmatic navigation** — `navigate()`, `prefetch()`, `isActive()` APIs
|
|
11
|
+
- 🎯 **Type-safe** — Full TypeScript support with route parameter inference
|
|
12
|
+
- 🚀 **Hydration-safe** — No runtime hacks, works seamlessly with SSR/SSG
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
bun add @zenithbuild/router
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Programmatic Navigation
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { navigate, prefetch, isActive, getRoute } from '@zenithbuild/router'
|
|
26
|
+
|
|
27
|
+
// Navigate to a route
|
|
28
|
+
navigate('/about')
|
|
29
|
+
|
|
30
|
+
// Navigate with replace (no history entry)
|
|
31
|
+
navigate('/dashboard', { replace: true })
|
|
32
|
+
|
|
33
|
+
// Prefetch a route for faster navigation
|
|
34
|
+
prefetch('/blog')
|
|
35
|
+
|
|
36
|
+
// Check if a route is active
|
|
37
|
+
if (isActive('/blog')) {
|
|
38
|
+
console.log('Currently on blog section')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Get current route state
|
|
42
|
+
const { path, params, query } = getRoute()
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### ZenLink Component (in .zen files)
|
|
46
|
+
|
|
47
|
+
```html
|
|
48
|
+
<ZenLink href="/about">About Us</ZenLink>
|
|
49
|
+
|
|
50
|
+
<!-- With prefetching on hover -->
|
|
51
|
+
<ZenLink href="/blog" preload>Blog</ZenLink>
|
|
52
|
+
|
|
53
|
+
<!-- External links automatically open in new tab -->
|
|
54
|
+
<ZenLink href="https://github.com">GitHub</ZenLink>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Build-time Route Manifest
|
|
58
|
+
|
|
59
|
+
The router generates a route manifest at compile time:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { generateRouteManifest, discoverPages } from '@zenithbuild/router/manifest'
|
|
63
|
+
|
|
64
|
+
const pagesDir = './src/pages'
|
|
65
|
+
const manifest = generateRouteManifest(pagesDir)
|
|
66
|
+
|
|
67
|
+
// manifest contains:
|
|
68
|
+
// - path: Route pattern (e.g., /blog/:id)
|
|
69
|
+
// - regex: Compiled RegExp for matching
|
|
70
|
+
// - paramNames: Dynamic segment names
|
|
71
|
+
// - score: Priority for deterministic matching
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Route Patterns
|
|
75
|
+
|
|
76
|
+
| File Path | Route Pattern |
|
|
77
|
+
|-----------|---------------|
|
|
78
|
+
| `pages/index.zen` | `/` |
|
|
79
|
+
| `pages/about.zen` | `/about` |
|
|
80
|
+
| `pages/blog/index.zen` | `/blog` |
|
|
81
|
+
| `pages/blog/[id].zen` | `/blog/:id` |
|
|
82
|
+
| `pages/posts/[...slug].zen` | `/posts/*slug` |
|
|
83
|
+
| `pages/[[...all]].zen` | `/*all?` (optional) |
|
|
84
|
+
|
|
85
|
+
## Architecture
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
@zenithbuild/router
|
|
89
|
+
├── src/
|
|
90
|
+
│ ├── index.ts # Main exports
|
|
91
|
+
│ ├── types.ts # Core types
|
|
92
|
+
│ ├── manifest.ts # Build-time manifest generation
|
|
93
|
+
│ ├── runtime.ts # Client-side SPA router
|
|
94
|
+
│ └── navigation/
|
|
95
|
+
│ ├── index.ts # Navigation exports
|
|
96
|
+
│ ├── zen-link.ts # Navigation API
|
|
97
|
+
│ └── ZenLink.zen # Declarative component
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## API Reference
|
|
101
|
+
|
|
102
|
+
### Navigation Functions
|
|
103
|
+
|
|
104
|
+
- `navigate(path, options?)` — Navigate to a path
|
|
105
|
+
- `prefetch(path)` — Prefetch a route for faster navigation
|
|
106
|
+
- `isActive(path, exact?)` — Check if path is currently active
|
|
107
|
+
- `getRoute()` — Get current route state
|
|
108
|
+
- `back()`, `forward()`, `go(delta)` — History navigation
|
|
109
|
+
|
|
110
|
+
### Manifest Generation
|
|
111
|
+
|
|
112
|
+
- `discoverPages(pagesDir)` — Find all .zen files in pages directory
|
|
113
|
+
- `generateRouteManifest(pagesDir)` — Generate complete route manifest
|
|
114
|
+
- `filePathToRoutePath(filePath, pagesDir)` — Convert file path to route
|
|
115
|
+
- `routePathToRegex(routePath)` — Compile route to RegExp
|
|
116
|
+
|
|
117
|
+
### Types
|
|
118
|
+
|
|
119
|
+
- `RouteState` — Current route state (path, params, query)
|
|
120
|
+
- `RouteRecord` — Compiled route definition
|
|
121
|
+
- `NavigateOptions` — Options for navigation
|
|
122
|
+
- `ZenLinkProps` — Props for ZenLink component
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
MIT
|
|
127
|
+
# zenith-router
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
/* auto-generated by NAPI-RS */
|
|
5
|
+
|
|
6
|
+
export declare function generateRouteManifestNative(pagesDir: string): Array<RouteRecord>
|
|
7
|
+
export declare function renderRouteNative(inputJson: string): string
|
|
8
|
+
export declare function resolveRouteNative(path: string, routes: Array<RouteRecord>): RouteState | null
|
|
9
|
+
export declare function generateRuntimeRouterNative(): string
|
|
10
|
+
export const enum SegmentType {
|
|
11
|
+
Static = 0,
|
|
12
|
+
Dynamic = 1,
|
|
13
|
+
CatchAll = 2,
|
|
14
|
+
OptionalCatchAll = 3
|
|
15
|
+
}
|
|
16
|
+
export interface ParsedSegment {
|
|
17
|
+
segmentType: SegmentType
|
|
18
|
+
paramName?: string
|
|
19
|
+
raw: string
|
|
20
|
+
}
|
|
21
|
+
export interface RouteRecord {
|
|
22
|
+
path: string
|
|
23
|
+
regex: string
|
|
24
|
+
paramNames: Array<string>
|
|
25
|
+
score: number
|
|
26
|
+
filePath: string
|
|
27
|
+
}
|
|
28
|
+
export interface RouteState {
|
|
29
|
+
path: string
|
|
30
|
+
params: Record<string, string>
|
|
31
|
+
query: Record<string, string>
|
|
32
|
+
matched?: RouteRecord
|
|
33
|
+
}
|
|
34
|
+
export interface RouteManifest {
|
|
35
|
+
routes: Array<RouteRecord>
|
|
36
|
+
generatedAt: number
|
|
37
|
+
}
|
|
38
|
+
export declare function routerBridge(): string
|
package/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { createRequire } from 'module';
|
|
2
|
+
const require = createRequire(import.meta.url);
|
|
3
|
+
const native = require('./zenith-router.node');
|
|
4
|
+
export const { generateRouteManifestNative, renderRouteNative, resolveRouteNative, generateRuntimeRouterNative, SegmentType, routerBridge } = native;
|
|
5
|
+
export default native;
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zenithbuild/router",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "File-based SPA router for Zenith framework with deterministic, compile-time route resolution",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./src/index.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"src",
|
|
10
|
+
"dist",
|
|
11
|
+
"tsconfig.json",
|
|
12
|
+
"index.js",
|
|
13
|
+
"index.d.ts"
|
|
14
|
+
],
|
|
15
|
+
"napi": {
|
|
16
|
+
"name": "zenith-router",
|
|
17
|
+
"package": {
|
|
18
|
+
"name": "@zenithbuild/router"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"exports": {
|
|
22
|
+
".": "./src/index.ts",
|
|
23
|
+
"./manifest": "./src/manifest.ts",
|
|
24
|
+
"./runtime": "./src/runtime.ts",
|
|
25
|
+
"./navigation": "./src/navigation/index.ts",
|
|
26
|
+
"./types": "./src/types.ts"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"zenith",
|
|
30
|
+
"router",
|
|
31
|
+
"spa",
|
|
32
|
+
"file-based-routing",
|
|
33
|
+
"compile-time"
|
|
34
|
+
],
|
|
35
|
+
"author": "Zenith Team",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+ssh://git@github.com/zenithbuild/zenith-router.git"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"format": "prettier --write \"**/*.ts\"",
|
|
42
|
+
"format:check": "prettier --check \"**/*.ts\"",
|
|
43
|
+
"typecheck": "tsc --noEmit",
|
|
44
|
+
"build": "napi build --release",
|
|
45
|
+
"build:debug": "napi build",
|
|
46
|
+
"release": "bun run scripts/release.ts",
|
|
47
|
+
"release:dry": "bun run scripts/release.ts --dry-run",
|
|
48
|
+
"release:patch": "bun run scripts/release.ts --bump=patch",
|
|
49
|
+
"release:minor": "bun run scripts/release.ts --bump=minor",
|
|
50
|
+
"release:major": "bun run scripts/release.ts --bump=major"
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
},
|
|
55
|
+
"private": false,
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@napi-rs/cli": "^2.18.4",
|
|
58
|
+
"@types/bun": "latest",
|
|
59
|
+
"@types/node": "latest",
|
|
60
|
+
"prettier": "^3.7.4"
|
|
61
|
+
},
|
|
62
|
+
"peerDependencies": {
|
|
63
|
+
"typescript": "^5"
|
|
64
|
+
}
|
|
65
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zenithbuild/router
|
|
3
|
+
*
|
|
4
|
+
* File-based SPA router for Zenith framework.
|
|
5
|
+
* Includes routing, navigation, and ZenLink components.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Deterministic, compile-time route resolution
|
|
9
|
+
* - File-based routing (pages/ directory → routes)
|
|
10
|
+
* - SPA navigation with prefetching
|
|
11
|
+
* - ZenLink component for declarative links
|
|
12
|
+
* - Type-safe route parameters
|
|
13
|
+
* - Hydration-safe, no runtime hacks
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { navigate, isActive, prefetch } from '@zenithbuild/router'
|
|
18
|
+
*
|
|
19
|
+
* // Navigate programmatically
|
|
20
|
+
* navigate('/about')
|
|
21
|
+
*
|
|
22
|
+
* // Check active state
|
|
23
|
+
* if (isActive('/blog')) {
|
|
24
|
+
* console.log('On blog section')
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* // Build-time manifest generation
|
|
31
|
+
* import { generateRouteManifest, discoverPages } from '@zenithbuild/router/manifest'
|
|
32
|
+
*
|
|
33
|
+
* const manifest = generateRouteManifest('./src/pages')
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
// ============================================
|
|
38
|
+
// Core Types
|
|
39
|
+
// ============================================
|
|
40
|
+
|
|
41
|
+
export * from "./types"
|
|
42
|
+
|
|
43
|
+
// ============================================
|
|
44
|
+
// Build-time Manifest Generation
|
|
45
|
+
// ============================================
|
|
46
|
+
|
|
47
|
+
export {
|
|
48
|
+
generateRouteManifest,
|
|
49
|
+
generateRouteManifestCode
|
|
50
|
+
} from "./manifest"
|
|
51
|
+
|
|
52
|
+
// ============================================
|
|
53
|
+
// Runtime Router
|
|
54
|
+
// ============================================
|
|
55
|
+
|
|
56
|
+
export {
|
|
57
|
+
initRouter,
|
|
58
|
+
resolveRoute,
|
|
59
|
+
navigate,
|
|
60
|
+
getRoute,
|
|
61
|
+
onRouteChange,
|
|
62
|
+
isActive,
|
|
63
|
+
prefetch,
|
|
64
|
+
isPrefetched,
|
|
65
|
+
generateRuntimeRouterCode
|
|
66
|
+
} from "./runtime"
|
|
67
|
+
|
|
68
|
+
// ============================================
|
|
69
|
+
// Navigation Utilities
|
|
70
|
+
// ============================================
|
|
71
|
+
|
|
72
|
+
export {
|
|
73
|
+
// Navigation API (zen* prefixed names)
|
|
74
|
+
zenNavigate,
|
|
75
|
+
zenBack,
|
|
76
|
+
zenForward,
|
|
77
|
+
zenGo,
|
|
78
|
+
zenIsActive,
|
|
79
|
+
zenPrefetch,
|
|
80
|
+
zenIsPrefetched,
|
|
81
|
+
zenGetRoute,
|
|
82
|
+
zenGetParam,
|
|
83
|
+
zenGetQuery,
|
|
84
|
+
createZenLink,
|
|
85
|
+
zenLink,
|
|
86
|
+
// Additional navigation utilities
|
|
87
|
+
back,
|
|
88
|
+
forward,
|
|
89
|
+
go,
|
|
90
|
+
getParam,
|
|
91
|
+
getQuery,
|
|
92
|
+
isExternalUrl,
|
|
93
|
+
shouldUseSPANavigation,
|
|
94
|
+
normalizePath,
|
|
95
|
+
setGlobalTransition,
|
|
96
|
+
getGlobalTransition,
|
|
97
|
+
createTransitionContext
|
|
98
|
+
} from "./navigation/index"
|
|
99
|
+
|
|
100
|
+
// ============================================
|
|
101
|
+
// Navigation Types
|
|
102
|
+
// ============================================
|
|
103
|
+
|
|
104
|
+
export type {
|
|
105
|
+
ZenLinkProps,
|
|
106
|
+
TransitionContext,
|
|
107
|
+
TransitionHandler
|
|
108
|
+
} from "./navigation/index"
|
package/src/lib.rs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
use napi_derive::napi;
|
|
2
|
+
|
|
3
|
+
pub mod manifest;
|
|
4
|
+
pub mod render;
|
|
5
|
+
pub mod resolve;
|
|
6
|
+
pub mod runtime_gen;
|
|
7
|
+
pub mod types;
|
|
8
|
+
|
|
9
|
+
pub use manifest::*;
|
|
10
|
+
pub use render::*;
|
|
11
|
+
pub use resolve::*;
|
|
12
|
+
pub use runtime_gen::*;
|
|
13
|
+
pub use types::*;
|
|
14
|
+
|
|
15
|
+
#[napi]
|
|
16
|
+
pub fn router_bridge() -> String {
|
|
17
|
+
"Zenith Router Native Bridge Connected".to_string()
|
|
18
|
+
}
|
package/src/manifest.rs
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
use crate::types::{ParsedSegment, RouteRecord, SegmentType};
|
|
2
|
+
use napi_derive::napi;
|
|
3
|
+
use std::path::Path;
|
|
4
|
+
use walkdir::WalkDir;
|
|
5
|
+
|
|
6
|
+
const STATIC_SCORE: i32 = 10;
|
|
7
|
+
const DYNAMIC_SCORE: i32 = 5;
|
|
8
|
+
const CATCH_ALL_SCORE: i32 = 1;
|
|
9
|
+
const OPTIONAL_CATCH_ALL_SCORE: i32 = 0;
|
|
10
|
+
|
|
11
|
+
pub fn discover_pages(pages_dir: &str) -> Vec<String> {
|
|
12
|
+
let mut pages = Vec::new();
|
|
13
|
+
for entry in WalkDir::new(pages_dir)
|
|
14
|
+
.into_iter()
|
|
15
|
+
.filter_map(|e| e.ok())
|
|
16
|
+
.filter(|e| {
|
|
17
|
+
e.file_type().is_file() && e.path().extension().map_or(false, |ext| ext == "zen")
|
|
18
|
+
})
|
|
19
|
+
{
|
|
20
|
+
pages.push(entry.path().to_string_lossy().to_string());
|
|
21
|
+
}
|
|
22
|
+
pages
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
pub fn file_path_to_route_path(file_path: &str, pages_dir: &str) -> String {
|
|
26
|
+
let base = Path::new(pages_dir);
|
|
27
|
+
let path = Path::new(file_path);
|
|
28
|
+
let relative = path.strip_prefix(base).unwrap_or(path);
|
|
29
|
+
|
|
30
|
+
let without_ext = relative.with_extension("");
|
|
31
|
+
let components: Vec<String> = without_ext
|
|
32
|
+
.components()
|
|
33
|
+
.map(|c| c.as_os_str().to_string_lossy().to_string())
|
|
34
|
+
.collect();
|
|
35
|
+
|
|
36
|
+
let mut route_segments = Vec::new();
|
|
37
|
+
for segment in components {
|
|
38
|
+
if segment == "index" {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if segment.starts_with("[[...") && segment.ends_with("]]") {
|
|
43
|
+
let param = &segment[5..segment.len() - 2];
|
|
44
|
+
route_segments.push(format!("*{}?", param));
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if segment.starts_with("[...") && segment.ends_with("]") {
|
|
49
|
+
let param = &segment[4..segment.len() - 1];
|
|
50
|
+
route_segments.push(format!("*{}", param));
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if segment.starts_with("[") && segment.ends_with("]") {
|
|
55
|
+
let param = &segment[1..segment.len() - 1];
|
|
56
|
+
route_segments.push(format!(":{}", param));
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
route_segments.push(segment);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let mut route_path = format!("/{}", route_segments.join("/"));
|
|
64
|
+
if route_path.len() > 1 && route_path.ends_with("/") {
|
|
65
|
+
route_path.pop();
|
|
66
|
+
}
|
|
67
|
+
route_path
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
pub fn parse_route_segments(route_path: &str) -> Vec<ParsedSegment> {
|
|
71
|
+
if route_path == "/" {
|
|
72
|
+
return Vec::new();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let segments = route_path[1..].split('/');
|
|
76
|
+
let mut parsed = Vec::new();
|
|
77
|
+
|
|
78
|
+
for segment in segments {
|
|
79
|
+
if segment.is_empty() {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if segment.starts_with('*') && segment.ends_with('?') {
|
|
84
|
+
parsed.push(ParsedSegment {
|
|
85
|
+
segment_type: SegmentType::OptionalCatchAll,
|
|
86
|
+
param_name: Some(segment[1..segment.len() - 1].to_string()),
|
|
87
|
+
raw: segment.to_string(),
|
|
88
|
+
});
|
|
89
|
+
} else if segment.starts_with('*') {
|
|
90
|
+
parsed.push(ParsedSegment {
|
|
91
|
+
segment_type: SegmentType::CatchAll,
|
|
92
|
+
param_name: Some(segment[1..].to_string()),
|
|
93
|
+
raw: segment.to_string(),
|
|
94
|
+
});
|
|
95
|
+
} else if segment.starts_with(':') {
|
|
96
|
+
parsed.push(ParsedSegment {
|
|
97
|
+
segment_type: SegmentType::Dynamic,
|
|
98
|
+
param_name: Some(segment[1..].to_string()),
|
|
99
|
+
raw: segment.to_string(),
|
|
100
|
+
});
|
|
101
|
+
} else {
|
|
102
|
+
parsed.push(ParsedSegment {
|
|
103
|
+
segment_type: SegmentType::Static,
|
|
104
|
+
param_name: None,
|
|
105
|
+
raw: segment.to_string(),
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
parsed
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
pub fn calculate_route_score(segments: &[ParsedSegment]) -> i32 {
|
|
113
|
+
if segments.is_empty() {
|
|
114
|
+
return 100;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let mut score = 0;
|
|
118
|
+
let mut static_count = 0;
|
|
119
|
+
|
|
120
|
+
for segment in segments {
|
|
121
|
+
score += match segment.segment_type {
|
|
122
|
+
SegmentType::Static => {
|
|
123
|
+
static_count += 1;
|
|
124
|
+
STATIC_SCORE
|
|
125
|
+
}
|
|
126
|
+
SegmentType::Dynamic => DYNAMIC_SCORE,
|
|
127
|
+
SegmentType::CatchAll => CATCH_ALL_SCORE,
|
|
128
|
+
SegmentType::OptionalCatchAll => OPTIONAL_CATCH_ALL_SCORE,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
score += static_count * 2;
|
|
133
|
+
score
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
pub fn route_path_to_regex_pattern(route_path: &str) -> String {
|
|
137
|
+
if route_path == "/" {
|
|
138
|
+
return r"^\/$".to_string();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let segments: Vec<&str> = route_path[1..]
|
|
142
|
+
.split('/')
|
|
143
|
+
.filter(|s| !s.is_empty())
|
|
144
|
+
.collect();
|
|
145
|
+
let mut regex_parts = Vec::new();
|
|
146
|
+
|
|
147
|
+
for segment in segments {
|
|
148
|
+
if segment.starts_with('*') && segment.ends_with('?') {
|
|
149
|
+
regex_parts.push(r"(?:\/(.*))?".to_string());
|
|
150
|
+
} else if segment.starts_with('*') {
|
|
151
|
+
regex_parts.push(r"\/(.+)".to_string());
|
|
152
|
+
} else if segment.starts_with(':') {
|
|
153
|
+
regex_parts.push(r"\/([^/]+)".to_string());
|
|
154
|
+
} else {
|
|
155
|
+
let escaped = regex::escape(segment);
|
|
156
|
+
regex_parts.push(format!(r"\/{}", escaped));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
format!(r"^{}\/?$", regex_parts.join(""))
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#[napi]
|
|
164
|
+
pub fn generate_route_manifest_native(pages_dir: String) -> Vec<RouteRecord> {
|
|
165
|
+
let pages = discover_pages(&pages_dir);
|
|
166
|
+
let mut definitions = Vec::new();
|
|
167
|
+
|
|
168
|
+
for file_path in pages {
|
|
169
|
+
let route_path = file_path_to_route_path(&file_path, &pages_dir);
|
|
170
|
+
let segments = parse_route_segments(&route_path);
|
|
171
|
+
let param_names = segments
|
|
172
|
+
.iter()
|
|
173
|
+
.filter_map(|s| s.param_name.clone())
|
|
174
|
+
.collect();
|
|
175
|
+
let score = calculate_route_score(&segments);
|
|
176
|
+
let regex = route_path_to_regex_pattern(&route_path);
|
|
177
|
+
|
|
178
|
+
definitions.push(RouteRecord {
|
|
179
|
+
path: route_path,
|
|
180
|
+
regex,
|
|
181
|
+
param_names,
|
|
182
|
+
score,
|
|
183
|
+
file_path,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
definitions.sort_by(|a, b| b.score.cmp(&a.score));
|
|
188
|
+
definitions
|
|
189
|
+
}
|