Skip to content
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

State platform v2 (asynchronicity and browser-side state mutation) #150

Closed
lukechu10 opened this issue Jun 8, 2022 · 7 comments · Fixed by #242
Closed

State platform v2 (asynchronicity and browser-side state mutation) #150

lukechu10 opened this issue Jun 8, 2022 · 7 comments · Fixed by #242
Labels
author-willing-to-impl The author of this issue is willing to try to implement the solution themselves. C-enhancement Category: enhancement tribble-reported This issue was reported through Tribble.
Milestone

Comments

@lukechu10
Copy link
Contributor

This issue is requesting an enhancement to Perseus. Details of the scope will be available in issue labels.
The user described the problem related to this request as follows:

Integration with sycamore Suspense and async components

The user described the issue as follows:

Sycamore v0.8 has support for async components and Suspense. Suspense allows temporarily pausing rendering while async tasks (such as data-fetching) are executed.

Perseus already has get_build_state and I think that should stay. However, it would be nice to also have a way to integrate with Suspense and async components.

There are quite a few details that would need to be decided upon. The first one would be whether suspense is also awaited on the server side or if it is purely a client side construct. Some information on how this is handled in SolidJS (a similar JS library) is available here: https://github.com/solidjs/solid/tree/main/packages/solid-ssr

