-
Notifications
You must be signed in to change notification settings - Fork 84
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Routing #28
Comments
So I've been adding things as I've needed them. Haven't needed a router yet since I've only been building a home page, but I certainly will soon. Funny enough yesterday I started googling around to see what routers already exist (without much luck). I'm still thinking about this, but super curious if you already have any ideas about how routing should look? Regardless this can be the tracking issue for routing (whether we implement it in |
I'd look at |
Very, very interesting, will definitely take a look at So in a site that I'm working on I've been experimenting with how aRust frontend web app could be organized. One thing that I'm toying with at the moment is just having a single I pass this I also pass in I haven't used this pattern enough to know if it scales well or not, but one thing I have in mind here... just providing a starting point for what I'm about to say: So in this scenario.. I'm imagining that when you visit a route the router parses the URL / query string into a
Params are stored in state (or maybe view utilities.. idk..)
And on page load as well as anytime you changed routes (clicked an anchor tag or changed programmatically) we'd update the A pattern that I've been using lately in frontend apps has been Example: So I could imagine that when you clicked an anchor tag or to change programmatically it'd just be Granted this is off the top of my head so I'd imagine that I'm missing a ton of considerations! Curious about your thoughts? |
The two options I see:
The first way is simpler and will always keep in sync. |
Oooo yeah option 2 would be be incredibly useful for stuff like admin routes. Until I read up on How might option 2 look as a first implementation? What about as something fully featured? Trying to get a better picture in my head. But yeah I'll read up on a few popular backend routers that sounds so much better on paper! |
Did some reading up on rocket’s routing and boy is that awesome. We can take a lot of inspiration from that for sure. I’m traveling this next week and a half so not sure of the exact timing... but I’m going to get some notes together one what a first implementation of a view router might look like and share them here. |
Starting sketching out how this might look here -> https://chinedufn.github.io/percy/router/index.html Feeling about 60% of the way there.. doesn't feel quite ergonomic yet but close.. nailing down the details.. But even still your feedback on the general direction / look and feel would be highly appreciated! |
Looks really good. #[route(path = "/posts/{post_id/edit", before = IsAdmin)] should be #[route(path = "/posts/{post_id}/edit", before = IsAdmin)] not sure I like |
Ah thanks for catching that typo And I like the :post_id good idea 👍🏿 |
Here's a breakdown of how the procedural macro for |
I'm experimenting with this awesome project to build a full stack Rust app, and I have to say, great work so far. I just wanted to ask, is the current implementation supposed to use the history push API to alter the location path? If not, are there plans for this? Also, I had to submodule the repo into my project because a few of the isomorphic app example's modules weren't published yet. I understand the project is still in an early state, but I have to say, great work so far, you have my encouragement, and if you're looking for donations to continue your work here, I'm sure there are ways to arrange that with OpenCollective or Patreon or similar. |
@cryptoquick thanks for all of those very kind words - so, so kind!! And thanks a lot for all of the contributions that you made today! Yeah - the routing story is incomplete. I'm using Percy for the website for a game that I'm working on so whenever I'm working on that website I end up working on Percy but whenever I'm working on gameplay progress slows a bit. So - the story here is that I haven't sat down to dig into routing quite yet. What you see so far was more of an initial exploration. WRT the history API. You're spot on that the isomorphic app example doesn't currently use it. This is only because I quickly added the routing part in a hotel room at the Rust Belt Rust conference and didn't add in the history API. I do plan to beef up the example with that. In general you're making me realize that it would be useful to lay out a roadmap for Percy so that people can see what's coming up and are also more empowered to contribute! Sorry about some of those modules not being published yet. If it helps you can use # In your Cargo.toml
router-rs = { git = "https://github.com/chinedufn/percy } Also - please continue to feel very free to open issues because that helps with prioritizing what to do. Absolutely pumped that you're experimenting with using Percy for a full stack Rust app and I'd love to do whatever is possible to make that experimentation easier! |
Alright I'm diving into the router. Here's the test case that I'm currently working on. Will try and get a PR out ASAP. WRT to the history API in the isomorphic example - I'll get that updated in the PR as well. Thanks for the nudge @cryptoquick ! |
Very cool! Also, I did something that I'm not super happy with, but I wanted it for type-correctness and consistency as well as just editor hints, and that was to use this following format for routes with the current router: use router_rs::prelude::*;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use virtual_dom_rs::prelude::*;
use crate::store::*;
use crate::views::*;
// TODO: Is there a better way to do this? To deduplicate this?
pub enum ActivePage {
Home,
Management,
Contractors,
Report,
}
pub fn get_path(page: &ActivePage) -> &'static str {
match page {
ActivePage::Home => "/",
ActivePage::Management => "/management",
ActivePage::Contractors => "/contractors",
ActivePage::Report => "/report",
}
}
pub fn get_page(path: &str) -> ActivePage {
match path {
"/" => ActivePage::Home,
"/management" => ActivePage::Management,
"/contractors" => ActivePage::Contractors,
"/report" => ActivePage::Report,
&_ => ActivePage::Home,
}
}
pub fn make_router(store: Rc<RefCell<Store>>) -> Router {
let mut router = Router::default();
let store_clone = Rc::clone(&store);
let param_types = HashMap::new();
let home_route = Route::new(
"/",
param_types,
Box::new(move |_params| Box::new(HomeView::new(Rc::clone(&store_clone))) as Box<View>),
);
let store_clone = Rc::clone(&store);
let param_types = HashMap::new();
let contractors_route = Route::new(
"/contractors",
param_types,
Box::new(move |_params| {
Box::new(ContractorsView::new(Rc::clone(&store_clone))) as Box<View>
}),
);
let store_clone = Rc::clone(&store);
let param_types = HashMap::new();
let management_route = Route::new(
"/management",
param_types,
Box::new(move |_params| {
Box::new(ManagementView::new(Rc::clone(&store_clone))) as Box<View>
}),
);
let store_clone = Rc::clone(&store);
let param_types = HashMap::new();
let report_route = Route::new(
"/report",
param_types,
Box::new(move |_params| Box::new(ReportView::new(Rc::clone(&store_clone))) as Box<View>),
);
router.add_route(home_route);
router.add_route(management_route);
router.add_route(contractors_route);
router.add_route(report_route);
router
} This code isn't ideal, since each route is duplicated in three places, but hopefully you see where I'm going with it. Also, just know, this isn't terribly open source code, but I'm providing it as an example of what an application developer might desire when working with Rust, and expecting their editor and their compiler from making mistakes with routes. Also, I'm not sure how it would work if I wanted to provide parameters to routes, like in your case. It might be interesting to experiment with user-implemented router methods, perhaps. The desire is for something that feels Rust-idiomatic, without a lot of repetition, and also, provides the route parameter flexibility desired in the near future. Feel free to break the API, that's fine when using software like this, and I already have the repo submoduled in so I can test changes right away. I'm super happy you're involved and working on this. There's a lot of good energy here that isn't in yew and other projects. I consider this to be best-in-class if you want to build full-stack Rust. BTW, I would suggest changing the phrasing, "isomorphic", since it's been brought up a couple of times on its overuse and lack of descriptiveness, and I even was really tired the other day and called it "isometric". I think "Full Stack Rust" is a much better way to describe the thing that's being worked on here. The only problem will come when it comes time to deploy, since there's little serverless support out there. It'd be sort of interesting to tackle serverless Rust with V8 Isolates like CloudFlare is doing, but they have their own fetch interface, which obviates much of what's already built for backend modules in the Rust ecosystem that relies on lower-level OS-facing modules like |
@cryptoquick thanks for sharing! It's super useful to see how you're using it so that I can factor real use cases into the design!! I have tests passing for route parameters right now - here's how it looks. #![feature(proc_macro_hygiene)]
use router_rs::prelude::*;
use router_rs_macro::{create_routes, route};
use virtual_node::{VirtualNode,VText};
// No Params
#[route(path = "/")]
fn no_params() -> VirtualNode {
VirtualNode::Text(VText::new("hello world"))
}
#[test]
fn root_path() {
let mut router = Router::default();
router.set_route_handlers(create_routes![no_params]);
assert_eq!(
router.view("/").unwrap(),
VirtualNode::Text(VText::new("hello world"))
);
}
// Route With One Param
#[route(path = "/:id")]
fn route_one_param(id: u32) -> VirtualNode {
VirtualNode::Text(VText::new(format!("{}", id).as_str()))
}
#[test]
fn one_param() {
let mut router = Router::default();
router.set_route_handlers(create_routes![route_one_param]);
assert_eq!(
router.view("/10").unwrap(),
VirtualNode::Text(VText::new("10"))
);
}
// Route With Two Params
#[route(path = "/user/:user_id/buddies/:buddy_id")]
fn route_two_params(user_id: u64, buddy_id: u64) -> VirtualNode {
VirtualNode::Text(VText::new(format!("User {}. Buddy {}", user_id, buddy_id).as_str()))
}
#[test]
fn two_params() {
let mut router = Router::default();
router.set_route_handlers(create_routes![route_two_params]);
assert_eq!(
router.view("/user/50/buddies/90").unwrap(),
VirtualNode::Text(VText::new("User 50. Buddy 90"))
);
} The next test case that I'm implementing (doesn't work yet) looks like this - and I think will solve what you're mentioning about providing data. It's inspired by Rocket's managed state. // Route with Provided Data
struct State {
count: u8
}
#[route(path = "/")]
fn route_provided_data(state: ProvidedData<State>) -> VirtualNode {
VirtualNode::Text(VText::new(format!("Count: {}", state.count).as_str()))
}
#[test]
fn provided_data() {
let mut router = Router::default();
router.provide(State {count: 50});
router.set_route_handlers(create_routes![route_provided_data]);
assert_eq!(
router.view("/").unwrap(),
VirtualNode::Text(VText::new("Count: 50"))
);
} WRT to the name isomorphic - I've opened #93 to address that |
Very cool. Let me know the code's available so I can refactor. Also, another thing comes to mind: The state management pattern is starting to become a little odious in my app. It's very much like a naive but idiomatic Redux implementation, and although there is redux-rs, I think it might be more valuable to make a Percy-specific React Hooks implementation; specifically, for useState, useReducer, and useEffect. I'm considering taking that on. I might make an issue for that, if you think that'd be good. |
I haven't used react hooks so I'm a bit ignorant to the value prop (did some quick reading just now). In general I have a fear of introducing concepts that people need to learn in order to be productive. In general I've gotten a lot of mileage out of React (we use it at my day job) by pretty much ignoring lots of the bells and whistles in the ecosystem and just having one global state object that gets passed around into every component (along with any smaller stuff that a parent component might choose to pass to a chid). The simplicity of this makes it easy to be productive without needing to learn concepts that get churned when new concepts get popular. That being said I haven't used hooks so I'm certainly curious to hear more about what you think could be of use here! So yeah - I'm always open to better ideas! Feel free to open an issue explaining the problems that you're running into and how you think we can solve them!!! |
Alright I've got things working nicely. You can have unlimited route parameters and unlimited state. Here's a screenshot with one route parameter and one state type to illustrate how it's used. Now I'll need to clean up the code and update the example ( that I will no longer refer to as isomorphic (; ) |
Neat! Set up a branch & PR, I'm eager to try it out! |
@cryptoquick mind giving #95 a quick read over? Once that's merged routing should be much nicer! |
Any ideas about routing?
It is an essential part, and strongly informs how we work with the server since we are aiming for isomorphicity.
The text was updated successfully, but these errors were encountered: