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

use_prepared_state & use_transitive_state #2650

Merged
merged 38 commits into from May 24, 2022
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
7aa57b0
Some initial implementation.
futursolo Apr 18, 2022
94651f5
Read prepared state during hydration.
futursolo Apr 24, 2022
e7a55a3
Decode each state with bincode.
futursolo Apr 24, 2022
0b378d4
Feature gate prepared state.
futursolo Apr 24, 2022
0c6924c
Update documentation.
futursolo Apr 24, 2022
de3e6d4
Switch from base64 to String.
futursolo Apr 24, 2022
fe09c93
Merge branch 'master' into fc-prepared-state
futursolo Apr 24, 2022
109fcfa
cargo +nightly fmt.
futursolo Apr 24, 2022
5f731b6
Fix test.
futursolo Apr 24, 2022
cd9f735
Add some tests.
futursolo Apr 24, 2022
95bb84d
Minor adjustments.
futursolo Apr 24, 2022
5365ffd
Remove unused marker.
futursolo Apr 24, 2022
8c659db
Update example.
futursolo Apr 30, 2022
3657129
Add use_transitive_state.
futursolo Apr 30, 2022
b9aa42e
Remove unused dead code notation.
futursolo Apr 30, 2022
72253e5
Opt for better code size.
futursolo Apr 30, 2022
6a676a5
Merge branch 'master' into fc-prepared-state
futursolo Apr 30, 2022
3faa58c
Add tests for use_transitive_state.
futursolo Apr 30, 2022
ee5caea
Fix cargo fmt.
futursolo Apr 30, 2022
1ff916a
Fix rustdoc.
futursolo Apr 30, 2022
59f0a0b
Asynchronously decode data during hydration.
futursolo May 4, 2022
566ecb8
Fix feature flags.
futursolo May 4, 2022
2344663
Fix docs.
futursolo May 4, 2022
337a31b
Feature flags on ssr_router.
futursolo May 4, 2022
a5ec89f
Adjust workflow to reflect feature flags.
futursolo May 4, 2022
e3d38ed
Merge branch 'master' into fc-prepared-state
futursolo May 4, 2022
53b6d68
Merge branch 'master' into fc-prepared-state
futursolo May 4, 2022
1a79e84
Merge branch 'master' into fc-prepared-state
futursolo May 4, 2022
335039f
Merge branch 'master' into fc-prepared-state
futursolo May 4, 2022
b0c8632
Merge branch 'master' into fc-prepared-state
futursolo May 22, 2022
8503d24
Fix features.
futursolo May 22, 2022
1f0b5e1
Restore wasm-bindgen-futures to be wasm32 only.
futursolo May 23, 2022
268cf57
Revert wasm-bindgen-futures.
futursolo May 23, 2022
92ebe1a
Second attempt to remove wasm-bindgen-futures.
futursolo May 23, 2022
5784ec7
Remove spaces as well.
futursolo May 23, 2022
7e12d61
Address reviews.
futursolo May 23, 2022
b8267ef
Better diagnostic message.
futursolo May 23, 2022
f01c200
Update diagnostic messages.
futursolo May 23, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/simple_ssr/Cargo.toml
Expand Up @@ -6,7 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
yew = { path = "../../packages/yew", features = ["ssr", "hydration"] }
yew = { path = "../../packages/yew", features = ["ssr", "hydration", "tokio"] }
reqwest = { version = "0.11.8", features = ["json"] }
serde = { version = "1.0.132", features = ["derive"] }
uuid = { version = "1.0.0", features = ["serde"] }
Expand Down
57 changes: 1 addition & 56 deletions examples/simple_ssr/src/lib.rs
@@ -1,14 +1,6 @@
use std::cell::RefCell;
use std::rc::Rc;

use serde::{Deserialize, Serialize};
#[cfg(not(target_arch = "wasm32"))]
use tokio::task::spawn_local;
use uuid::Uuid;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_futures::spawn_local;
use yew::prelude::*;
use yew::suspense::{Suspension, SuspensionResult};