Another exciting possibility would be SSR streaming. This page (https://nextjs.org/docs/advanced-features/react-18/streaming) describes how NextJS does it with React.

Lastly, how would this work with the existing get_build_state? Should it be recommended to perform data-fetching with get_build_state or using Suspense?

I'll be willing to work on this but since the scope of this issue is so large, we'll probably need to do this in multiple steps. I can also give guidance on Sycamore's internals/implementations if needed.

  • The author is willing to attempt an implementation: true
Tribble internal data

dHJpYmJsZS1yZXBvcnRlZCxDLWVuaGFuY2VtZW50LGF1dGhvci13aWxsaW5nLXRvLWltcGw=

@github-actions github-actions bot added author-willing-to-impl The author of this issue is willing to try to implement the solution themselves. C-enhancement Category: enhancement tribble-reported This issue was reported through Tribble. labels Jun 8, 2022
@arctic-hen7
Copy link
Member

Yeah this is a great idea! I think the server side stuff should stay as is, because async is already fully supported there, and then an integration on the client side with async components should be great! Of course, we'd also need to support that when we actually render the components on the server, but that shouldn't be a problem.

As for streaming, this is also definitely something I want to set up. There's already some WS stuff on the client side for hot reloading, so similar code could be reused for this. Then it'd just be a matter of setting this up with all the server integrations.

@lukechu10
Copy link
Contributor Author

As for streaming, this is also definitely something I want to set up. There's already some WS stuff on the client side for hot reloading, so similar code could be reused for this. Then it'd just be a matter of setting this up with all the server integration

I believe we're talking about two different kinds of streaming here. What I was thinking of was something like https://dev.to/tigt/the-weirdly-obscure-art-of-streamed-html-4gc2 using HTTP chunked transfer encoding.

The way this would work (and also the way ReactJS works too IIRC) would be once a suspense boundary resolves, a snippet of html and some inline js would be sent over the stream and then sycamore would insert it into the right place.

Here's another post that describes how this works in React: reactwg/react-18#37

@arctic-hen7
Copy link
Member

I think we are talking about the same thing, but would this use the same HTTP connection stream?

If so, I reckon WS could be very useful for inbuilt realtime operations (maybe a supplementary library).

Either way I'd like to hold off on streaming until the islands system is done, because I think there'll be a lot of integration there. Async components I'm happy to go ahead with after I've finished removing .perseus/.

@lukechu10
Copy link
Contributor Author

I think we are talking about the same thing, but would this use the same HTTP connection stream?
The streaming I was thinking about would only be for the initial page loading. The WS based streaming, from what I understood, seems to be for realtime communication between client and server.

@arctic-hen7
Copy link
Member

arctic-hen7 commented Nov 11, 2022

Alright, here are my more formal thoughts on this.

  1. Perseus should not interfere with Sycamore's suspense system at all, allowing users to use it whenever it suits them. That would be evaluated as normal on the browser-side, and would just render the fallback state on the engine-side (as I believe is the current Sycamore default --- EDIT: not quite the case, see Suspense fails #225).
  2. Perseus should not support asynchronous templates, rather adding a browser-side addition to the state platform, whereby the user can register an arbitrary number of asynchronous browser-side functions that receive a reactive copy of the page state. These functions can then modify the state in the browser arbitrarily, while the main template reactively updates from user-set fallback states that can have arbitrary degrees of granularity. (E.g. a user might define a state with three different properties that require browser-side network requests, and each of these could be done in parallel, while a matrix could define a different fallback state for each of the eight possible states (2^3), if they want to).
  3. Perseus should add support for state delaying, whereby, once make_rx is converted to a derive macro (if possible), a derive macro helper #[delay_state] is defined, which can be used to annotate parts of a template's state that are particularly heavy. These can then be generated as usual on the engine-side, but they will be placed into separate JSON files on the engine-side, while placeholders referencing the URLs of those files will be placed in the actual state sent to the browser. Then, the user matches on an Option (or perhaps a custom enum) in their template, so the user receives a fallback state while heavy state is requested by the app shell.
  4. Preloading should receive a breaking change whereby preload directives must specify the state type of the preloaded template with a type parameter, allowing deserialization and reactivation of that state, along with fetching of its delayed state, which is only possible if we can get to the reactive version (since that will be where #[make_rx]'s successor will have put the function .fetch_delayed()). There should still be old-style preload directives that don't take the state type, for the rare cases in which the user does not know what they're preloading (which would mean delayed state would have to be fetched in the moment). This should be a distinct edge case, though.

This should pretty much cover everything this issue focuses on, and, for that reason, I'm going to appropriate this for v2 of Perseus' state platform, which will have a strong focus on browser-side state and asynchronicity. @lukechu10 does this basically cover everything you're talking about? I'm aware I haven't really addressed proper streaming, though I don't think this would convey any real benefit to Perseus (or any Wasm framework) at this stage, since Wasm is still unchunked (something I intend to deal with in 2023). The main network bottleneck would be getting the runtime necessary to handle streaming to the browser in the first place. Delayed state, however, does provide a very satisfactory solution (for me at least) to the problems of very large state.

There is also an argument that the render config should be fetched with a separate request, although this may require intertwining the render context with the Perseus router in a way that could be very technically complex. Hopefully, it will be fairly straightforward, and this will likely reduce the volume of bytes sent on initial page loads substantially (since the render config, which is basically an object defining all the routes in an app) has to be sent with every initial load right now.

@arctic-hen7 arctic-hen7 changed the title Integration with sycamore Suspense and async components State platform v2 (asynchronicity and browser-side state mutation) Nov 11, 2022
@arctic-hen7 arctic-hen7 added this to the v0.4.0 milestone Nov 11, 2022
@arctic-hen7
Copy link
Member

Note that this is conceptually, but not functionally, blocked by #225. Development on this will proceed even before that's fixed, if necessary.

@arctic-hen7
Copy link
Member

I have made a substantial miscalculation: delayed state is a terrible idea! As it stands, commits relating to this will be reverted and the system will not be deployed to main, let alone an actual release of Perseus. The system could only ever work with pure build state, since request-time delayed state would have to be written separately for every single request, which is not even remotely scalable. Either delayed state would only work with build state, or I implement a special in-memory store for delayed state. The latter option would massively increase the complexity of Perseus' server-side systems, and the former would increase complexity unnecessarily and add a major opportunity for confusion, while also complicating the amalgamation system greatly.

Unless there is an explicit feature request for a build-state-only version of delayed state, it will not be implemented. If anyone was counting on this feature, please let me know by opening a new issue!

arctic-hen7 added a commit that referenced this issue Nov 28, 2022
arctic-hen7 added a commit that referenced this issue Nov 29, 2022
…#242)

* feat: created `RxResult`

This should facilitate error handling very effectively in suspended state.

* feat: created suspense system

This needs a lot of cleaning up, but it works!

* feat: added nested suspense support and cleaned up

* feat: created delayed state macro

* feat(wip): progress on delayed state system

* revert: removed delayed state system entirely

See
#150 (comment)
for details.

This reverts commit 8b5da97.

* test: added tests for suspense

* feat: added request state and amalgamation to global state

* fix: fixed store errors

`NotFound` errors were being reported incorrectly, meaning apps not
using global state couldn't be compiled.

* fix: fixed test for `global_state` example

BREAKING CHANGE: global state is now built per-locale, and build state
functions therefore take a `locale: String` argument now
BREAKING CHANGE: functional plugin actions for failing global state have
been removed due to alternate generation systems (you should hook into
failed builds instead)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
author-willing-to-impl The author of this issue is willing to try to implement the solution themselves. C-enhancement Category: enhancement tribble-reported This issue was reported through Tribble.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants