yewstack / yew_router Goto Github PK
View Code? Open in Web Editor NEWA routing library for the Yew frontend framework
License: Apache License 2.0
A routing library for the Yew frontend framework
License: Apache License 2.0
Go from
vec![BModel::RESOLVER, AModel::RESOLVER]
to
routes![BModel, AModel]
Based on saschagrunert/yew-router#1
The current nested routing solution has its place, but due to constraints of Yew, it has its drawbacks - namely that it can't support callbacks. Instead, keeping a Route Agent attached to a component could allow a RouteInfo
acquired from the agent to create an enum variant, which can then be matched on.
Proposed code could look like:
// 'Switch' because there are already enough things named 'Router'
#[derive(Switch)]
enum AppRoute {
#[at = "/"]
Home,
#[at = "/feed"] // route with no members
Feed,
#[at = "/profile/{user_id}"]
Profile { user_id: i64 }, // members matched to path segments by name
#[at = "/foo/{name}/{id}"]
Foo { name: String, id: i64 }, // multiple members matched to path segments by name
#[at = "/post/{id}"]
Post(i64), // unnamed members also work
#[at = "/bar/{name}/{id}"]
Bar(String, i64), // even multiple unnamed members - an IndexMap can be used in these cases to preserve order.
#[at = "/settings"] // sub-routes - Should these be absolute, or relative? Possibly another macro attribute?
Settings(SettingsRoute),
}
and
fn view(&self) -> Html<Self> {
match AppRoute::switch(self.route_info) {
Some(AppRoute::Home) => html!{},
Some(AppRoute::Profile{user_id)}=> html!{}
None => html!{"404"}
_ => panic!("I'm not implementing every one of these in an example")
}
}
Switch trait definition could look like:
pub trait Switch {
fn switch<T>(route_info: RouteInfo<T>) -> Option<Self>;
}
Edit: maybe Routable
as a possible name.
The macro should just take a type and produce all the aliases (and traits?) that correspond to it.
handle {*}, {4}, etc... cases
While having the Router
agent will always be necessary for some tasks, it would be nice to be able to avoid having to attach it to the Component when all the component does is handle button or link clicks.
Having components that wrap <a>
and <button>
tags that will send messages to the router when clicked would be a huge ergonomics boost.
So a macro will be needed to write implementations for converting a string into a function that extracts fields for a props struct.
So, setting expectations:
/
- segment delimiter. Matches will not be allowed across multiple delimiters under normal circumstances*
- Match anything for a given segment`.**
- Match anything for any number of segments`.:ident
- For a given segment, capture its contents and store them in the props. The ident must match a field in the props. No duplicate idents allowed.::ident
- capture the contents across multiple segments and store them (as a vector of strings?) in the props. No duplicate idents.ident
- match this "ident" exactly or reject routing.#
- Begin of hash/fragment, must come last. Same rules apply, sans the double-match operators.?
- Begin of query. This will have additional grammar rules. Namely, the pattern for ?ident=:ident
or ?ident=ident
.route!(componentA::Properties, "articles/:number"
TryFrom<String>
.So the process for matching is as follows:
Option<HashMap<String, String>>
(Or btree?)HashMap<String,String>
.Ideally it should look like after a first pass:
<Router
routes = vec![
Route<ComponentA>::new(route!("/articles/:number"))
]
/>
And as a final ideal, final state:
<Router
<Route<ComponentA>: route = route!("/articles/:number"), exact=true />
/>
https://cprimozic.net/blog/writing-a-hashmap-to-struct-procedural-macro-in-rust/
Use some intermediary trait with a blanket impl to effectively implement FromStr
for Option<T: FromStr>
and Result<T: FromStr, E>
.
I feel like the logic should be that if the value for the key exists, but can't be parsed, then return None
, but if the value does not exist for the key, then store None/Err
for the field.
The transition to the current implementation left quite a lot of old code in its wake. That should all be removed.
The prelude should just be the output of the alias macro and maybe a couple of traits.
Yew 0.10.0 is anticipated to cause a few breaking changes that will require updates to this crate.
This issue will track those changes.
A next
branch should be created to keep up with breaking changes in Yew. At least for the 0.10.0 release.
Expose a type aliased YewRouter = BaseYewRouter<()>, but allow users to define their own routers using the BaseYewRouter if they want to store state.
The current implementation works, but is rather inflexible.
It would be nice to be able to allow the component to specify its render target via nested children instead of the route!() macro. Currently though, I believe that this would mean giving up capturing sections of the url-route.
I see that there is a checkbox for the following feature in the PR for nested components: Investigate passing required properties from (P) -> (C)
.
I don't have a full understanding of what this means, but if would allow for something like:
<Parent>
<ArbitraryChild prop=parent.hashmap["key"] />
</Parent>
Then this would allow the contents of a Route to access the captures parts of the url.
instead of:
vec![
RouterOption::new::<AModel, _>(|route| a_component::Props::try_from(route).ok()),
...
]
Something like:
vec![
route!(AModel, |route| a_component::Props::try_from(route).ok() ),
...
]
Or more aspirational:
routes![
AModel => |route| a_component::Props::try_from(route).ok(),
...
]
Or as an end goal once flexible RouteOption backends are implemented:
routes![
AModel => {"/a/*", case_insensitive},
...
]
will_try_to_route()
.emit_error_if_no_routes_match
flag in the yew_router's props, which defaults to true
.This way, it is much easier to implement something as routable, while still allowing the yew_router to display nothing even if it is under another router capable of displaying a routing error.
Right now, queries have limited match support. This likely needs expanding.
Ideally, a wrapper component could exist that for children which implement a specific trait, it could set a property indicating if they are active or not depending on the current route string.
This wrapper would act similarly to the Router
that currently exists, but instead of choosing a specific route, it would call a function to set the is_active prop for any of its children that match the route.
This feature would allow the creation of navbars with buttons that indicate which page is currently active.
The trait would have two functions, one to expose a mutable reference to an Item
(Associated type?) representing the is_active
value, and another one to expose a reference to the matcher. Components would have to implement this trait in order to be children of this ActiveWrapper
.
This other type parameter will control the scoping of the messages sent between the PageNotFoundRouters and SimpleRouters.
By allowing the specification of other message types for the routers, routers with different types can't send messages to each other. This allows there to be a default router at the top level that won't receive error messages from other routers at lower levels; meaning that you can have multiple PageNotFoundRouters where only the lower one displays anything.
They fundamentally do the same thing, only that FromMatches is designed to work via a HashMap produced from nom, while afaict Properties stays within the syn ecosystem.
It might be possible to bridge the gap.
RouteString, currently residing at https://github.com/yewstack/yew_router/blob/master/src/route_info.rs#L21, is a compatibility enum used to provide access to individual parts of a route (path/query/fragment). The library currently doesn't make use of having access to individual sections of a route string, and sometimes having the ability to do so is confusing and possibly misleading.
It should be removed and instead, if needed, be replaced with a parser that can extract the path, query and fragment from a plain string. The only downside to this would be including another library to do the parsing or a general parsing library would be required to always be included in order to hand-roll a parser to do this.
Hey ๐,
@jstarry reached me out via mail and asked if we want to join our efforts to keep the yew ecosystem homogeneous. I would suggest that we join the repositories into one and use the GitHub CODEOWNERS to collaborate effectively. We also have some ideas about future improvements in mind. WDYT?
cc @Boscop
We have a RoutingService
, a Router
, a YewRouter
. I don't know if that is acceptable, but it is a little confusing at first glance. Maybe rename the Router
to RouterAgent
? YewRouter
to RouterComponent
?
Then there is the confusion around RESOLVER
and tune_props_from_route()
. I think that these should use the same naming scheme. I also don't like either of these names. A better word is needed to represent optionally creating an entity from another depending on user-defined rules.
When using <YewRouter: routes=routes![ComponentA, ComponentB],>
I would like to be able to forward state from the parent component through to ComponentA and/or ComponentB. As far as I understand, the current option is to store information via the History API, i.e. implement the RouteState trait and then construct the component's properties from the Route in resolve_props
.
Is there a more idiomatic way to do this, such that I could pass properties to routable components as properties of the YewRouter component? e.g. something along the lines of <YewRouter: routes=routes![ComponentA, ComponentB], properties=..., />
?
Yew currently doesn't drop its components when the are no longer needed.
Because of this, YewRouters sit around after another route change has invalidated them.
When the route changes again, these phantom YewRouters instantiate their respective child, but because the YewRouter isn't attached to the DOM, the child isn't either.
This child is created, and may make network requests.
If too many children are created at once due to this problem, the browser won't be able to make all the required requests at once, and will queue some of them until existing requests finish.
This is particularly bad for the responsiveness of the site.
The solution is to have Yew call drop()
on the componetns, so the bridges are dropped as well, causing the links to the RouterAgent to be disconnected.
This change will be made in yewstack/yew#288, but the router system will have strange behavior like this until a PR related to the issue is merged.
RouteInfo
is overengineered. There isn't a need to keep all portions of the route separate.
Its replacement should have String, T
as its fields, and ideally &str
s should be usable in cases where T = ()
. so setting paths could have no syntax overhead.
Create a MatchProvider enum containing the existing path matcher and a new regex holder
Allow (
)
to appear in the path matcher string. any section captured by that becomes optional when the matcher parser is built.
Mime the react router syntax of /thing/:capture(choice|choice)
.
This might be possible to do using the existing MatcherToken
s.
I have stuff like RouteBase and Route as an alias to it.
I don't like this. Instead, it should be switched to Route and SimpleRoute = Route<()>.
yewstack/yew#589 needs to be merged.
Component orphaning should be addressed yewstack/yew#604
Route syntax needs to be finished and thoroughly tested #38
Doc-comment all public facing items.
Yew 0.9 needs to be released (w/children and orphaning fixes)
Rename project to avoid colliding with yew-router crate
Integrate existing yew-router, and implement a fully working version of saschagrunert/yew-router#1
Currently, the route parser uses ()
to encode optional sections.
This Issue proposes changing optional sections to use []
instead.
As part of this change ()
would be freed up to be used in capture sections to limit what strings are able to be successfully captured.
Valid syntax would be as follows: /path/{(this|that)}
and /path/{name(this|that)}
and /path/{*:name(this|that)}
Permissive -> will not error on the parser pass. This can be used to grab routes for props that have Options in FromMatches, so all captures don't need to be present.
I'm not super keen on this one. I want to brainstorm on if there is a better way to represent optional query and fragment sections.
Strict -> Won't handle trailing '/' characters.
CaseInsensitive -> Don't care about case when matching.
Either there should be some benefit to the macro including the render function, or it should be split off into another macro.
It would be ideal if the macro could tell if there aren't corresponding named sections in the matcher for component props. Ideally this could be done at compile time, but runtime should work as well.
There isn't a need to have the matches hashmap have owned strings as keys.
<div>
instead of <>
, as they won't be cleaned up properly when re-routing if you use fragments.Someone might want to define their matcher somewhere other than in the Router body itself.
This should enable code like
<Router>
<AComponent />
<BComponent />
<Route matcher=route!(), render=component::<CComponent>() />
</Router>
If a component's props implement FromMatches and implement a new trait that allows them to get a matcher for them, then it should be possible to just nest components within a Router.
A possible concequence of this would be that the router itself isn't rendering items anymore, but is choosing a single child to delegate rendering to. This would mean that Renderable
would have to be implemented on Route
.
The new router doesn't use the blanket traits that make definitions more terse. It should be switched to that.
Macros can't be tested from the main crate due to ::crate-name
vs ::crate
name mismatches. Creating a separate crate to put macro related tests into would be better than stuffing them in doc comments to get around this restriction at the moment.
A PageNotFoundYewRouter
component would display the 404 page the normal YewRouter
does currently. The difference here would be the use of the directional message bus described here to forward a message to the PageNotFoundYewRouter
when a YewRouter
couldn't resolve a route to any of its components.
This would facilitate error handling where the top level router could route to a specific page when any other router fails to resolve its children.
By using a standard YewRouter
as the top level router, you can still specify custom 404 handlers for any depth of the route hierarchy, by placing PageNotFoundYewRouter
below it.
With the introduction of the Enum-based routing derive macro, the appeal of the Router
component seems to be diminished.
Using the Router component currently represents a trade-off: you don't have to touch the RouteAgent, and in exchange, you can't use callbacks inside of it.
Currently a Switch
ed enum requires that you have a proper update loop in place for the router agent, but allows callbacks without having to go through two component's worth of indirection.
As a proposal, I think it makes sense to combine them, taking the positive attributes from both. Resulting "html" could look like:
html! {
<Router<State, Msg, RouteEnum>
callback = From::from,
render = Router::render(|route: Option<RouteEnum>| {
match route {
Some(RouteEnum::Variant) => html!{...}
...
}
})
/>
}
The router component could keep track of the agent and the event loop, while you still get to specify the match block for your route enum.
Users can opt into the unstable feature in order to gain access to items and modules that may not be fully baked yet.
A good candidate for this would be the RouteInjector
component, because that has usability issues at the moment that might require changes.
yew-router
is taken, invalidating yew_router
.
Alternatives:
yewstack-router
yew-routing
This routing failed message would be picked up by Yew routers with the 404 role, and the default route would be displayed.
It would be a novel idea if components didn't need to keep the router around for this. Conceptually, the router never needs to be attached to the model if all it does is send things, as an instance could quickly be created and destroyed.
Code like this should be possible:
if response.did_404() {
TemporaryRouter::send(self.link, RouterRequest::RoutingFailed);
}
As part of getting the initial router implementation, I stripped away type parameters from the Router to get it to compile with a slightly defective yew build that had nested-children features. This was fixed quickly and I should reinstate the ability of Routers to take type parameters.
On a related note, I think the name of the Router should remain Router, but a type alias should be reexported at the crate root of type Router = Router<()>
because storing state along with the route should be a rare use case - users can still import the base impl.
Currently a bad quote from Star Wars Episode 3 is used to test the routing syntax.
Changing it to lorem ipsum dolor sit ...
would give them a more ... professional(?) look.
CI can be added after a 0.1 release.
Also, I probably need access to this project's settings tab, which I lost when I migrated it to yewstack.
TODO -> ask jstarry if TravisCi would be good for homogenity's sake with the main crate, or if trying out github actions would be a worthwhile endeavor.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.