#[derive(Serialize, Deserialize)]
struct UuidResponse {
Expand All @@ -23,56 +15,9 @@ async fn fetch_uuid() -> Uuid {
uuid_resp.uuid
}

pub struct UuidState {
s: Suspension,
value: Rc<RefCell<Option<Uuid>>>,
}

impl UuidState {
fn new() -> Self {
let (s, handle) = Suspension::new();
let value: Rc<RefCell<Option<Uuid>>> = Rc::default();

{
let value = value.clone();
// we use tokio spawn local here.
spawn_local(async move {
let uuid = fetch_uuid().await;

{
let mut value = value.borrow_mut();
*value = Some(uuid);
}

handle.resume();
});
}

Self { s, value }
}
}

impl PartialEq for UuidState {
fn eq(&self, rhs: &Self) -> bool {
self.s == rhs.s
}
}

#[hook]
fn use_random_uuid() -> SuspensionResult<Uuid> {
let s = use_state(UuidState::new);

let result = match *s.value.borrow() {
Some(ref m) => Ok(*m),
None => Err(s.s.clone()),
};

result
}

#[function_component]
fn Content() -> HtmlResult {
let uuid = use_random_uuid()?;
let uuid = use_prepared_state!(async |_| -> Uuid { fetch_uuid().await }, ())?.unwrap();

Ok(html! {
<div>{"Random UUID: "}{uuid}</div>
Expand Down
5 changes: 5 additions & 0 deletions packages/yew-macro/src/function_component.rs
Expand Up @@ -319,6 +319,11 @@ impl FunctionComponent {
fn destroy(&mut self, _ctx: &::yew::html::Context<Self>) {
::yew::functional::FunctionComponent::<Self>::destroy(&self.function_component)
}

#[inline]
fn prepare_state(&self) -> ::std::option::Option<::std::string::String> {
::yew::functional::FunctionComponent::<Self>::prepare_state(&self.function_component)
}
}
}
}
Expand Down
34 changes: 34 additions & 0 deletions packages/yew-macro/src/lib.rs
Expand Up @@ -53,6 +53,8 @@ mod hook;
mod html_tree;
mod props;
mod stringify;
mod use_prepared_state;
mod use_transitive_state;

use derive_props::DerivePropsInput;
use function_component::{function_component_impl, FunctionComponent, FunctionComponentName};
Expand All @@ -62,6 +64,8 @@ use proc_macro::TokenStream;
use quote::ToTokens;
use syn::buffer::Cursor;
use syn::parse_macro_input;
use use_prepared_state::PreparedState;
use use_transitive_state::TransitiveState;

trait Peek<'a, T> {
fn peek(cursor: Cursor<'a>) -> Option<(T, Cursor<'a>)>;
Expand Down Expand Up @@ -150,3 +154,33 @@ pub fn hook(attr: TokenStream, item: TokenStream) -> proc_macro::TokenStream {
.unwrap_or_else(|err| err.to_compile_error())
.into()
}

#[proc_macro]
pub fn use_prepared_state_with_closure(input: TokenStream) -> TokenStream {
let prepared_state = parse_macro_input!(input as PreparedState<false>);
prepared_state.to_token_stream_with_closure().into()
}

#[proc_macro]
pub fn use_prepared_state_with_closure_and_suspension(input: TokenStream) -> TokenStream {
let prepared_state = parse_macro_input!(input as PreparedState<true>);
prepared_state.to_token_stream_with_closure().into()
}

#[proc_macro]
pub fn use_prepared_state_without_closure(input: TokenStream) -> TokenStream {
let prepared_state = parse_macro_input!(input as PreparedState<true>);
prepared_state.to_token_stream_without_closure().into()
}

#[proc_macro]
pub fn use_transitive_state_with_closure(input: TokenStream) -> TokenStream {
let transitive_state = parse_macro_input!(input as TransitiveState);
transitive_state.to_token_stream_with_closure().into()
}

#[proc_macro]
pub fn use_transitive_state_without_closure(input: TokenStream) -> TokenStream {
let transitive_state = parse_macro_input!(input as TransitiveState);
transitive_state.to_token_stream_without_closure().into()
}
121 changes: 121 additions & 0 deletions packages/yew-macro/src/use_prepared_state.rs
@@ -0,0 +1,121 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::{parse_quote, Expr, ExprClosure, ReturnType, Token, Type};

#[derive(Debug)]
pub struct PreparedState<const WITH_ASYNC_CLOSURE: bool> {
closure: ExprClosure,
return_type: Type,
deps: Expr,
}

impl<const WITH_ASYNC_CLOSURE: bool> Parse for PreparedState<WITH_ASYNC_CLOSURE> {
fn parse(input: ParseStream) -> syn::Result<Self> {
// Reads a closure.
let closure: ExprClosure = input.parse()?;

input.parse::<Token![,]>()?;

let return_type = match &closure.output {
ReturnType::Default => {
return Err(syn::Error::new_spanned(
&closure,
"You must specify a return type for this closure. This is used when the \
closure is omitted from the client side rendering bundle.",
))
}
ReturnType::Type(_rarrow, ty) => *ty.to_owned(),
};

// Reads the deps.
let deps = input.parse()?;

if !input.is_empty() {
let maybe_trailing_comma = input.lookahead1();

if !maybe_trailing_comma.peek(Token![,]) {
return Err(maybe_trailing_comma.error());
}
}

if !WITH_ASYNC_CLOSURE {
if let Some(m) = closure.asyncness.as_ref() {
return Err(syn::Error::new_spanned(
&m,
"You need to enable feature tokio to use async closure under non-wasm32 \
targets.",
));
}
}

Ok(Self {
closure,
return_type,
deps,
})
}
}

impl<const WITH_ASYNC_CLOSURE: bool> PreparedState<WITH_ASYNC_CLOSURE> {
// Async closure is not stable, so we rewrite it to clsoure + async block
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another case of #2681

pub fn rewrite_to_closure_with_async_block(&self) -> ExprClosure {
let async_token = match &self.closure.asyncness {
Some(m) => m,
None => return self.closure.clone(),
};

let move_token = &self.closure.capture;
let body = &self.closure.body;

let inner = parse_quote! {
#async_token #move_token {
#body
}
};

let mut closure = self.closure.clone();

closure.asyncness = None;
// We omit the output type as it's an opaque future type.
closure.output = ReturnType::Default;

closure.body = inner;

closure.attrs.push(parse_quote! { #[allow(unused_braces)] });

closure
}

pub fn to_token_stream_with_closure(&self) -> TokenStream {
let deps = &self.deps;
let rt = &self.return_type;
let closure = self.rewrite_to_closure_with_async_block();

match &self.closure.asyncness {
Some(_) => quote! {
::yew::functional::use_prepared_state_with_suspension::<#rt, _, _, _>(#closure, #deps)
},
None => quote! {
::yew::functional::use_prepared_state::<#rt, _, _>(#closure, #deps)
},
}
}

// Expose a hook for the client side.
//
// The closure is stripped from the client side.
pub fn to_token_stream_without_closure(&self) -> TokenStream {
let deps = &self.deps;
let rt = &self.return_type;

match &self.closure.asyncness {
Some(_) => quote! {
::yew::functional::use_prepared_state_with_suspension::<#rt, _>(#deps)
},
None => quote! {
::yew::functional::use_prepared_state::<#rt, _>(#deps)
},
}
}
}
72 changes: 72 additions & 0 deletions packages/yew-macro/src/use_transitive_state.rs
@@ -0,0 +1,72 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::{Expr, ExprClosure, ReturnType, Token, Type};

#[derive(Debug)]
pub struct TransitiveState {
closure: ExprClosure,
return_type: Type,
deps: Expr,
}

impl Parse for TransitiveState {
fn parse(input: ParseStream) -> syn::Result<Self> {
// Reads a closure.
let closure: ExprClosure = input.parse()?;

input.parse::<Token![,]>()?;

let return_type = match &closure.output {
ReturnType::Default => {
return Err(syn::Error::new_spanned(
&closure,
"You must specify a return type for this closure. This is used when the \
closure is omitted from the client side rendering bundle.",
))
}
ReturnType::Type(_rarrow, ty) => *ty.to_owned(),
};

// Reads the deps.
let deps = input.parse()?;

if !input.is_empty() {
let maybe_trailing_comma = input.lookahead1();

if !maybe_trailing_comma.peek(Token![,]) {
return Err(maybe_trailing_comma.error());
}
}

Ok(Self {
closure,
return_type,
deps,
})
}
}

impl TransitiveState {
pub fn to_token_stream_with_closure(&self) -> TokenStream {
let deps = &self.deps;
let rt = &self.return_type;
let closure = &self.closure;

quote! {
::yew::functional::use_transitive_state::<#rt, _, _>(#closure, #deps)
}
}

// Expose a hook for the client side.
//
// The closure is stripped from the client side.
pub fn to_token_stream_without_closure(&self) -> TokenStream {
let deps = &self.deps;
let rt = &self.return_type;

quote! {
::yew::functional::use_transitive_state::<#rt, _>(#deps)
}
}
}
8 changes: 6 additions & 2 deletions packages/yew/Cargo.toml
Expand Up @@ -29,6 +29,9 @@ thiserror = "1.0"

futures = { version = "0.3", optional = true }
html-escape = { version = "0.2.9", optional = true }
base64ct = { version = "1.5.0", features = ["std"], optional = true }
bincode = { version = "1.3.3", optional = true }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not blocking but it would be nice if we could change the implementation, similar to how the global allocator can be changed. If one wants to use JSON, BSON, etc for this, they should have the option to do so

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should create a separate issue for this. Can you create one when merging this PR?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure.

serde = { version = "1", features = ["derive"] }

[dependencies.web-sys]
version = "0.3"
Expand Down Expand Up @@ -61,6 +64,7 @@ features = [
"UiEvent",
"WheelEvent",
"Window",
"HtmlScriptElement",
]

[target.'cfg(target_arch = "wasm32")'.dependencies]
Expand All @@ -87,9 +91,9 @@ features = [
]

[features]
ssr = ["futures", "html-escape"]
ssr = ["futures", "html-escape", "base64ct", "bincode"]
csr = []
hydration = ["csr"]
hydration = ["csr", "base64ct", "bincode"]
trace_hydration = ["hydration"]
doc_test = ["csr", "hydration", "ssr"]
wasm_test = ["csr", "hydration", "ssr"]
Expand Down
2 changes: 1 addition & 1 deletion packages/yew/src/dom_bundle/btag/listeners.rs
Expand Up @@ -312,7 +312,7 @@ mod tests {
M: Mixin + Properties + Default,
{
// Remove any existing elements
let body = document().body().unwrap();
let body = document().query_selector("#output").unwrap().unwrap();
while let Some(child) = body.query_selector("div#testroot").unwrap() {
body.remove_child(&child).unwrap();
}
Expand Down