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

SSR Hydration #2552

Merged
merged 39 commits into from Apr 2, 2022
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
5ce4df6
Bring changes to this branch.
futursolo Mar 14, 2022
e9b8aa0
Add feature hydration.
futursolo Mar 14, 2022
dd209e4
Hydrate text.
futursolo Mar 14, 2022
7c5a5be
Hydrate tag.
futursolo Mar 14, 2022
b62f347
Hydrate node.
futursolo Mar 14, 2022
02b21bc
Hydrate List.
futursolo Mar 14, 2022
bfc7a0f
Hydrate Suspense.
futursolo Mar 14, 2022
7f9eae6
Hydrate component.
futursolo Mar 14, 2022
7feac4a
Renderer::hydrate.
futursolo Mar 14, 2022
f0cd363
Add example and tests.
futursolo Mar 15, 2022
b1377bd
Fix comp_id.
futursolo Mar 18, 2022
09e4275
Move some code away from generics.
futursolo Mar 22, 2022
80eb187
Merge branch 'master' into hydration-3
futursolo Mar 26, 2022
4a653c8
Fix everything.
futursolo Mar 26, 2022
1805c49
trybuild?
futursolo Mar 26, 2022
a008b5b
Collectable!
futursolo Mar 26, 2022
3f0ec59
Phantom component.
futursolo Mar 26, 2022
fb65153
Migrate docs as well.
futursolo Mar 26, 2022
cf5f99d
Update example.
futursolo Mar 26, 2022
383d6b7
Fix docs and improve debug message.
futursolo Mar 26, 2022
e105e28
Minor fixing.
futursolo Mar 26, 2022
23937ff
Merge branch 'less-generics' into hydration-3
futursolo Mar 26, 2022
137f89b
Add hydration to feature soundness check.
futursolo Mar 26, 2022
054026d
Fix name in debug.
futursolo Mar 26, 2022
5fb2ed8
Remove Shift.
futursolo Mar 26, 2022
9b23b55
Remove comment.
futursolo Mar 26, 2022
7065ac9
Adjust readme.
futursolo Mar 26, 2022
eb5631d
Update website/docs/advanced-topics/server-side-rendering.md
futursolo Mar 31, 2022
236baa1
Update packages/yew/src/dom_bundle/bnode.rs
futursolo Mar 31, 2022
6cb88c9
Update packages/yew/src/dom_bundle/bnode.rs
futursolo Mar 31, 2022
95889da
Once via structopt, now direct clap.
futursolo Apr 1, 2022
6d4c364
Merge branch 'hydration-3' of https://github.com/futursolo/yew into h…
futursolo Apr 1, 2022
ffd05f5
Fix docs and empty fragment.
futursolo Apr 2, 2022
bfef5d3
Remove struct component warning.
futursolo Apr 2, 2022
b5dcd2f
Move function router into a separate binary.
futursolo Apr 2, 2022
9555d4a
Optimise Code Logic.
futursolo Apr 2, 2022
5c86c50
Fix condition.
futursolo Apr 2, 2022
73280cd
Fix rendering behaviour.
futursolo Apr 2, 2022
a339020
Fix comment.
futursolo Apr 2, 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: 2 additions & 0 deletions .github/workflows/main-checks.yml
Expand Up @@ -32,6 +32,7 @@ jobs:
cargo clippy -- --deny=warnings
cargo clippy --features=ssr -- --deny=warnings
cargo clippy --features=csr -- --deny=warnings
cargo clippy --features=hydration -- --deny=warnings
cargo clippy --all-features --all-targets -- --deny=warnings
working-directory: packages/yew

