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 all 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
9 changes: 4 additions & 5 deletions .github/workflows/main-checks.yml
Expand Up @@ -25,7 +25,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: clippy
args: --all-targets -- -D warnings
args: --all-targets --all-features -- -D warnings

- name: Lint feature soundness
run: |
Expand All @@ -51,12 +51,11 @@ jobs:

- uses: Swatinem/rust-cache@v1


- name: Run clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: --all-targets --release -- -D warnings
args: --all-targets --all-features --release -- -D warnings

- name: Lint feature soundness
run: |
Expand Down Expand Up @@ -108,7 +107,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: test
args: --doc --workspace --exclude yew --exclude changelog --exclude website-test --target wasm32-unknown-unknown
args: --doc --workspace --exclude yew --exclude changelog --exclude website-test --exclude ssr_router --exclude simple_ssr --target wasm32-unknown-unknown

- name: Run website code snippet tests
uses: actions-rs/cargo@v1
Expand Down Expand Up @@ -196,7 +195,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: test
args: --all-targets --workspace --exclude yew --exclude website-test
args: --all-targets --workspace --exclude yew --exclude website-test --exclude ssr_router --exclude simple_ssr

- name: Run native tests for yew
uses: actions-rs/cargo@v1
Expand Down
6 changes: 5 additions & 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" }
reqwest = { version = "0.11.8", features = ["json"] }
serde = { version = "1.0.132", features = ["derive"] }
uuid = { version = "1.0.0", features = ["serde"] }
Expand All @@ -23,3 +23,7 @@ num_cpus = "1.13"
tokio-util = { version = "0.7", features = ["rt"] }
once_cell = "1.5"
clap = { version = "3.1.7", features = ["derive"] }

[features]
hydration = ["yew/hydration"]
ssr = ["yew/ssr", "yew/tokio"]
2 changes: 1 addition & 1 deletion examples/simple_ssr/README.md
Expand Up @@ -10,7 +10,7 @@ This example demonstrates server-side rendering.

2. Run the server

`cargo run --bin simple_ssr_server -- --dir examples/simple_ssr/dist`
`cargo run --features=ssr --bin simple_ssr_server -- --dir examples/simple_ssr/dist`

3. Open Browser

Expand Down
2 changes: 1 addition & 1 deletion examples/simple_ssr/index.html
Expand Up @@ -4,6 +4,6 @@
<meta charset="utf-8" />
<title>Yew SSR Example</title>

<link data-trunk rel="rust" data-bin="simple_ssr_hydrate" />
<link data-trunk rel="rust" data-bin="simple_ssr_hydrate" data-cargo-features="hydration" />
</head>
</html>
58 changes: 2 additions & 56 deletions examples/simple_ssr/src/lib.rs
@@ -1,20 +1,13 @@
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 {
uuid: Uuid,
}

#[cfg(feature = "ssr")]
async fn fetch_uuid() -> Uuid {
// reqwest works for both non-wasm and wasm targets.
let resp = reqwest::get("https://httpbin.org/uuid").await.unwrap();
Expand All @@ -23,56 +16,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
6 changes: 5 additions & 1 deletion examples/ssr_router/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" }
function_router = { path = "../function_router" }
log = "0.4"

Expand All @@ -24,3 +24,7 @@ num_cpus = "1.13"
tokio-util = { version = "0.7", features = ["rt"] }
once_cell = "1.5"
clap = { version = "3.1.7", features = ["derive"] }

[features]
ssr = ["yew/ssr"]
hydration = ["yew/hydration"]
2 changes: 1 addition & 1 deletion examples/ssr_router/README.md
Expand Up @@ -12,7 +12,7 @@ of the function router example.

2. Run the server

`cargo run --bin ssr_router_server -- --dir examples/ssr_router/dist`
`cargo run --features=ssr --bin ssr_router_server -- --dir examples/ssr_router/dist`

3. Open Browser

Expand Down
2 changes: 1 addition & 1 deletion examples/ssr_router/index.html
Expand Up @@ -3,7 +3,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link data-trunk rel="rust" data-bin="ssr_router_hydrate" />
<link data-trunk rel="rust" data-bin="ssr_router_hydrate" data-cargo-features="hydration" />

<title>Yew • SSR Router</title>
<link
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
28 changes: 28 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,27 @@ 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);
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);
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()
}
futursolo marked this conversation as resolved.
Show resolved Hide resolved
116 changes: 116 additions & 0 deletions packages/yew-macro/src/use_prepared_state.rs
@@ -0,0 +1,116 @@
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 {
closure: ExprClosure,
return_type: Type,
deps: Expr,
}

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

let closure = match expr {
Expr::Closure(m) => m,
other => return Err(syn::Error::new_spanned(other, "expected closure")),
};

input.parse::<Token![,]>().map_err(|e| {
syn::Error::new(
e.span(),
"this hook takes 2 arguments but 1 argument was supplied",
)
})?;

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 PreparedState {
// 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;

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