feat: AGENTS.md and architecture guides for agents!
This commit is contained in:
1 parent
2ee8ecd477
commit
638c02e15a
12 files changed
+3324
No files matched your search
@@ -0,0 +1,363 @@
|
||||
# Dioxus Router Architecture
|
||||
|
||||
The Dioxus router provides type-safe, declarative routing with support for nested layouts, dynamic parameters, and platform-agnostic navigation.
|
||||
|
||||
## Route Definition
|
||||
|
||||
### Routable Trait
|
||||
|
||||
Routes are defined using Rust enums with the `#[derive(Routable)]` macro:
|
||||
|
||||
```rust
|
||||
#[derive(Routable, Clone, PartialEq, Debug)]
|
||||
enum Route {
|
||||
#[route("/")]
|
||||
Home {},
|
||||
#[route("/blog/:blog_id")]
|
||||
Blog { blog_id: usize },
|
||||
#[route("/edit?:blog_id")]
|
||||
Edit { blog_id: usize },
|
||||
#[route("/hashtag/#:hash")]
|
||||
Hash { hash: String },
|
||||
}
|
||||
```
|
||||
|
||||
The `Routable` trait provides:
|
||||
- **`const SITE_MAP`**: Static map of all route segments
|
||||
- **`render(level: usize) -> Element`**: Renders component at nesting level
|
||||
- **`FromStr` implementation**: Parses URLs into route enums
|
||||
- **`Display` implementation**: Converts routes to URLs
|
||||
- **Helper methods**: `is_child_of()`, `parent()`, `static_routes()`
|
||||
|
||||
### Route Matching Priority
|
||||
|
||||
1. Query routes (`/?:query`)
|
||||
2. Static routes (`/route`)
|
||||
3. Dynamic routes (`/:route`)
|
||||
4. Catch-all routes (`/:..route`)
|
||||
|
||||
### Segment Types
|
||||
|
||||
- **Static**: Literal path segments like `/blog`
|
||||
- **Dynamic**: Single parameters like `/:id` (implements `FromRouteSegment`)
|
||||
- **CatchAll**: Multiple segments like `/:..segments` (implements `FromRouteSegments`)
|
||||
|
||||
### Query Parameters
|
||||
|
||||
- **Spread query**: `/?:..query` (entire query string, uses `FromQuery`)
|
||||
- **Segmented query**: `/?:param1&:param2` (individual params, use `FromQueryArgument`)
|
||||
|
||||
### Hash Fragments
|
||||
|
||||
`/#:hash_segment` (uses `FromHashFragment`)
|
||||
|
||||
## Navigation
|
||||
|
||||
### RouterContext
|
||||
|
||||
Manages navigation state with these core methods:
|
||||
|
||||
```rust
|
||||
impl RouterContext {
|
||||
// Navigation
|
||||
pub fn push(&self, target: impl Into<NavigationTarget>);
|
||||
pub fn replace(&self, target: impl Into<NavigationTarget>);
|
||||
|
||||
// History
|
||||
pub fn go_back(&self);
|
||||
pub fn go_forward(&self);
|
||||
pub fn can_go_back(&self) -> bool;
|
||||
pub fn can_go_forward(&self) -> bool;
|
||||
|
||||
// Introspection
|
||||
pub fn current<R: Routable>(&self) -> R;
|
||||
pub fn full_route_string(&self) -> String;
|
||||
pub fn prefix(&self) -> Option<String>;
|
||||
|
||||
// Site map
|
||||
pub fn site_map(&self) -> &'static [SiteMapSegment];
|
||||
}
|
||||
```
|
||||
|
||||
The router context is:
|
||||
1. Created in `Router` component with `RouterConfig`
|
||||
2. Provided via Dioxus context to children
|
||||
3. Uses **signals** for reactive updates
|
||||
4. Tracks **subscribers** for re-rendering
|
||||
5. Integrates with **history provider**
|
||||
|
||||
### NavigationTarget
|
||||
|
||||
```rust
|
||||
pub enum NavigationTarget<R = String> {
|
||||
Internal(R), // Route parsed by router
|
||||
External(String), // External URL (browser handles)
|
||||
}
|
||||
```
|
||||
|
||||
## Link Component
|
||||
|
||||
Declarative navigation:
|
||||
|
||||
```rust
|
||||
Link {
|
||||
to: NavigationTarget,
|
||||
active_class: Option<String>, // Applied when matches current
|
||||
class: Option<String>,
|
||||
new_tab: bool,
|
||||
onclick: Option<EventHandler>,
|
||||
children: Element,
|
||||
}
|
||||
```
|
||||
|
||||
**Behavior**:
|
||||
1. Accesses router without subscribing
|
||||
2. Checks if target matches current for `active_class`
|
||||
3. On click (left button, no modifiers):
|
||||
- Prevents default
|
||||
- Routes via `router.push_any(target)`
|
||||
- Executes optional onclick
|
||||
4. Generates `<a>` tag with `href` for SEO
|
||||
5. External links use default browser navigation
|
||||
|
||||
## Nested Routes
|
||||
|
||||
### Layouts and Outlets
|
||||
|
||||
```rust
|
||||
#[derive(Routable)]
|
||||
enum Route {
|
||||
#[nest("/admin")]
|
||||
#[layout(AdminLayout)]
|
||||
#[route("/")]
|
||||
AdminHome {},
|
||||
#[route("/users")]
|
||||
Users {},
|
||||
#[end_layout]
|
||||
#[end_nest]
|
||||
|
||||
#[route("/")]
|
||||
Home {},
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn AdminLayout() -> Element {
|
||||
rsx! {
|
||||
header { "Admin Panel" }
|
||||
Outlet::<Route> {} // Children render here
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Outlet Component
|
||||
|
||||
- Renders child routes at correct nesting depth
|
||||
- Uses `OutletContext` to track depth level
|
||||
- Works with layout wrapping via macro
|
||||
|
||||
### VirtualDOM Integration
|
||||
|
||||
1. `Routable::render(level)` matches route at nesting level
|
||||
2. Each layout increments nesting level
|
||||
3. Router changes mark context-accessing components dirty
|
||||
4. Outlet at correct depth renders matched component
|
||||
|
||||
## Route Parameters
|
||||
|
||||
### Parameter Traits
|
||||
|
||||
| Component | Trait | Example |
|
||||
|-----------|-------|---------|
|
||||
| Path segment | `FromRouteSegment` / `ToRouteSegment` | `/users/:id` |
|
||||
| Multiple segments | `FromRouteSegments` / `ToRouteSegments` | `/files/:..path` |
|
||||
| Query full | `FromQuery` | `/?:q=search` |
|
||||
| Query arg | `FromQueryArgument` | `/?page=1&sort=name` |
|
||||
| Hash | `FromHashFragment` | `/#section-id` |
|
||||
|
||||
### Auto-Implementation
|
||||
|
||||
Traits auto-implement for types with standard traits:
|
||||
- `FromRouteSegment`: `FromStr + Default`
|
||||
- `FromQueryArgument`: `FromStr + Default`
|
||||
- `FromQuery`: `From<&str>`
|
||||
- `FromHashFragment`: `FromStr + Default`
|
||||
|
||||
### URL Encoding
|
||||
|
||||
- Path segments: `PATH_ASCII_SET` (excludes `/`, `?`, `#`)
|
||||
- Query strings: `QUERY_ASCII_SET` (excludes `&`, `=`)
|
||||
- Hash fragments: `FRAGMENT_ASCII_SET`
|
||||
- Auto encode/decode during parse/display
|
||||
|
||||
## Router Macro
|
||||
|
||||
### Code Generation
|
||||
|
||||
`#[derive(Routable)]` generates three implementations:
|
||||
|
||||
**1. FromStr Implementation**:
|
||||
```rust
|
||||
impl std::str::FromStr for Route {
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
// Split URL into path, query, hash
|
||||
// Percent-decode segments
|
||||
// Try matching against all routes (specificity order)
|
||||
// Return error with attempted routes if no match
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**2. Display Implementation**:
|
||||
```rust
|
||||
impl std::fmt::Display for Route {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
match self {
|
||||
Route::Blog { blog_id } => {
|
||||
write!(f, "/blog/{}", blog_id)?;
|
||||
// Percent-encode as needed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**3. Routable Trait Implementation**:
|
||||
```rust
|
||||
impl Routable for Route {
|
||||
const SITE_MAP: &'static [SiteMapSegment] = &[...];
|
||||
|
||||
fn render(&self, level: usize) -> Element {
|
||||
match (level, self) {
|
||||
(0, Self::Blog { .. }) => rsx! { Layout { Outlet {} } }
|
||||
(1, Self::Blog { blog_id }) => rsx! { Blog { blog_id: *blog_id } }
|
||||
_ => VNode::empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Error Type Generation
|
||||
|
||||
Each route generates detailed error enum:
|
||||
```rust
|
||||
pub enum RouteMatchError {
|
||||
Home(HomeParseError),
|
||||
Blog(BlogParseError),
|
||||
BlogIdParseError(ParseIntError),
|
||||
}
|
||||
```
|
||||
|
||||
### Segment Parsing
|
||||
|
||||
```rust
|
||||
pub enum RouteSegment {
|
||||
Static(String), // "/blog" -> Static("blog")
|
||||
Dynamic(Ident, Type), // "/:id" -> Dynamic(id, u32)
|
||||
CatchAll(Ident, Type), // "/:..paths" -> CatchAll(paths, Vec<String>)
|
||||
}
|
||||
```
|
||||
|
||||
**Process**:
|
||||
1. Split URL into path, query, hash
|
||||
2. Remove trailing slashes
|
||||
3. Percent-decode components
|
||||
4. Split path by `/`
|
||||
5. Classify segments (static/dynamic/catch-all)
|
||||
6. Match field names to route parameters
|
||||
7. Validate types exist in struct fields
|
||||
|
||||
## History Abstraction
|
||||
|
||||
### History Trait
|
||||
|
||||
Platform abstraction for navigation:
|
||||
|
||||
```rust
|
||||
pub trait History {
|
||||
fn current_route(&self) -> String;
|
||||
fn current_prefix(&self) -> Option<String>;
|
||||
|
||||
fn can_go_back(&self) -> bool;
|
||||
fn can_go_forward(&self) -> bool;
|
||||
fn go_back(&self);
|
||||
fn go_forward(&self);
|
||||
|
||||
fn push(&self, route: String);
|
||||
fn replace(&self, path: String);
|
||||
fn external(&self, url: String) -> bool;
|
||||
|
||||
fn updater(&self, callback: Arc<dyn Fn() + Send + Sync>);
|
||||
fn include_prevent_default(&self) -> bool;
|
||||
}
|
||||
```
|
||||
|
||||
### MemoryHistory
|
||||
|
||||
In-memory fallback implementation:
|
||||
|
||||
```rust
|
||||
pub struct MemoryHistory {
|
||||
state: RefCell<MemoryHistoryState>,
|
||||
base_path: Option<String>,
|
||||
}
|
||||
|
||||
struct MemoryHistoryState {
|
||||
current: String,
|
||||
history: Vec<String>, // For back
|
||||
future: Vec<String>, // For forward
|
||||
}
|
||||
```
|
||||
|
||||
**Behavior**:
|
||||
- Default starts at `/`
|
||||
- `push`: Adds current to history, sets new as current, clears future
|
||||
- `replace`: Only changes current
|
||||
- `go_back/forward`: Swaps between stacks
|
||||
- Does not navigate external URLs
|
||||
|
||||
**Use Cases**:
|
||||
- Testing/SSR without browser history
|
||||
- In-memory fullstack navigation
|
||||
- Development without browser
|
||||
|
||||
### Platform Implementations
|
||||
|
||||
**Web**: Browser History API, popstate events
|
||||
**Desktop**: Platform-specific window/navigation APIs
|
||||
**LiveView**: WebSocket with server-managed state
|
||||
|
||||
## VirtualDOM Integration
|
||||
|
||||
1. **Initialization**:
|
||||
- `Router` creates `RouterContext` with config
|
||||
- Provides to descendants via context
|
||||
- Gets current route from history
|
||||
- Syncs with history if needed
|
||||
|
||||
2. **URL Change**:
|
||||
- `RouterContext::push_any(NavigationTarget)` called
|
||||
- History provider's method invoked
|
||||
- `change_route()` callback fires
|
||||
- Subscribers marked dirty
|
||||
|
||||
3. **Re-render**:
|
||||
- Components using `use_router()`/`use_navigator()` marked dirty
|
||||
- `Routable::render(level)` generates new tree
|
||||
- Layouts render at nesting levels
|
||||
- `Outlet` renders final component
|
||||
|
||||
4. **Parameter Passing**:
|
||||
- Route parameters part of enum variant
|
||||
- Passed as component props
|
||||
- Type-safe at compile time
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
1. **Trait-Based Extensibility**: Custom types implement parameter traits
|
||||
2. **Type Safety**: Routes are enums - invalid routes are compile errors
|
||||
3. **Macro-Driven**: Reduces boilerplate, efficient matching
|
||||
4. **Platform Abstraction**: History trait enables different renderers
|
||||
5. **Reactive Updates**: Signals and subscriptions for efficient re-renders
|
||||
6. **Nested Layouts**: Outlets allow composition without complexity
|
||||
7. **URL Encoding**: Automatic percent-encoding/decoding
|
||||
8. **SEO-Friendly**: Links generate real `<a>` tags with hrefs
|
||||
Reference in new issue
Block a user