Expand Down Expand Up @@ -62,6 +63,7 @@ jobs:
cargo clippy --release -- --deny=warnings
cargo clippy --release --features=ssr -- --deny=warnings
cargo clippy --release --features=csr -- --deny=warnings
cargo clippy --release --features=hydration -- --deny=warnings
cargo clippy --release --all-features --all-targets -- --deny=warnings
working-directory: packages/yew

Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -34,6 +34,7 @@ members = [
"examples/two_apps",
"examples/webgl",
"examples/web_worker_fib",
"examples/ssr_router",
"examples/suspense",

# Tools
Expand Down
7 changes: 2 additions & 5 deletions examples/function_router/Cargo.toml
Expand Up @@ -13,14 +13,11 @@ yew-router = { path = "../../packages/yew-router" }
serde = { version = "1.0", features = ["derive"] }
lazy_static = "1.4.0"
gloo-timers = "0.2"
wasm-logger = "0.2"
instant = { version = "0.1", features = ["wasm-bindgen"] }

[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2", features = ["js"] }
instant = { version = "0.1", features = ["wasm-bindgen"] }
wasm-logger = "0.2"

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
instant = { version = "0.1" }

[features]
csr = ["yew/csr"]
2 changes: 1 addition & 1 deletion examples/function_router/index.html
Expand Up @@ -11,7 +11,7 @@
href="https://cdn.jsdelivr.net/npm/bulma@0.9.0/css/bulma.min.css"
/>
<link data-trunk rel="sass" href="index.scss" />
<link data-trunk rel="rust" data-cargo-features="csr" />
<link data-trunk rel="rust" data-cargo-features="csr" data-bin="function_router" />
</head>

<body></body>
Expand Down
73 changes: 32 additions & 41 deletions examples/function_router/src/app.rs
@@ -1,4 +1,8 @@
use std::collections::HashMap;

use yew::prelude::*;
use yew::virtual_dom::AttrValue;
use yew_router::history::{AnyHistory, History, MemoryHistory};
use yew_router::prelude::*;

use crate::components::nav::Nav;
Expand Down Expand Up @@ -47,53 +51,40 @@ pub fn App() -> Html {
}
}

#[cfg(not(target_arch = "wasm32"))]
mod arch_native {
use super::*;

use yew::virtual_dom::AttrValue;
use yew_router::history::{AnyHistory, History, MemoryHistory};

use std::collections::HashMap;

#[derive(Properties, PartialEq, Debug)]
pub struct ServerAppProps {
pub url: AttrValue,
pub queries: HashMap<String, String>,
}
#[derive(Properties, PartialEq, Debug)]
pub struct ServerAppProps {
pub url: AttrValue,
pub queries: HashMap<String, String>,
}

#[function_component]
pub fn ServerApp(props: &ServerAppProps) -> Html {
let history = AnyHistory::from(MemoryHistory::new());
history
.push_with_query(&*props.url, &props.queries)
.unwrap();
#[function_component]
pub fn ServerApp(props: &ServerAppProps) -> Html {
let history = AnyHistory::from(MemoryHistory::new());
history
.push_with_query(&*props.url, &props.queries)
.unwrap();

html! {
<Router history={history}>
<Nav />
html! {
<Router history={history}>
<Nav />

<main>
<Switch<Route> render={Switch::render(switch)} />
</main>
<footer class="footer">
<div class="content has-text-centered">
{ "Powered by " }
<a href="https://yew.rs">{ "Yew" }</a>
{ " using " }
<a href="https://bulma.io">{ "Bulma" }</a>
{ " and images from " }
<a href="https://unsplash.com">{ "Unsplash" }</a>
</div>
</footer>
</Router>
}
<main>
<Switch<Route> render={Switch::render(switch)} />
</main>
<footer class="footer">
<div class="content has-text-centered">
{ "Powered by " }
<a href="https://yew.rs">{ "Yew" }</a>
{ " using " }
<a href="https://bulma.io">{ "Bulma" }</a>
{ " and images from " }
<a href="https://unsplash.com">{ "Unsplash" }</a>
</div>
</footer>
</Router>
}
}

#[cfg(not(target_arch = "wasm32"))]
pub use arch_native::*;

fn switch(routes: &Route) -> Html {
match routes.clone() {
Route::Post { id } => {
Expand Down
7 changes: 7 additions & 0 deletions examples/function_router/src/bin/function_router.rs
@@ -0,0 +1,7 @@
pub use function_router::*;

fn main() {
wasm_logger::init(wasm_logger::Config::new(log::Level::Trace));
#[cfg(feature = "csr")]
yew::Renderer::<App>::new().render();
}
14 changes: 0 additions & 14 deletions examples/function_router/src/main.rs

This file was deleted.

17 changes: 14 additions & 3 deletions examples/simple_ssr/Cargo.toml
Expand Up @@ -6,9 +6,20 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
tokio = { version = "1.15.0", features = ["full"] }
warp = "0.3"
yew = { path = "../../packages/yew", features = ["ssr"] }
yew = { path = "../../packages/yew", features = ["ssr", "hydration"] }
reqwest = { version = "0.11.8", features = ["json"] }
serde = { version = "1.0.132", features = ["derive"] }
uuid = { version = "0.8.2", features = ["serde"] }

[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen-futures = "0.4"
wasm-logger = "0.2"
log = "0.4"

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { version = "1.15.0", features = ["full"] }
warp = "0.3"
num_cpus = "1.13"
tokio-util = { version = "0.7", features = ["rt"] }
once_cell = "1.5"
clap = { version = "3.1.7", features = ["derive"] }
15 changes: 13 additions & 2 deletions examples/simple_ssr/README.md
Expand Up @@ -2,5 +2,16 @@

This example demonstrates server-side rendering.

Run `cargo run -p simple_ssr` and navigate to http://localhost:8080/ to
view results.
# How to run this example

1. build hydration bundle

`trunk build examples/simple_ssr/index.html`

2. Run the server

`cargo run --bin simple_ssr_server -- --dir examples/simple_ssr/dist`

3. Open Browser

Navigate to http://localhost:8080/ to view results.
9 changes: 9 additions & 0 deletions examples/simple_ssr/index.html
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Yew SSR Example</title>

<link data-trunk rel="rust" data-bin="simple_ssr_hydrate" />
</head>
</html>
7 changes: 7 additions & 0 deletions examples/simple_ssr/src/bin/simple_ssr_hydrate.rs
@@ -0,0 +1,7 @@
use simple_ssr::App;

fn main() {
#[cfg(target_arch = "wasm32")]
wasm_logger::init(wasm_logger::Config::new(log::Level::Trace));
yew::Renderer::<App>::new().hydrate();
}
54 changes: 54 additions & 0 deletions examples/simple_ssr/src/bin/simple_ssr_server.rs
@@ -0,0 +1,54 @@
use clap::Parser;
use once_cell::sync::Lazy;
use simple_ssr::App;
use std::path::PathBuf;
use structopt::StructOpt;
use tokio_util::task::LocalPoolHandle;
use warp::Filter;

// We spawn a local pool that is as big as the number of cpu threads.
static LOCAL_POOL: Lazy<LocalPoolHandle> = Lazy::new(|| LocalPoolHandle::new(num_cpus::get()));

/// A basic example
#[derive(Parser, Debug)]
struct Opt {
/// the "dist" created by trunk directory to be served for hydration.
#[structopt(short, long, parse(from_os_str))]
dir: PathBuf,
}

async fn render(index_html_s: &str) -> String {
let content = LOCAL_POOL
.spawn_pinned(move || async move {
let renderer = yew::ServerRenderer::<App>::new();

renderer.render().await
})
.await
.expect("the task has failed.");

// Good enough for an example, but developers should avoid the replace and extra allocation
// here in an actual app.
index_html_s.replace("<body>", &format!("<body>{}", content))
}

#[tokio::main]
async fn main() {
let opts = Opt::parse();

let index_html_s = tokio::fs::read_to_string(opts.dir.join("index.html"))
.await
.expect("failed to read index.html");

let html = warp::path::end().then(move || {
let index_html_s = index_html_s.clone();

async move { warp::reply::html(render(&index_html_s).await) }
});

let routes = html.or(warp::fs::dir(opts.dir));

println!("You can view the website at: http://localhost:8080/");

warp::serve(routes).run(([127, 0, 0, 1], 8080)).await;
}
50 changes: 6 additions & 44 deletions examples/simple_ssr/src/main.rs → examples/simple_ssr/src/lib.rs
Expand Up @@ -2,13 +2,15 @@ use std::cell::RefCell;
use std::rc::Rc;

use serde::{Deserialize, Serialize};
use tokio::task::LocalSet;
use tokio::task::{spawn_blocking, spawn_local};
use uuid::Uuid;
use warp::Filter;
use yew::prelude::*;
use yew::suspense::{Suspension, SuspensionResult};

#[cfg(not(target_arch = "wasm32"))]
use tokio::task::spawn_local;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_futures::spawn_local;

#[derive(Serialize, Deserialize)]
struct UuidResponse {
uuid: Uuid,
Expand Down Expand Up @@ -79,7 +81,7 @@ fn Content() -> HtmlResult {
}

#[function_component]
fn App() -> Html {
pub fn App() -> Html {
let fallback = html! {<div>{"Loading..."}</div>};

html! {
Expand All @@ -88,43 +90,3 @@ fn App() -> Html {
</Suspense>
}
}

async fn render() -> String {
let content = spawn_blocking(move || {
use tokio::runtime::Builder;
let set = LocalSet::new();

let rt = Builder::new_current_thread().enable_all().build().unwrap();

set.block_on(&rt, async {
let renderer = yew::ServerRenderer::<App>::new();

renderer.render().await
})
})
.await
.expect("the thread has failed.");

format!(
r#"<!DOCTYPE HTML>
<html>
<head>
<title>Yew SSR Example</title>
</head>
<body>
{}
</body>
</html>
"#,
content
)
}

#[tokio::main]
async fn main() {
let routes = warp::any().then(|| async move { warp::reply::html(render().await) });

println!("You can view the website at: http://localhost:8080/");

warp::serve(routes).run(([127, 0, 0, 1], 8080)).await;
}
24 changes: 24 additions & 0 deletions examples/ssr_router/Cargo.toml
@@ -0,0 +1,24 @@
[package]
name = "ssr_router"
version = "0.1.0"
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", "trace_hydration"] }
function_router = { path = "../function_router" }
log = "0.4"

[target.'cfg(target_arch = "wasm32")'.dependencies]
futursolo marked this conversation as resolved.
Show resolved Hide resolved
wasm-bindgen-futures = "0.4"
wasm-logger = "0.2"

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { version = "1.15.0", features = ["full"] }
warp = "0.3"
Copy link
Member

Choose a reason for hiding this comment

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

The other example uses warp so how about we use axum here? That would show usage with different libraries and should also demonstrate usage with tower

Copy link
Member Author

@futursolo futursolo Apr 1, 2022

Choose a reason for hiding this comment

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

I tried to rewrite this example with axum and cannot seem to get it to match the current warp server's behaviour.

  1. any files if exists would be served by ServeDir.
  2. then any route that didn't match would fallback to render() an index html (still requires to read Request for path and queries).

Doc example I am referring to: https://docs.rs/tower-http/latest/tower_http/services/fs/struct.ServeDir.html#handling-files-not-found

There does not seem to be a way that allows both ServeDir to be used and making another response with request being taking into consideration upon file not found?

Related: tower-rs/tower-http#240

Copy link
Member Author

Choose a reason for hiding this comment

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

@hamza1311 Do you know how to do this with axum?

Copy link
Member

Choose a reason for hiding this comment

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

I haven't tried it. I will give it a shot later. We can merge it in as-is and I will hopefully update it soon. I'm probably going to need some help from the #axum in tokio's server though...

env_logger = "0.9"
num_cpus = "1.13"
tokio-util = { version = "0.7", features = ["rt"] }
once_cell = "1.5"
clap = { version = "3.1.7", features = ["derive"] }
19 changes: 19 additions & 0 deletions examples/ssr_router/README.md
@@ -0,0 +1,19 @@
# SSR Router Example

This example is the same as the function router example, but with
server-side rendering and hydration support. It reuses the same codebase
of the function router example.

# How to run this example

1. Build Hydration Bundle

`trunk build examples/ssr_router/index.html`

2. Run the server

`cargo run --bin ssr_router_server -- --dir examples/ssr_router/dist`

3. Open Browser

Navigate to http://localhost:8080/ to view results.