-
Notifications
You must be signed in to change notification settings - Fork 147
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
Router/history library #13
Comments
My understanding is that routers have pretty universally moved to |
Advantages of
I went through several implementations of routing implementation and API, and settled on one inspired by ReasonML: The route is described by a Url struct which contains a Vec<&str> which describes the relative path, hash, search, and the (by all browsers?) unimplemented Initial routing is fed through the 'routes' function. Two ways to initialize routing. B: Any element given a 'href' attribute, will trigger routing according to the |
Implementation details: We use serde to serialize the I'm not sure the best way to make this standalone, since it involves calling a framework-specific update function. Overall, need to address what Gloo's endpoints are before adding a PR. |
Perhaps it should return a |
I don't know much about Futures/Stream, but doesn't it imply a (potential) delay? Doesn't seem semantically appropriate. Maybe an Update, or RouteUpdate trait? Might be too opinionated. |
There is a 1-tick delay when you start listening to updates, but there isn't any delay when an update occurs. I think the semantics match very well. Another option is to make the URL a Signal, which also matches very well. |
I mean, Futures, as described on this page, are intended to be used for something that may be slow like network requests, which routing is not. My understanding of Futures/signals are both weak, but I get the impression this could be a drop-in replacement where we currently call the framework-specific update function. Need to figure out how to replace all occurrences of Also need to verify that search/hash is working properly, as I'm not sure how they're intended to be used. Eg using |
Futures and Streams have nothing to do with "slowness", they have to do with asynchronous values. An asynchronous value is a value which isn't here now, but will be here in the future. By that definition, of course a router update is asynchronous. There are plenty of Futures which are extremely fast (including Futures like As for Streams, they're basically a way of retrieving multiple asynchronous values, which makes them perfect for router updates (since there can be multiple router updates, and each update is asynchronous). |
Based on your description, perhaps Futures/Stream can be applied more broadly in how Gloo modules interact with each other and outside code... |
Sorted using generics |
Ref #26 SummaryAdd a frontend router, based on a MotivationFirst router proposal for Gloo Detailed ExplanationI propose adding a router similar to the one described in the PR above. It uses a An additional endpoint is a function which stores a A (more unconventional, and perhaps controversial) API is also availible, which allows any element with an Overall, I think the most appealing part of this approach is its external API. Drawbacks, Rationale, and AlternativesThere are other ways to design a router; by nature this is opinionated, and there may be improvements, or better designs proposed. Some framework styles may not work well with the generic types used as endpoints. Applying This approach is inspired by reason-react's router and designed to avoid mistakes that make Unresolved QuestionsI have a poor grasp on how |
Thanks for writing this up @David-OConnor! First off, I'm not super familiar with client side routers and the design space, so I appreciate y'all's patience with me :) I have a few questions:
What are the other designs? What are their pros and cons? I don't feel like this proposal quite gives me enough to have an informed position on the topic. If there are multiple high-level designs we could potentially implement, can they all be implemented well on top of our just-above-
Which mistakes are these? Do they consider it a mistake that they keep around for backwards compatibility? (Want to make sure we aren't "simplifying" an API by accidentally making valid use cases unsupportable.) |
My thoughts are mixed on the Re splitting into layers: What immediately comes to mind is the recent discussion about event systems. This (and probably most/all) routing approaches involve a popstate listener. If we could expose a higher-level layer instead of web_sys::window()
.expect_throw("Can't find window")
.history()
.expect_throw("Can't find history")
.push_state_with_url with a cleaner API. Other routing approaches that come to mind: parsing URLs using An alternative for the
My biggest concern: There are probably use-cases my proposal doesn't handle elegantly, or at all, which I haven't thought of. There are probably better ways to design the API and internal code, that I reject for no reason other than not being aware of them. My experience with routing is limited: We need other opinions on this, from people experienced with different styles of routing who can better criticize this plan, and offer improvements/alternatives. I'd specifically like feedback from someone who uses |
There is an advantage of that: it allows you to have a header/footer that is the same on all pages, thus only the middle part of the page changes on route changes. I don't have strong opinions about routing, since I personally haven't used routing much. So take what I say with a grain of salt. |
@David-OConnor I don't like the names I guess this would work: fn push_state<U: Into<Url>>(url: U) |
Actually, the history API allows you to omit the URL when calling Here are the things of the History API that aren't covered yet:
|
When you call Another question: Do we even need to parse URLs? This is already done by the browser in window.location |
Agree on generic In the PR I submitted, URL parsing is only required for the The if let Some(hash) = url.hash {
location
.set_hash(&hash)
.expect("Problem setting hash");
}
if let Some(search) = url.search {
location
.set_search(&search)
.expect("Problem setting search");
} Or when setting the let mut path = String::from("/") + &url.path.join("/");
if let Some(hash) = url.hash {
path += "#" + &hash;
} |
I thought Firefox used
I wasn't sure what you meant, so I looked into your PR (which was reverted): When |
Hi! -- I just saw this thread, and have done quite a bit with the History API in the past, so this is an attempt at sharing some of my experiences. I hope this can be of help for Gloo's design! Events / HooksIn Choo we ended up having about 5 events for interacting with the router (ref impl):
Even if Gloo's router might not be event-based, this should cover the base functionality for interacting with the browser's history API. Triggering route changesThe way people would trigger route changes was actually kind of nice: instead of using custom types that needed to be passed around, we declared a global listener for all click events, and would figure out if it came from an anchor tag pointing to an address on the same host. In practice this meant that connecting to the router was as simple as: app.view('/my-route', (state, emit) => {
html`
<body>
<a href="/other-route">Go to other route</a>
</body>
`
}) This turned out to be a rather nice model to work with, and people often seemed pleasantly surprised at how little boilerplate was required to get things to work. Other NotesRegarding hash routing: we added optional support for it in Choo (by setting an option), but I think in general it's not a hard requirement. Especially when combined with a server that was able to handle view rendering on the same routes as the browser. Which brings me to another point: it's good to think about ways of exporting the routes declared in Gloo, so they can be used in servers. The exact ways of doing this don't matter too much, as long as it's something that's considered from the start. If not it can become tedious to sync routes between two code bases. And finally: we've experimented a lot with the state object in the browser's history API, but it ended up being rather clunky to work with. In the end just keeping application state outside of it was much easier to reason about, and lead to fewer bugs. We probably didn't quite nail the UX for changing titles, but there's probably a way of using traits to make that a bit nicer. ConclusionI by no means have all answers for how we should design a router in Rust. But I hope that sharing my experiences of building a routing story in JS can help in Gloo's design. I hope this is helpful! |
What changes would y'all like to make this happen? Or are we waiting for alternative designs/implementations to compare with? |
To me, I still have a little bit of fuzziness with the design, and I think the proposal would benefit from an explicit API skeleton that we've been formalizing in #63 and #65 However: I'm not super familiar with client-side routing (at this point I have more experience with "client" / Web frontend programming inside the firefox devtools rather than with a Web page talking to a server, and it isn't quite the same even though it is all Web technologies in both cases; e.g. we never needed routing in the devtools). Therefore, I think it would be helpful if some other Gloo team members could step up and be the design reviewers for this proposal. @yoshuawuyts @Pauan would you two be willing to do that? |
@fitzgen would be happy to! -- I like the proposal skeleton we have a lot (: |
I'd like whatever gloo decides for routing to be modular. One part of that for me is some macros for generating maps from I'm doing some experimenting along these lines (see https://github.com/derekdreery/route-exp/blob/master/examples/simple.rs). That's kinda how I would like it to look. Take a look and tell me what you think of the design. (I haven't implemented parsing yet, only |
At the request of another reddit user, I'm posting my project here: I wouldn't say it's stabilized yet, but feel free to either use it directly or take pieces of the code that may be helpful. I especially don't like the It's interesting to see an enum approach (in |
I'm also having a play in this space: I've published Eventually we'd want to unify all these efforts, but for now I think it's good to experiment. I've also been playing with implementing Also I have to say that I love syn. You can parse rust very easily. The |
Thoughts on getting a not-perfect-but workable module released? |
When the WebSocket is used with frameworks, passed down as props, it might be `drop`ed automatically, which closes the WebSocket connection. Initially `Clone` was added so sender and receiver can be in different `spawn_local`s but it turns out that `StreamExt::split` solves that problem very well. See #13 for more information about the issue
* Initial commit * provide a better interface for errors, rename `RequestMethod` to `Method` * remove method for array buffer and blob in favor of as_raw * prepare for release * add CI, update readme * hide JsError in the docs * fix CI? * Install wasm-pack in CI * misc * websocket API Fixes: ranile/reqwasm#1 * add tests for websocket * update documentation, prepare for release * fix mistake in documentation * Rewrite WebSockets code (#4) * redo websockets * docs + tests * remove gloo-console * fix CI * Add getters for the underlying WebSocket fields * better API * better API part 2 electric boogaloo * deserialize Blob to Vec<u8> (#9) * Update to Rust 2021 and use JsError from gloo-utils (#10) * Update to Rust 2021 and use JsError from gloo-utils * use new toolchain * Add response.binary method to obtain response as Vec<u8> Fixes: ranile/reqwasm#7 * Remove `Clone` impl from WebSocket. When the WebSocket is used with frameworks, passed down as props, it might be `drop`ed automatically, which closes the WebSocket connection. Initially `Clone` was added so sender and receiver can be in different `spawn_local`s but it turns out that `StreamExt::split` solves that problem very well. See #13 for more information about the issue * Rustfmt + ignore editor config files * Fix onclose handling (#14) * feat: feature gate json, websocket and http; enable them by default (#16) * feat: feature gate json support * feat: feature gate weboscket api * ci: check websocket and json features seperately in CI, check no default features * feat: feature gate the http API * refactor: use futures-core and futures-sink instead of depending on whole of futures * ci: test http feature seperately in CI * fix: only compile error conversion funcs if either APIs are enabled * fix: add futures to dev-deps for tests, fix doc test * 0.3.0 * Fix outdated/missing docs * 0.3.1 * Change edition from 2021 to 2018 (#18) * Change edition from 2021 to 2018 * Fix missing import due to edition 2021 prelude * hopefully this will fix the issue (#19) * There's no message * Replace `async-broadcast` with `futures-channel::mpsc` (#21) We no longer need a multi-producer-multi-consumer channel. There's only one consumer as of ranile/reqwasm@445e9a5 * Release 0.4.0 * Fix message ordering not being preserved (#29) The websocket specification guarantees that messages are received in the same order they are sent. The implementation in this library can violate this guarantee because messages are parsed in a spawn_local block before being sent over the channel. When multiple messages are received before the next executor tick the scheduling order of the futures is unspecified. We fix this by performing all operations synchronously. The only part where async is needed is the conversion of Blob to ArrayBuffer which we obsolete by setting the websocket to always receive binary data as ArrayBuffer. * 0.4.1 * move files for gloo merge * remove licence files * gloo-specific patches * fix CI * re-export net from gloo Co-authored-by: Michael Hueschen <[email protected]> Co-authored-by: Stepan Henek <[email protected]> Co-authored-by: Yusuf Bera Ertan <[email protected]> Co-authored-by: Luke Chu <[email protected]> Co-authored-by: Valentin <[email protected]>
Needs some exploratory survey and design work!
#
fragment?pushState
and history API integration?The text was updated successfully, but these errors were encountered: