From 199ca5e5114f587c7d2f8bf5b4bfd2b7a8a7011a Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Wed, 11 May 2022 23:06:04 +0900 Subject: [PATCH 01/50] yew::platform? --- packages/yew/Cargo.toml | 10 ++- packages/yew/src/html/component/scope.rs | 52 ++++++++++- packages/yew/src/io_coop.rs | 32 ------- packages/yew/src/lib.rs | 6 +- packages/yew/src/platform.rs | 98 ++++++++++++++++++++ packages/yew/src/server_renderer.rs | 108 +++++++++++++++++++++-- packages/yew/src/suspense/suspension.rs | 2 +- packages/yew/src/virtual_dom/mod.rs | 65 +++++++++++--- packages/yew/src/virtual_dom/vnode.rs | 14 ++- 9 files changed, 324 insertions(+), 63 deletions(-) delete mode 100644 packages/yew/src/io_coop.rs create mode 100644 packages/yew/src/platform.rs diff --git a/packages/yew/Cargo.toml b/packages/yew/Cargo.toml index 491a44695dd..27d43ccf125 100644 --- a/packages/yew/Cargo.toml +++ b/packages/yew/Cargo.toml @@ -14,7 +14,7 @@ keywords = ["web", "webasm", "javascript"] categories = ["gui", "wasm", "web-programming"] description = "A framework for making client-side single-page apps" readme = "../../README.md" -rust-version = "1.56.0" +rust-version = "1.60.0" [dependencies] console_error_panic_hook = "0.1" @@ -70,6 +70,9 @@ wasm-bindgen-futures = "0.4" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { version = "1.15.0", features = ["rt"], optional = true } +num_cpus = { version = "1.13", optional = true } +tokio-util = { version = "0.7", features = ["rt"], optional = true } +once_cell = "1" [dev-dependencies] wasm-bindgen-test = "0.3" @@ -87,9 +90,8 @@ features = [ ] [features] -# TODO: `dep:` syntax only supported with MSRV 1.60, would be more precise -# tokio = ["dep:tokio"] -ssr = ["futures", "html-escape"] # dep:html-escape +tokio = ["dep:tokio", "dep:num_cpus", "dep:tokio-util"] +ssr = ["dep:futures", "dep:html-escape"] csr = [] hydration = ["csr"] default = [] diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index b3ca005e0ea..8a824741889 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -12,7 +12,7 @@ use super::lifecycle::ComponentState; use super::BaseComponent; use crate::callback::Callback; use crate::context::{ContextHandle, ContextProvider}; -use crate::io_coop::spawn_local; +use crate::platform::spawn_local; #[cfg(any(feature = "csr", feature = "ssr"))] use crate::scheduler::Shared; @@ -260,6 +260,9 @@ impl Scope { #[cfg(feature = "ssr")] mod feat_ssr { + use std::borrow::Cow; + + use futures::channel::mpsc::UnboundedSender; use futures::channel::oneshot; use super::*; @@ -270,6 +273,53 @@ mod feat_ssr { use crate::virtual_dom::Collectable; impl Scope { + pub(crate) async fn render_streamed( + &self, + tx: &mut UnboundedSender>, + props: Rc, + hydratable: bool, + ) { + let (html_tx, html_rx) = oneshot::channel(); + let state = ComponentRenderState::Ssr { + sender: Some(html_tx), + }; + + scheduler::push_component_create( + self.id, + Box::new(CreateRunner { + initial_render_state: state, + props, + scope: self.clone(), + }), + Box::new(RenderRunner { + state: self.state.clone(), + }), + ); + scheduler::start(); + + let collectable = Collectable::for_component::(); + + if hydratable { + collectable.write_open_tag_(tx); + } + + let html = html_rx.await.unwrap(); + + let self_any_scope = AnyScope::from(self.clone()); + html.render_into_stream(tx, &self_any_scope, hydratable) + .await; + + if hydratable { + collectable.write_close_tag_(tx); + } + + scheduler::push_component_destroy(Box::new(DestroyRunner { + state: self.state.clone(), + parent_to_detach: false, + })); + scheduler::start(); + } + pub(crate) async fn render_to_string( self, w: &mut String, diff --git a/packages/yew/src/io_coop.rs b/packages/yew/src/io_coop.rs deleted file mode 100644 index 6fdd72ac3d4..00000000000 --- a/packages/yew/src/io_coop.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! module that provides io compatibility over browser tasks and other async io tasks (e.g.: tokio) - -#[cfg(target_arch = "wasm32")] -mod arch { - pub use wasm_bindgen_futures::spawn_local; -} - -#[cfg(not(target_arch = "wasm32"))] -mod arch { - use std::future::Future; - - // spawn_local in tokio is more powerful, but we need to adjust the function signature to match - // wasm_bindgen_futures. - #[inline(always)] - pub(crate) fn spawn_local(f: F) - where - F: Future + 'static, - { - #[cfg(feature = "tokio")] - ::tokio::task::spawn_local(f); - #[cfg(not(feature = "tokio"))] - { - let _ = f; - panic!( - r#"No scheduler configured for this platform, features related to async can't be used. - Either compile with `target_arch = "wasm32", or enable the `tokio` feature."# - ); - } - } -} - -pub(crate) use arch::*; diff --git a/packages/yew/src/lib.rs b/packages/yew/src/lib.rs index b9bdd9eb257..933efd28bee 100644 --- a/packages/yew/src/lib.rs +++ b/packages/yew/src/lib.rs @@ -24,9 +24,7 @@ //! - `csr`: Enables Client-side Rendering support and [`Renderer`]. Only enable this feature if you //! are making a Yew application (not a library). //! - `ssr`: Enables Server-side Rendering support and [`ServerRenderer`]. -//! - `tokio`: Enables future-based APIs on non-wasm32 targets with tokio runtime. (You may want to -//! enable this if your application uses future-based APIs and it does not compile / lint on -//! non-wasm32 targets.) +//! - `tokio`: Enables future-based APIs on non-wasm32 targets with tokio runtime. //! - `hydration`: Enables Hydration support. //! //! ## Example @@ -280,7 +278,7 @@ pub mod context; mod dom_bundle; pub mod functional; pub mod html; -mod io_coop; +pub mod platform; pub mod scheduler; mod sealed; #[cfg(feature = "ssr")] diff --git a/packages/yew/src/platform.rs b/packages/yew/src/platform.rs new file mode 100644 index 00000000000..f83aab08923 --- /dev/null +++ b/packages/yew/src/platform.rs @@ -0,0 +1,98 @@ +//! module that provides io compatibility over browser tasks and other asyncio runtimes (e.g.: +//! tokio) + +use std::future::Future; + +#[cfg(target_arch = "wasm32")] +mod arch { + pub(super) use wasm_bindgen_futures::spawn_local; + + pub(super) async fn run_pinned(create_task: F) -> Fut::Output + where + F: FnOnce() -> Fut, + F: Send + 'static, + Fut: Future + 'static, + Fut::Output: Send + 'static, + { + create_task().await + } +} + +#[cfg(not(target_arch = "wasm32"))] +mod arch { + use super::*; + + #[cfg(feature = "tokio")] + pub(super) async fn run_pinned(create_task: F) -> Fut::Output + where + F: FnOnce() -> Fut, + F: Send + 'static, + Fut: Future + 'static, + Fut::Output: Send + 'static, + { + use once_cell::sync::Lazy; + use tokio_util::task::LocalPoolHandle; + + static POOL_HANDLE: Lazy = + Lazy::new(|| LocalPoolHandle::new(num_cpus::get())); + + POOL_HANDLE + .spawn_pinned(create_task) + .await + .expect("future has panicked!") + } + + #[inline(always)] + pub(super) fn spawn_local(f: F) + where + F: Future + 'static, + { + #[cfg(feature = "tokio")] + ::tokio::task::spawn_local(f); + #[cfg(not(feature = "tokio"))] + { + let _ = f; + panic!( + r#"No runtime configured for this platform, features that requires task spawning can't be used. + Either compile with `target_arch = "wasm32", or enable the `tokio` feature."# + ); + } + } + + #[cfg(not(feature = "tokio"))] + pub(crate) async fn run_pinned(create_task: F) -> Fut::Output + where + F: FnOnce() -> Fut, + F: Send + 'static, + Fut: Future + 'static, + Fut::Output: Send + 'static, + { + panic!( + r#"No runtime configured for this platform, features that requires task spawning can't be used. + Either compile with `target_arch = "wasm32", or enable the `tokio` feature."# + ) + } +} + +/// Spawns a task on current thread. +#[inline(always)] +pub fn spawn_local(f: F) +where + F: Future + 'static, +{ + arch::spawn_local(f); +} + +/// Runs a task with it pinned onto a worker thread. +/// +/// This can be used to execute non-Send futures without blocking the current thread. +#[inline(always)] +pub async fn run_pinned(create_task: F) -> Fut::Output +where + F: FnOnce() -> Fut, + F: Send + 'static, + Fut: Future + 'static, + Fut::Output: Send + 'static, +{ + arch::run_pinned(create_task).await +} diff --git a/packages/yew/src/server_renderer.rs b/packages/yew/src/server_renderer.rs index 9574cdbc623..363c5d3fa3e 100644 --- a/packages/yew/src/server_renderer.rs +++ b/packages/yew/src/server_renderer.rs @@ -1,9 +1,15 @@ +use std::borrow::Cow; + +use futures::channel::mpsc; +use futures::stream::Stream; + use crate::html::{BaseComponent, Scope}; +use crate::platform::run_pinned; -/// A Yew Server-side Renderer. +/// A Yew Server-side Renderer that renders on the current thread. #[cfg_attr(documenting, doc(cfg(feature = "ssr")))] #[derive(Debug)] -pub struct ServerRenderer +pub struct LocalServerRenderer where COMP: BaseComponent, { @@ -11,7 +17,7 @@ where hydratable: bool, } -impl Default for ServerRenderer +impl Default for LocalServerRenderer where COMP: BaseComponent, COMP::Properties: Default, @@ -21,22 +27,22 @@ where } } -impl ServerRenderer +impl LocalServerRenderer where COMP: BaseComponent, COMP::Properties: Default, { - /// Creates a [ServerRenderer] with default properties. + /// Creates a [LocalServerRenderer] with default properties. pub fn new() -> Self { Self::default() } } -impl ServerRenderer +impl LocalServerRenderer where COMP: BaseComponent, { - /// Creates a [ServerRenderer] with custom properties. + /// Creates a [LocalServerRenderer] with custom properties. pub fn with_props(props: COMP::Properties) -> Self { Self { props, @@ -72,4 +78,92 @@ where .render_to_string(w, self.props.into(), self.hydratable) .await; } + + /// Renders Yew Applications with a Stream + pub async fn render_streamed(self) -> impl Stream> { + let (mut tx, rx) = mpsc::unbounded::>(); + + let scope = Scope::::new(None); + scope + .render_streamed(&mut tx, self.props.into(), self.hydratable) + .await; + + rx + } +} + +/// A Yew Server-side Renderer. +/// +/// For runtimes with multi-threading support, +/// Yew manages a default worker pool with the number of worker thread equal to the CPU running +/// cores. You may override the spawning logic with +#[cfg_attr(documenting, doc(cfg(feature = "ssr")))] +#[derive(Debug)] +pub struct ServerRenderer +where + COMP: BaseComponent, + COMP::Properties: Send, +{ + props: COMP::Properties, + hydratable: bool, +} + +impl Default for ServerRenderer +where + COMP: BaseComponent, + COMP::Properties: Default + Send, +{ + fn default() -> Self { + Self::with_props(COMP::Properties::default()) + } +} + +impl ServerRenderer +where + COMP: BaseComponent, + COMP::Properties: Default + Send, +{ + /// Creates a [ServerRenderer] with default properties. + pub fn new() -> Self { + Self::default() + } +} + +impl ServerRenderer +where + COMP: BaseComponent, + COMP::Properties: Send, +{ + /// Creates a [ServerRenderer] with custom properties. + pub fn with_props(props: COMP::Properties) -> Self { + Self { + props, + hydratable: true, + } + } + + /// Sets whether an the rendered result is hydratable. + /// + /// Defaults to `true`. + /// + /// When this is sets to `true`, the rendered artifact will include additional information + /// to assist with the hydration process. + pub fn hydratable(mut self, val: bool) -> Self { + self.hydratable = val; + + self + } + + /// Renders Yew Application. + pub async fn render(self) -> String { + let Self { props, hydratable } = self; + + run_pinned(move || async move { + LocalServerRenderer::::with_props(props) + .hydratable(hydratable) + .render() + .await + }) + .await + } } diff --git a/packages/yew/src/suspense/suspension.rs b/packages/yew/src/suspense/suspension.rs index 01f0dd8849d..23663f974ff 100644 --- a/packages/yew/src/suspense/suspension.rs +++ b/packages/yew/src/suspense/suspension.rs @@ -7,7 +7,7 @@ use std::task::{Context, Poll}; use thiserror::Error; -use crate::io_coop::spawn_local; +use crate::platform::spawn_local; use crate::Callback; thread_local! { diff --git a/packages/yew/src/virtual_dom/mod.rs b/packages/yew/src/virtual_dom/mod.rs index ff54cfc6f15..c89726832a6 100644 --- a/packages/yew/src/virtual_dom/mod.rs +++ b/packages/yew/src/virtual_dom/mod.rs @@ -247,7 +247,31 @@ mod feat_ssr_hydration { } } - #[cfg(feature = "ssr")] + #[cfg(feature = "hydration")] + pub fn name(&self) -> super::Cow<'static, str> { + match self { + #[cfg(debug_assertions)] + Self::Component(m) => format!("Component({})", m).into(), + #[cfg(not(debug_assertions))] + Self::Component(_) => "Component".into(), + Self::Suspense => "Suspense".into(), + } + } + } +} + +#[cfg(any(feature = "ssr", feature = "hydration"))] +pub(crate) use feat_ssr_hydration::*; + +#[cfg(feature = "ssr")] +mod feat_ssr { + use std::borrow::Cow; + + use futures::channel::mpsc::UnboundedSender; + + use super::*; + + impl Collectable { pub fn write_open_tag(&self, w: &mut String) { w.push_str(""); } - #[cfg(feature = "ssr")] pub fn write_close_tag(&self, w: &mut String) { w.push_str(""); } - #[cfg(feature = "hydration")] - pub fn name(&self) -> super::Cow<'static, str> { + pub fn write_open_tag_(&self, tx: &mut UnboundedSender>) { + let _ = tx.start_send(Cow::Borrowed("")); + } + + pub fn write_close_tag_(&self, tx: &mut UnboundedSender>) { + let _ = tx.start_send(Cow::Borrowed("")); } } } -#[cfg(any(feature = "ssr", feature = "hydration"))] -pub(crate) use feat_ssr_hydration::*; - /// A collection of attributes for an element #[derive(PartialEq, Eq, Clone, Debug)] pub enum Attributes { diff --git a/packages/yew/src/virtual_dom/vnode.rs b/packages/yew/src/virtual_dom/vnode.rs index fbfb6ffdf17..e50be08449c 100644 --- a/packages/yew/src/virtual_dom/vnode.rs +++ b/packages/yew/src/virtual_dom/vnode.rs @@ -1,5 +1,3 @@ -//! This module contains the implementation of abstract virtual node. - use std::cmp::PartialEq; use std::fmt; use std::iter::FromIterator; @@ -149,12 +147,24 @@ impl PartialEq for VNode { #[cfg(feature = "ssr")] mod feat_ssr { + use std::borrow::Cow; + + use futures::channel::mpsc::UnboundedSender; use futures::future::{FutureExt, LocalBoxFuture}; use super::*; use crate::html::AnyScope; impl VNode { + pub(crate) async fn render_into_stream<'a>( + &'a self, + tx: &'a mut UnboundedSender>, + parent_scope: &'a AnyScope, + hydratable: bool, + ) { + let _tx = tx; + } + // Boxing is needed here, due to: https://rust-lang.github.io/async-book/07_workarounds/04_recursion.html pub(crate) fn render_to_string<'a>( &'a self, From e6fd176d94adde611c602c14558402e8205e8bdb Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 22 May 2022 18:51:03 +0900 Subject: [PATCH 02/50] Stream Response. --- examples/function_router/src/app.rs | 3 +- .../ssr_router/src/bin/ssr_router_server.rs | 19 +----- packages/yew/src/server_renderer.rs | 33 ++++++++-- packages/yew/src/virtual_dom/vcomp.rs | 40 +++++++++++ packages/yew/src/virtual_dom/vlist.rs | 28 ++++++++ packages/yew/src/virtual_dom/vnode.rs | 35 +++++++++- packages/yew/src/virtual_dom/vsuspense.rs | 26 ++++++++ packages/yew/src/virtual_dom/vtag.rs | 66 +++++++++++++++++++ packages/yew/src/virtual_dom/vtext.rs | 16 +++++ 9 files changed, 239 insertions(+), 27 deletions(-) diff --git a/examples/function_router/src/app.rs b/examples/function_router/src/app.rs index 6f2140e5e77..23a2f759036 100644 --- a/examples/function_router/src/app.rs +++ b/examples/function_router/src/app.rs @@ -1,7 +1,6 @@ use std::collections::HashMap; use yew::prelude::*; -use yew::virtual_dom::AttrValue; use yew_router::history::{AnyHistory, History, MemoryHistory}; use yew_router::prelude::*; @@ -55,7 +54,7 @@ pub fn App() -> Html { #[derive(Properties, PartialEq, Debug)] pub struct ServerAppProps { - pub url: AttrValue, + pub url: String, pub queries: HashMap, } diff --git a/examples/ssr_router/src/bin/ssr_router_server.rs b/examples/ssr_router/src/bin/ssr_router_server.rs index aca57c29143..5fad3484e5c 100644 --- a/examples/ssr_router/src/bin/ssr_router_server.rs +++ b/examples/ssr_router/src/bin/ssr_router_server.rs @@ -11,14 +11,9 @@ use axum::routing::get; use axum::{Extension, Router}; use clap::Parser; use function_router::{ServerApp, ServerAppProps}; -use once_cell::sync::Lazy; -use tokio_util::task::LocalPoolHandle; use tower::ServiceExt; use tower_http::services::ServeDir; -// We spawn a local pool that is as big as the number of cpu threads. -static LOCAL_POOL: Lazy = Lazy::new(|| LocalPoolHandle::new(num_cpus::get())); - /// A basic example #[derive(Parser, Debug)] struct Opt { @@ -34,19 +29,11 @@ async fn render( ) -> Html { let url = url.uri().to_string(); - let content = LOCAL_POOL - .spawn_pinned(move || async move { - let server_app_props = ServerAppProps { - url: url.into(), - queries, - }; + let server_app_props = ServerAppProps { url, queries }; - let renderer = yew::ServerRenderer::::with_props(server_app_props); + let renderer = yew::ServerRenderer::::with_props(server_app_props); - renderer.render().await - }) - .await - .expect("the task has failed."); + let content = renderer.render().await; // Good enough for an example, but developers should avoid the replace and extra allocation // here in an actual app. diff --git a/packages/yew/src/server_renderer.rs b/packages/yew/src/server_renderer.rs index 363c5d3fa3e..d2cac6ab410 100644 --- a/packages/yew/src/server_renderer.rs +++ b/packages/yew/src/server_renderer.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use futures::channel::mpsc; -use futures::stream::Stream; +use futures::stream::{Stream, StreamExt}; use crate::html::{BaseComponent, Scope}; use crate::platform::run_pinned; @@ -73,13 +73,14 @@ where /// Renders Yew Application to a String. pub async fn render_to_string(self, w: &mut String) { - let scope = Scope::::new(None); - scope - .render_to_string(w, self.props.into(), self.hydratable) - .await; + let mut s = self.render_streamed().await; + + while let Some(m) = s.next().await { + w.push_str(&m); + } } - /// Renders Yew Applications with a Stream + /// Renders Yew Applications into a string Stream pub async fn render_streamed(self) -> impl Stream> { let (mut tx, rx) = mpsc::unbounded::>(); @@ -156,12 +157,30 @@ where /// Renders Yew Application. pub async fn render(self) -> String { + let mut s = String::new(); + + self.render_to_string(&mut s).await; + + s + } + + /// Renders Yew Application to a String. + pub async fn render_to_string(self, w: &mut String) { + let mut s = self.render_streamed().await; + + while let Some(m) = s.next().await { + w.push_str(&m); + } + } + + /// Renders Yew Applications into a string Stream. + pub async fn render_streamed(self) -> impl Stream> { let Self { props, hydratable } = self; run_pinned(move || async move { LocalServerRenderer::::with_props(props) .hydratable(hydratable) - .render() + .render_streamed() .await }) .await diff --git a/packages/yew/src/virtual_dom/vcomp.rs b/packages/yew/src/virtual_dom/vcomp.rs index c330b12ec8e..e104af33513 100644 --- a/packages/yew/src/virtual_dom/vcomp.rs +++ b/packages/yew/src/virtual_dom/vcomp.rs @@ -1,9 +1,13 @@ //! This module contains the implementation of a virtual component (`VComp`). use std::any::TypeId; +#[cfg(feature = "ssr")] +use std::borrow::Cow; use std::fmt; use std::rc::Rc; +#[cfg(feature = "ssr")] +use futures::channel::mpsc::UnboundedSender; #[cfg(feature = "ssr")] use futures::future::{FutureExt, LocalBoxFuture}; #[cfg(feature = "csr")] @@ -83,6 +87,14 @@ pub(crate) trait Mountable { fragment: &mut Fragment, node_ref: NodeRef, ) -> Box; + + #[cfg(feature = "ssr")] + fn render_into_stream<'a>( + &'a self, + w: &'a mut UnboundedSender>, + parent_scope: &'a AnyScope, + hydratable: bool, + ) -> LocalBoxFuture<'a, ()>; } pub(crate) struct PropsWrapper { @@ -140,6 +152,22 @@ impl Mountable for PropsWrapper { .boxed_local() } + #[cfg(feature = "ssr")] + fn render_into_stream<'a>( + &'a self, + tx: &'a mut UnboundedSender>, + parent_scope: &'a AnyScope, + hydratable: bool, + ) -> LocalBoxFuture<'a, ()> { + async move { + let scope: Scope = Scope::new(Some(parent_scope.clone())); + scope + .render_streamed(tx, self.props.clone(), hydratable) + .await; + } + .boxed_local() + } + #[cfg(feature = "hydration")] fn hydrate( self: Box, @@ -251,6 +279,18 @@ mod feat_ssr { .render_to_string(w, parent_scope, hydratable) .await; } + + pub(crate) async fn render_into_stream<'a>( + &'a self, + tx: &'a mut UnboundedSender>, + parent_scope: &'a AnyScope, + hydratable: bool, + ) { + self.mountable + .as_ref() + .render_into_stream(tx, parent_scope, hydratable) + .await; + } } } diff --git a/packages/yew/src/virtual_dom/vlist.rs b/packages/yew/src/virtual_dom/vlist.rs index 4717246e72c..f981b2ba28b 100644 --- a/packages/yew/src/virtual_dom/vlist.rs +++ b/packages/yew/src/virtual_dom/vlist.rs @@ -146,6 +146,11 @@ mod test { #[cfg(feature = "ssr")] mod feat_ssr { + use std::borrow::Cow; + + use futures::channel::mpsc::{self, UnboundedSender}; + use futures::stream::StreamExt; + use super::*; use crate::html::AnyScope; @@ -169,6 +174,29 @@ mod feat_ssr { w.push_str(&fragment) } } + + pub(crate) async fn render_into_stream<'a>( + &'a self, + tx: &'a mut UnboundedSender>, + parent_scope: &'a AnyScope, + hydratable: bool, + ) { + // Concurrently render all children. + for fragment in futures::future::join_all(self.children.iter().map(|m| async move { + let (mut tx, rx) = mpsc::unbounded(); + + m.render_into_stream(&mut tx, parent_scope, hydratable) + .await; + + let s: String = rx.collect().await; + + s + })) + .await + { + let _ = tx.unbounded_send(fragment.into()); + } + } } } diff --git a/packages/yew/src/virtual_dom/vnode.rs b/packages/yew/src/virtual_dom/vnode.rs index e50be08449c..9dcbd6f5f05 100644 --- a/packages/yew/src/virtual_dom/vnode.rs +++ b/packages/yew/src/virtual_dom/vnode.rs @@ -161,8 +161,39 @@ mod feat_ssr { tx: &'a mut UnboundedSender>, parent_scope: &'a AnyScope, hydratable: bool, - ) { - let _tx = tx; + ) -> LocalBoxFuture<'a, ()> { + async move { + match self { + VNode::VTag(vtag) => { + vtag.render_into_stream(tx, parent_scope, hydratable).await + } + VNode::VText(vtext) => { + vtext.render_into_stream(tx, parent_scope, hydratable).await + } + VNode::VComp(vcomp) => { + vcomp.render_into_stream(tx, parent_scope, hydratable).await + } + VNode::VList(vlist) => { + vlist.render_into_stream(tx, parent_scope, hydratable).await + } + // We are pretty safe here as it's not possible to get a web_sys::Node without + // DOM support in the first place. + // + // The only exception would be to use `ServerRenderer` in a browser or wasm32 + // environment with jsdom present. + VNode::VRef(_) => { + panic!("VRef is not possible to be rendered in to a string.") + } + // Portals are not rendered. + VNode::VPortal(_) => {} + VNode::VSuspense(vsuspense) => { + vsuspense + .render_into_stream(tx, parent_scope, hydratable) + .await + } + } + } + .boxed_local() } // Boxing is needed here, due to: https://rust-lang.github.io/async-book/07_workarounds/04_recursion.html diff --git a/packages/yew/src/virtual_dom/vsuspense.rs b/packages/yew/src/virtual_dom/vsuspense.rs index 0d4aaf9bdc5..4312d894ea6 100644 --- a/packages/yew/src/virtual_dom/vsuspense.rs +++ b/packages/yew/src/virtual_dom/vsuspense.rs @@ -26,6 +26,10 @@ impl VSuspense { #[cfg(feature = "ssr")] mod feat_ssr { + use std::borrow::Cow; + + use futures::channel::mpsc::UnboundedSender; + use super::*; use crate::html::AnyScope; use crate::virtual_dom::Collectable; @@ -52,6 +56,28 @@ mod feat_ssr { collectable.write_close_tag(w); } } + + pub(crate) async fn render_into_stream<'a>( + &'a self, + tx: &'a mut UnboundedSender>, + parent_scope: &'a AnyScope, + hydratable: bool, + ) { + let collectable = Collectable::Suspense; + + if hydratable { + collectable.write_open_tag_(tx); + } + + // always render children on the server side. + self.children + .render_into_stream(tx, parent_scope, hydratable) + .await; + + if hydratable { + collectable.write_close_tag_(tx); + } + } } } diff --git a/packages/yew/src/virtual_dom/vtag.rs b/packages/yew/src/virtual_dom/vtag.rs index bab201bb8b5..c61d6344ecc 100644 --- a/packages/yew/src/virtual_dom/vtag.rs +++ b/packages/yew/src/virtual_dom/vtag.rs @@ -430,6 +430,8 @@ impl PartialEq for VTag { mod feat_ssr { use std::fmt::Write; + use futures::channel::mpsc::UnboundedSender; + use super::*; use crate::html::AnyScope; use crate::virtual_dom::VText; @@ -441,6 +443,70 @@ mod feat_ssr { ]; impl VTag { + pub(crate) async fn render_into_stream<'a>( + &'a self, + tx: &'a mut UnboundedSender>, + parent_scope: &'a AnyScope, + hydratable: bool, + ) { + let mut start_tag = "<".to_string(); + start_tag.push_str(self.tag()); + + let write_attr = |w: &mut String, name: &str, val: Option<&str>| { + write!(w, " {}", name).unwrap(); + + if let Some(m) = val { + write!(w, "=\"{}\"", html_escape::encode_double_quoted_attribute(m)).unwrap(); + } + }; + + if let VTagInner::Input(_) = self.inner { + if let Some(m) = self.value() { + write_attr(&mut start_tag, "value", Some(m)); + } + + if self.checked() { + write_attr(&mut start_tag, "checked", None); + } + } + + for (k, v) in self.attributes.iter() { + write_attr(&mut start_tag, k, Some(v)); + } + + start_tag.push('>'); + let _ = tx.unbounded_send(start_tag.into()); + + match self.inner { + VTagInner::Input(_) => {} + VTagInner::Textarea { .. } => { + if let Some(m) = self.value() { + VText::new(m.to_owned()) + .render_into_stream(tx, parent_scope, hydratable) + .await; + } + + let _ = tx.unbounded_send("".into()); + } + VTagInner::Other { + ref tag, + ref children, + .. + } => { + if !VOID_ELEMENTS.contains(&tag.as_ref()) { + children + .render_into_stream(tx, parent_scope, hydratable) + .await; + + let _ = tx.unbounded_send(format!("", tag).into()); + } else { + // We don't write children of void elements nor closing tags. + debug_assert!(children.is_empty(), "{} cannot have any children!", tag); + } + } + } + } + pub(crate) async fn render_to_string( &self, w: &mut String, diff --git a/packages/yew/src/virtual_dom/vtext.rs b/packages/yew/src/virtual_dom/vtext.rs index e187d4d5605..b698468d32f 100644 --- a/packages/yew/src/virtual_dom/vtext.rs +++ b/packages/yew/src/virtual_dom/vtext.rs @@ -34,6 +34,10 @@ impl PartialEq for VText { #[cfg(feature = "ssr")] mod feat_ssr { + use std::borrow::Cow; + + use futures::channel::mpsc::UnboundedSender; + use super::*; use crate::html::AnyScope; @@ -46,6 +50,18 @@ mod feat_ssr { ) { html_escape::encode_text_to_string(&self.text, w); } + + pub(crate) async fn render_into_stream<'a>( + &'a self, + tx: &'a mut UnboundedSender>, + _parent_scope: &'a AnyScope, + _hydratable: bool, + ) { + let mut s = String::with_capacity(self.text.len()); + html_escape::encode_text_to_string(&self.text, &mut s); + + let _ = tx.unbounded_send(s.into()); + } } } From a6525782dff5913410434bfd0cb6374a8fe0c086 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 22 May 2022 19:53:20 +0900 Subject: [PATCH 03/50] Migrate example --- examples/ssr_router/Cargo.toml | 1 + .../ssr_router/src/bin/ssr_router_server.rs | 41 +++++++++++++------ packages/yew/Makefile.toml | 2 +- packages/yew/src/platform.rs | 4 ++ packages/yew/src/scheduler.rs | 2 +- packages/yew/src/server_renderer.rs | 12 ++++-- packages/yew/src/virtual_dom/vlist.rs | 28 ++++++++----- packages/yew/src/virtual_dom/vnode.rs | 41 +------------------ 8 files changed, 62 insertions(+), 69 deletions(-) diff --git a/examples/ssr_router/Cargo.toml b/examples/ssr_router/Cargo.toml index 635bd21d19a..62273e49205 100644 --- a/examples/ssr_router/Cargo.toml +++ b/examples/ssr_router/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" yew = { path = "../../packages/yew", features = ["ssr", "hydration"] } function_router = { path = "../function_router" } log = "0.4" +futures = "0.3" [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen-futures = "0.4" diff --git a/examples/ssr_router/src/bin/ssr_router_server.rs b/examples/ssr_router/src/bin/ssr_router_server.rs index 5fad3484e5c..a1b01f39b8d 100644 --- a/examples/ssr_router/src/bin/ssr_router_server.rs +++ b/examples/ssr_router/src/bin/ssr_router_server.rs @@ -1,16 +1,18 @@ use std::collections::HashMap; +use std::convert::Infallible; use std::path::PathBuf; -use axum::body::Body; +use axum::body::{Body, StreamBody}; use axum::error_handling::HandleError; use axum::extract::Query; use axum::handler::Handler; use axum::http::{Request, StatusCode}; -use axum::response::Html; +use axum::response::IntoResponse; use axum::routing::get; use axum::{Extension, Router}; use clap::Parser; use function_router::{ServerApp, ServerAppProps}; +use futures::stream::{self, StreamExt}; use tower::ServiceExt; use tower_http::services::ServeDir; @@ -23,21 +25,21 @@ struct Opt { } async fn render( - Extension(index_html_s): Extension, + Extension((index_html_before, index_html_after)): Extension<(String, String)>, url: Request, Query(queries): Query>, -) -> Html { +) -> impl IntoResponse { let url = url.uri().to_string(); let server_app_props = ServerAppProps { url, queries }; - let renderer = yew::ServerRenderer::::with_props(server_app_props); - let content = renderer.render().await; - - // Good enough for an example, but developers should avoid the replace and extra allocation - // here in an actual app. - Html(index_html_s.replace("", &format!("{}", content))) + StreamBody::new( + stream::once(async move { index_html_before }) + .chain(renderer.render_streamed().await.map(|m| m.into_owned())) + .chain(stream::once(async move { index_html_after })) + .map(Result::<_, Infallible>::Ok), + ) } #[tokio::main] @@ -50,6 +52,12 @@ async fn main() { .await .expect("failed to read index.html"); + let (index_html_before, index_html_after) = index_html_s.split_once("").unwrap(); + let mut index_html_before = index_html_before.to_owned(); + index_html_before.push_str(""); + + let index_html_after = index_html_after.to_owned(); + let handle_error = |e| async move { ( StatusCode::INTERNAL_SERVER_ERROR, @@ -60,13 +68,22 @@ async fn main() { let app = Router::new() .route("/api/test", get(|| async move { "Hello World" })) // needed because https://github.com/tower-rs/tower-http/issues/262 - .route("/", get(render)) + .route( + "/", + get(render.layer(Extension(( + index_html_before.clone(), + index_html_after.clone(), + )))), + ) .fallback(HandleError::new( ServeDir::new(opts.dir) .append_index_html_on_directories(false) .fallback( render - .layer(Extension(index_html_s)) + .layer(Extension(( + index_html_before.clone(), + index_html_after.clone(), + ))) .into_service() .map_err(|err| -> std::io::Error { match err {} }), ), diff --git a/packages/yew/Makefile.toml b/packages/yew/Makefile.toml index 02cf9062616..d2126987048 100644 --- a/packages/yew/Makefile.toml +++ b/packages/yew/Makefile.toml @@ -1,6 +1,6 @@ [tasks.native-test] command = "cargo" -args = ["test", "--features", "csr,ssr,hydration"] +args = ["test", "--features", "csr,ssr,hydration,tokio"] [tasks.wasm-test] command = "wasm-pack" diff --git a/packages/yew/src/platform.rs b/packages/yew/src/platform.rs index f83aab08923..84101c16239 100644 --- a/packages/yew/src/platform.rs +++ b/packages/yew/src/platform.rs @@ -7,6 +7,8 @@ use std::future::Future; mod arch { pub(super) use wasm_bindgen_futures::spawn_local; + use super::*; + pub(super) async fn run_pinned(create_task: F) -> Fut::Output where F: FnOnce() -> Fut, @@ -67,6 +69,8 @@ mod arch { Fut: Future + 'static, Fut::Output: Send + 'static, { + let _ = create_task; + panic!( r#"No runtime configured for this platform, features that requires task spawning can't be used. Either compile with `target_arch = "wasm32", or enable the `tokio` feature."# diff --git a/packages/yew/src/scheduler.rs b/packages/yew/src/scheduler.rs index 2216e16c1f7..3fc57f94dcf 100644 --- a/packages/yew/src/scheduler.rs +++ b/packages/yew/src/scheduler.rs @@ -165,7 +165,7 @@ pub(crate) fn start_now() { #[cfg(target_arch = "wasm32")] mod arch { - use crate::io_coop::spawn_local; + use crate::platform::spawn_local; /// We delay the start of the scheduler to the end of the micro task queue. /// So any messages that needs to be queued can be queued. diff --git a/packages/yew/src/server_renderer.rs b/packages/yew/src/server_renderer.rs index d2cac6ab410..f64e0328298 100644 --- a/packages/yew/src/server_renderer.rs +++ b/packages/yew/src/server_renderer.rs @@ -4,7 +4,7 @@ use futures::channel::mpsc; use futures::stream::{Stream, StreamExt}; use crate::html::{BaseComponent, Scope}; -use crate::platform::run_pinned; +use crate::platform::{run_pinned, spawn_local}; /// A Yew Server-side Renderer that renders on the current thread. #[cfg_attr(documenting, doc(cfg(feature = "ssr")))] @@ -81,13 +81,17 @@ where } /// Renders Yew Applications into a string Stream + // Whilst not required to be async here, this function is async to keep the same function + // signature as the ServerRenderer. pub async fn render_streamed(self) -> impl Stream> { let (mut tx, rx) = mpsc::unbounded::>(); let scope = Scope::::new(None); - scope - .render_streamed(&mut tx, self.props.into(), self.hydratable) - .await; + spawn_local(async move { + scope + .render_streamed(&mut tx, self.props.into(), self.hydratable) + .await; + }); rx } diff --git a/packages/yew/src/virtual_dom/vlist.rs b/packages/yew/src/virtual_dom/vlist.rs index f981b2ba28b..483fa9b965a 100644 --- a/packages/yew/src/virtual_dom/vlist.rs +++ b/packages/yew/src/virtual_dom/vlist.rs @@ -149,7 +149,7 @@ mod feat_ssr { use std::borrow::Cow; use futures::channel::mpsc::{self, UnboundedSender}; - use futures::stream::StreamExt; + use futures::stream::{FuturesOrdered, StreamExt}; use super::*; use crate::html::AnyScope; @@ -182,19 +182,25 @@ mod feat_ssr { hydratable: bool, ) { // Concurrently render all children. - for fragment in futures::future::join_all(self.children.iter().map(|m| async move { - let (mut tx, rx) = mpsc::unbounded(); + let mut children_f: FuturesOrdered<_> = self + .children + .iter() + .map(|m| async move { + let (mut inner_tx, inner_rx) = mpsc::unbounded(); - m.render_into_stream(&mut tx, parent_scope, hydratable) - .await; + m.render_into_stream(&mut inner_tx, parent_scope, hydratable) + .await; - let s: String = rx.collect().await; + drop(inner_tx); - s - })) - .await - { - let _ = tx.unbounded_send(fragment.into()); + let s: String = inner_rx.collect().await; + + s + }) + .collect(); + + while let Some(m) = children_f.next().await { + let _ = tx.unbounded_send(m.into()); } } } diff --git a/packages/yew/src/virtual_dom/vnode.rs b/packages/yew/src/virtual_dom/vnode.rs index 9dcbd6f5f05..f3e1af8d341 100644 --- a/packages/yew/src/virtual_dom/vnode.rs +++ b/packages/yew/src/virtual_dom/vnode.rs @@ -156,7 +156,7 @@ mod feat_ssr { use crate::html::AnyScope; impl VNode { - pub(crate) async fn render_into_stream<'a>( + pub(crate) fn render_into_stream<'a>( &'a self, tx: &'a mut UnboundedSender>, parent_scope: &'a AnyScope, @@ -195,44 +195,5 @@ mod feat_ssr { } .boxed_local() } - - // Boxing is needed here, due to: https://rust-lang.github.io/async-book/07_workarounds/04_recursion.html - pub(crate) fn render_to_string<'a>( - &'a self, - w: &'a mut String, - parent_scope: &'a AnyScope, - hydratable: bool, - ) -> LocalBoxFuture<'a, ()> { - async move { - match self { - VNode::VTag(vtag) => vtag.render_to_string(w, parent_scope, hydratable).await, - VNode::VText(vtext) => { - vtext.render_to_string(w, parent_scope, hydratable).await - } - VNode::VComp(vcomp) => { - vcomp.render_to_string(w, parent_scope, hydratable).await - } - VNode::VList(vlist) => { - vlist.render_to_string(w, parent_scope, hydratable).await - } - // We are pretty safe here as it's not possible to get a web_sys::Node without - // DOM support in the first place. - // - // The only exception would be to use `ServerRenderer` in a browser or wasm32 - // environment with jsdom present. - VNode::VRef(_) => { - panic!("VRef is not possible to be rendered in to a string.") - } - // Portals are not rendered. - VNode::VPortal(_) => {} - VNode::VSuspense(vsuspense) => { - vsuspense - .render_to_string(w, parent_scope, hydratable) - .await - } - } - } - .boxed_local() - } } } From bb41baa0fcf849de86ab0e094ccdb4f6b966d19c Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 22 May 2022 20:11:54 +0900 Subject: [PATCH 04/50] Remove old implementation. --- examples/simple_ssr/Cargo.toml | 2 + .../simple_ssr/src/bin/simple_ssr_server.rs | 43 +++++++------ packages/yew/src/html/component/scope.rs | 46 +------------- packages/yew/src/server_renderer.rs | 2 +- packages/yew/src/virtual_dom/vcomp.rs | 42 +------------ packages/yew/src/virtual_dom/vlist.rs | 20 ------- packages/yew/src/virtual_dom/vsuspense.rs | 22 ------- packages/yew/src/virtual_dom/vtag.rs | 60 ------------------- packages/yew/src/virtual_dom/vtext.rs | 9 --- 9 files changed, 31 insertions(+), 215 deletions(-) diff --git a/examples/simple_ssr/Cargo.toml b/examples/simple_ssr/Cargo.toml index a9559e37335..974d3537e12 100644 --- a/examples/simple_ssr/Cargo.toml +++ b/examples/simple_ssr/Cargo.toml @@ -10,6 +10,8 @@ yew = { path = "../../packages/yew", features = ["ssr", "hydration"] } reqwest = { version = "0.11.8", features = ["json"] } serde = { version = "1.0.132", features = ["derive"] } uuid = { version = "1.0.0", features = ["serde"] } +futures = "0.3" +bytes = "1.0" [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen-futures = "0.4" diff --git a/examples/simple_ssr/src/bin/simple_ssr_server.rs b/examples/simple_ssr/src/bin/simple_ssr_server.rs index bf86e7cf0dc..50b667a6962 100644 --- a/examples/simple_ssr/src/bin/simple_ssr_server.rs +++ b/examples/simple_ssr/src/bin/simple_ssr_server.rs @@ -1,13 +1,13 @@ +use std::error::Error; use std::path::PathBuf; +use bytes::Bytes; use clap::Parser; -use once_cell::sync::Lazy; +use futures::stream::{self, Stream, StreamExt}; use simple_ssr::App; -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 = Lazy::new(|| LocalPoolHandle::new(num_cpus::get())); +type BoxedError = Box; /// A basic example #[derive(Parser, Debug)] @@ -17,19 +17,18 @@ struct Opt { dir: PathBuf, } -async fn render(index_html_s: &str) -> String { - let content = LOCAL_POOL - .spawn_pinned(move || async move { - let renderer = yew::ServerRenderer::::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("", &format!("{}", content)) +async fn render( + index_html_before: String, + index_html_after: String, +) -> Box> + Send> { + let renderer = yew::ServerRenderer::::new(); + + Box::new( + stream::once(async move { index_html_before }) + .chain(renderer.render_streamed().await.map(|m| m.into_owned())) + .chain(stream::once(async move { index_html_after })) + .map(|m| Result::<_, BoxedError>::Ok(m.into())), + ) } #[tokio::main] @@ -40,10 +39,16 @@ async fn main() { .await .expect("failed to read index.html"); + let (index_html_before, index_html_after) = index_html_s.split_once("").unwrap(); + let mut index_html_before = index_html_before.to_owned(); + index_html_before.push_str(""); + let index_html_after = index_html_after.to_owned(); + let html = warp::path::end().then(move || { - let index_html_s = index_html_s.clone(); + let index_html_before = index_html_before.clone(); + let index_html_after = index_html_after.clone(); - async move { warp::reply::html(render(&index_html_s).await) } + async move { warp::reply::html(render(index_html_before, index_html_after).await) } }); let routes = html.or(warp::fs::dir(opts.dir)); diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index 8a824741889..535e72a9d5b 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -273,7 +273,7 @@ mod feat_ssr { use crate::virtual_dom::Collectable; impl Scope { - pub(crate) async fn render_streamed( + pub(crate) async fn render_into_stream( &self, tx: &mut UnboundedSender>, props: Rc, @@ -319,50 +319,6 @@ mod feat_ssr { })); scheduler::start(); } - - pub(crate) async fn render_to_string( - self, - w: &mut String, - props: Rc, - hydratable: bool, - ) { - let (tx, rx) = oneshot::channel(); - let state = ComponentRenderState::Ssr { sender: Some(tx) }; - - scheduler::push_component_create( - self.id, - Box::new(CreateRunner { - initial_render_state: state, - props, - scope: self.clone(), - }), - Box::new(RenderRunner { - state: self.state.clone(), - }), - ); - scheduler::start(); - - let collectable = Collectable::for_component::(); - - if hydratable { - collectable.write_open_tag(w); - } - - let html = rx.await.unwrap(); - - let self_any_scope = AnyScope::from(self.clone()); - html.render_to_string(w, &self_any_scope, hydratable).await; - - if hydratable { - collectable.write_close_tag(w); - } - - scheduler::push_component_destroy(Box::new(DestroyRunner { - state: self.state.clone(), - parent_to_detach: false, - })); - scheduler::start(); - } } } diff --git a/packages/yew/src/server_renderer.rs b/packages/yew/src/server_renderer.rs index f64e0328298..bc8f9c7e09d 100644 --- a/packages/yew/src/server_renderer.rs +++ b/packages/yew/src/server_renderer.rs @@ -89,7 +89,7 @@ where let scope = Scope::::new(None); spawn_local(async move { scope - .render_streamed(&mut tx, self.props.into(), self.hydratable) + .render_into_stream(&mut tx, self.props.into(), self.hydratable) .await; }); diff --git a/packages/yew/src/virtual_dom/vcomp.rs b/packages/yew/src/virtual_dom/vcomp.rs index e104af33513..b9cb769ba81 100644 --- a/packages/yew/src/virtual_dom/vcomp.rs +++ b/packages/yew/src/virtual_dom/vcomp.rs @@ -71,9 +71,9 @@ pub(crate) trait Mountable { fn reuse(self: Box, scope: &dyn Scoped, next_sibling: NodeRef); #[cfg(feature = "ssr")] - fn render_to_string<'a>( + fn render_into_stream<'a>( &'a self, - w: &'a mut String, + w: &'a mut UnboundedSender>, parent_scope: &'a AnyScope, hydratable: bool, ) -> LocalBoxFuture<'a, ()>; @@ -87,14 +87,6 @@ pub(crate) trait Mountable { fragment: &mut Fragment, node_ref: NodeRef, ) -> Box; - - #[cfg(feature = "ssr")] - fn render_into_stream<'a>( - &'a self, - w: &'a mut UnboundedSender>, - parent_scope: &'a AnyScope, - hydratable: bool, - ) -> LocalBoxFuture<'a, ()>; } pub(crate) struct PropsWrapper { @@ -136,22 +128,6 @@ impl Mountable for PropsWrapper { scope.reuse(self.props, next_sibling); } - #[cfg(feature = "ssr")] - fn render_to_string<'a>( - &'a self, - w: &'a mut String, - parent_scope: &'a AnyScope, - hydratable: bool, - ) -> LocalBoxFuture<'a, ()> { - async move { - let scope: Scope = Scope::new(Some(parent_scope.clone())); - scope - .render_to_string(w, self.props.clone(), hydratable) - .await; - } - .boxed_local() - } - #[cfg(feature = "ssr")] fn render_into_stream<'a>( &'a self, @@ -162,7 +138,7 @@ impl Mountable for PropsWrapper { async move { let scope: Scope = Scope::new(Some(parent_scope.clone())); scope - .render_streamed(tx, self.props.clone(), hydratable) + .render_into_stream(tx, self.props.clone(), hydratable) .await; } .boxed_local() @@ -268,18 +244,6 @@ mod feat_ssr { use crate::html::AnyScope; impl VComp { - pub(crate) async fn render_to_string( - &self, - w: &mut String, - parent_scope: &AnyScope, - hydratable: bool, - ) { - self.mountable - .as_ref() - .render_to_string(w, parent_scope, hydratable) - .await; - } - pub(crate) async fn render_into_stream<'a>( &'a self, tx: &'a mut UnboundedSender>, diff --git a/packages/yew/src/virtual_dom/vlist.rs b/packages/yew/src/virtual_dom/vlist.rs index 483fa9b965a..d133aa83f59 100644 --- a/packages/yew/src/virtual_dom/vlist.rs +++ b/packages/yew/src/virtual_dom/vlist.rs @@ -155,26 +155,6 @@ mod feat_ssr { use crate::html::AnyScope; impl VList { - pub(crate) async fn render_to_string( - &self, - w: &mut String, - parent_scope: &AnyScope, - hydratable: bool, - ) { - // Concurrently render all children. - for fragment in futures::future::join_all(self.children.iter().map(|m| async move { - let mut w = String::new(); - - m.render_to_string(&mut w, parent_scope, hydratable).await; - - w - })) - .await - { - w.push_str(&fragment) - } - } - pub(crate) async fn render_into_stream<'a>( &'a self, tx: &'a mut UnboundedSender>, diff --git a/packages/yew/src/virtual_dom/vsuspense.rs b/packages/yew/src/virtual_dom/vsuspense.rs index 4312d894ea6..49b0bd9b32d 100644 --- a/packages/yew/src/virtual_dom/vsuspense.rs +++ b/packages/yew/src/virtual_dom/vsuspense.rs @@ -35,28 +35,6 @@ mod feat_ssr { use crate::virtual_dom::Collectable; impl VSuspense { - pub(crate) async fn render_to_string( - &self, - w: &mut String, - parent_scope: &AnyScope, - hydratable: bool, - ) { - let collectable = Collectable::Suspense; - - if hydratable { - collectable.write_open_tag(w); - } - - // always render children on the server side. - self.children - .render_to_string(w, parent_scope, hydratable) - .await; - - if hydratable { - collectable.write_close_tag(w); - } - } - pub(crate) async fn render_into_stream<'a>( &'a self, tx: &'a mut UnboundedSender>, diff --git a/packages/yew/src/virtual_dom/vtag.rs b/packages/yew/src/virtual_dom/vtag.rs index c61d6344ecc..0064d62819b 100644 --- a/packages/yew/src/virtual_dom/vtag.rs +++ b/packages/yew/src/virtual_dom/vtag.rs @@ -506,66 +506,6 @@ mod feat_ssr { } } } - - pub(crate) async fn render_to_string( - &self, - w: &mut String, - parent_scope: &AnyScope, - hydratable: bool, - ) { - write!(w, "<{}", self.tag()).unwrap(); - - let write_attr = |w: &mut String, name: &str, val: Option<&str>| { - write!(w, " {}", name).unwrap(); - - if let Some(m) = val { - write!(w, "=\"{}\"", html_escape::encode_double_quoted_attribute(m)).unwrap(); - } - }; - - if let VTagInner::Input(_) = self.inner { - if let Some(m) = self.value() { - write_attr(w, "value", Some(m)); - } - - if self.checked() { - write_attr(w, "checked", None); - } - } - - for (k, v) in self.attributes.iter() { - write_attr(w, k, Some(v)); - } - - write!(w, ">").unwrap(); - - match self.inner { - VTagInner::Input(_) => {} - VTagInner::Textarea { .. } => { - if let Some(m) = self.value() { - VText::new(m.to_owned()) - .render_to_string(w, parent_scope, hydratable) - .await; - } - - w.push_str(""); - } - VTagInner::Other { - ref tag, - ref children, - .. - } => { - if !VOID_ELEMENTS.contains(&tag.as_ref()) { - children.render_to_string(w, parent_scope, hydratable).await; - - write!(w, "", tag).unwrap(); - } else { - // We don't write children of void elements nor closing tags. - debug_assert!(children.is_empty(), "{} cannot have any children!", tag); - } - } - } - } } } diff --git a/packages/yew/src/virtual_dom/vtext.rs b/packages/yew/src/virtual_dom/vtext.rs index b698468d32f..d77904612d7 100644 --- a/packages/yew/src/virtual_dom/vtext.rs +++ b/packages/yew/src/virtual_dom/vtext.rs @@ -42,15 +42,6 @@ mod feat_ssr { use crate::html::AnyScope; impl VText { - pub(crate) async fn render_to_string( - &self, - w: &mut String, - _parent_scope: &AnyScope, - _hydratable: bool, - ) { - html_escape::encode_text_to_string(&self.text, w); - } - pub(crate) async fn render_into_stream<'a>( &'a self, tx: &'a mut UnboundedSender>, From 5c5f9e0e6aca56b107891ccb83031f7cda89b336 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 22 May 2022 20:16:31 +0900 Subject: [PATCH 05/50] Remove extra implementation. --- examples/simple_ssr/Cargo.toml | 3 -- examples/ssr_router/Cargo.toml | 3 -- packages/yew/src/html/component/scope.rs | 4 +-- packages/yew/src/virtual_dom/mod.rs | 43 ++++++----------------- packages/yew/src/virtual_dom/vsuspense.rs | 4 +-- 5 files changed, 14 insertions(+), 43 deletions(-) diff --git a/examples/simple_ssr/Cargo.toml b/examples/simple_ssr/Cargo.toml index 974d3537e12..af381be6c2d 100644 --- a/examples/simple_ssr/Cargo.toml +++ b/examples/simple_ssr/Cargo.toml @@ -21,7 +21,4 @@ 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"] } diff --git a/examples/ssr_router/Cargo.toml b/examples/ssr_router/Cargo.toml index 62273e49205..5cb51f77351 100644 --- a/examples/ssr_router/Cargo.toml +++ b/examples/ssr_router/Cargo.toml @@ -21,7 +21,4 @@ axum = "0.5" tower = { version = "0.4", features = ["make"] } tower-http = { version = "0.3", features = ["fs"] } 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"] } diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index 535e72a9d5b..7f6baabfe4e 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -300,7 +300,7 @@ mod feat_ssr { let collectable = Collectable::for_component::(); if hydratable { - collectable.write_open_tag_(tx); + collectable.write_open_tag(tx); } let html = html_rx.await.unwrap(); @@ -310,7 +310,7 @@ mod feat_ssr { .await; if hydratable { - collectable.write_close_tag_(tx); + collectable.write_close_tag(tx); } scheduler::push_component_destroy(Box::new(DestroyRunner { diff --git a/packages/yew/src/virtual_dom/mod.rs b/packages/yew/src/virtual_dom/mod.rs index c89726832a6..541aebdb7d4 100644 --- a/packages/yew/src/virtual_dom/mod.rs +++ b/packages/yew/src/virtual_dom/mod.rs @@ -272,7 +272,10 @@ mod feat_ssr { use super::*; impl Collectable { - pub fn write_open_tag(&self, w: &mut String) { + pub fn write_open_tag(&self, tx: &mut UnboundedSender>) { + // + let mut w = String::with_capacity(11); + w.push_str(""); + + let _ = tx.unbounded_send(w.into()); } - pub fn write_close_tag(&self, w: &mut String) { + pub fn write_close_tag(&self, tx: &mut UnboundedSender>) { + // + let mut w = String::with_capacity(12); w.push_str(""); - } - - pub fn write_open_tag_(&self, tx: &mut UnboundedSender>) { - let _ = tx.start_send(Cow::Borrowed("")); - } - - pub fn write_close_tag_(&self, tx: &mut UnboundedSender>) { - let _ = tx.start_send(Cow::Borrowed("")); + let _ = tx.unbounded_send(w.into()); } } } diff --git a/packages/yew/src/virtual_dom/vsuspense.rs b/packages/yew/src/virtual_dom/vsuspense.rs index 49b0bd9b32d..60594b6a057 100644 --- a/packages/yew/src/virtual_dom/vsuspense.rs +++ b/packages/yew/src/virtual_dom/vsuspense.rs @@ -44,7 +44,7 @@ mod feat_ssr { let collectable = Collectable::Suspense; if hydratable { - collectable.write_open_tag_(tx); + collectable.write_open_tag(tx); } // always render children on the server side. @@ -53,7 +53,7 @@ mod feat_ssr { .await; if hydratable { - collectable.write_close_tag_(tx); + collectable.write_close_tag(tx); } } } From fca682dd5a47298f99c27c09f860bde0fe16c7e2 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 22 May 2022 20:27:25 +0900 Subject: [PATCH 06/50] Prefer String instead of Cow. --- .../simple_ssr/src/bin/simple_ssr_server.rs | 2 +- .../ssr_router/src/bin/ssr_router_server.rs | 2 +- packages/yew/src/html/component/scope.rs | 4 +-- packages/yew/src/server_renderer.rs | 8 +++--- packages/yew/src/virtual_dom/mod.rs | 10 +++---- packages/yew/src/virtual_dom/vcomp.rs | 14 +++++----- packages/yew/src/virtual_dom/vlist.rs | 27 +++++++++---------- packages/yew/src/virtual_dom/vnode.rs | 4 +-- packages/yew/src/virtual_dom/vsuspense.rs | 10 +++---- packages/yew/src/virtual_dom/vtag.rs | 12 ++++----- packages/yew/src/virtual_dom/vtext.rs | 12 ++++----- 11 files changed, 44 insertions(+), 61 deletions(-) diff --git a/examples/simple_ssr/src/bin/simple_ssr_server.rs b/examples/simple_ssr/src/bin/simple_ssr_server.rs index 50b667a6962..fef68668ef0 100644 --- a/examples/simple_ssr/src/bin/simple_ssr_server.rs +++ b/examples/simple_ssr/src/bin/simple_ssr_server.rs @@ -25,7 +25,7 @@ async fn render( Box::new( stream::once(async move { index_html_before }) - .chain(renderer.render_streamed().await.map(|m| m.into_owned())) + .chain(renderer.render_streamed().await) .chain(stream::once(async move { index_html_after })) .map(|m| Result::<_, BoxedError>::Ok(m.into())), ) diff --git a/examples/ssr_router/src/bin/ssr_router_server.rs b/examples/ssr_router/src/bin/ssr_router_server.rs index a1b01f39b8d..fb7e4b153b6 100644 --- a/examples/ssr_router/src/bin/ssr_router_server.rs +++ b/examples/ssr_router/src/bin/ssr_router_server.rs @@ -36,7 +36,7 @@ async fn render( StreamBody::new( stream::once(async move { index_html_before }) - .chain(renderer.render_streamed().await.map(|m| m.into_owned())) + .chain(renderer.render_streamed().await) .chain(stream::once(async move { index_html_after })) .map(Result::<_, Infallible>::Ok), ) diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index 7f6baabfe4e..d59dcd30730 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -260,8 +260,6 @@ impl Scope { #[cfg(feature = "ssr")] mod feat_ssr { - use std::borrow::Cow; - use futures::channel::mpsc::UnboundedSender; use futures::channel::oneshot; @@ -275,7 +273,7 @@ mod feat_ssr { impl Scope { pub(crate) async fn render_into_stream( &self, - tx: &mut UnboundedSender>, + tx: &mut UnboundedSender, props: Rc, hydratable: bool, ) { diff --git a/packages/yew/src/server_renderer.rs b/packages/yew/src/server_renderer.rs index bc8f9c7e09d..5f658f6febb 100644 --- a/packages/yew/src/server_renderer.rs +++ b/packages/yew/src/server_renderer.rs @@ -1,5 +1,3 @@ -use std::borrow::Cow; - use futures::channel::mpsc; use futures::stream::{Stream, StreamExt}; @@ -83,8 +81,8 @@ where /// Renders Yew Applications into a string Stream // Whilst not required to be async here, this function is async to keep the same function // signature as the ServerRenderer. - pub async fn render_streamed(self) -> impl Stream> { - let (mut tx, rx) = mpsc::unbounded::>(); + pub async fn render_streamed(self) -> impl Stream { + let (mut tx, rx) = mpsc::unbounded::(); let scope = Scope::::new(None); spawn_local(async move { @@ -178,7 +176,7 @@ where } /// Renders Yew Applications into a string Stream. - pub async fn render_streamed(self) -> impl Stream> { + pub async fn render_streamed(self) -> impl Stream { let Self { props, hydratable } = self; run_pinned(move || async move { diff --git a/packages/yew/src/virtual_dom/mod.rs b/packages/yew/src/virtual_dom/mod.rs index 541aebdb7d4..fad826bd973 100644 --- a/packages/yew/src/virtual_dom/mod.rs +++ b/packages/yew/src/virtual_dom/mod.rs @@ -265,14 +265,12 @@ pub(crate) use feat_ssr_hydration::*; #[cfg(feature = "ssr")] mod feat_ssr { - use std::borrow::Cow; - use futures::channel::mpsc::UnboundedSender; use super::*; impl Collectable { - pub fn write_open_tag(&self, tx: &mut UnboundedSender>) { + pub fn write_open_tag(&self, tx: &mut UnboundedSender) { // let mut w = String::with_capacity(11); @@ -288,10 +286,10 @@ mod feat_ssr { w.push_str(self.end_mark()); w.push_str("-->"); - let _ = tx.unbounded_send(w.into()); + let _ = tx.unbounded_send(w); } - pub fn write_close_tag(&self, tx: &mut UnboundedSender>) { + pub fn write_close_tag(&self, tx: &mut UnboundedSender) { // let mut w = String::with_capacity(12); w.push_str(""); - let _ = tx.unbounded_send(w.into()); + let _ = tx.unbounded_send(w); } } } diff --git a/packages/yew/src/virtual_dom/vcomp.rs b/packages/yew/src/virtual_dom/vcomp.rs index b9cb769ba81..3e3ae189a55 100644 --- a/packages/yew/src/virtual_dom/vcomp.rs +++ b/packages/yew/src/virtual_dom/vcomp.rs @@ -1,8 +1,6 @@ //! This module contains the implementation of a virtual component (`VComp`). use std::any::TypeId; -#[cfg(feature = "ssr")] -use std::borrow::Cow; use std::fmt; use std::rc::Rc; @@ -73,7 +71,7 @@ pub(crate) trait Mountable { #[cfg(feature = "ssr")] fn render_into_stream<'a>( &'a self, - w: &'a mut UnboundedSender>, + w: &'a mut UnboundedSender, parent_scope: &'a AnyScope, hydratable: bool, ) -> LocalBoxFuture<'a, ()>; @@ -131,7 +129,7 @@ impl Mountable for PropsWrapper { #[cfg(feature = "ssr")] fn render_into_stream<'a>( &'a self, - tx: &'a mut UnboundedSender>, + tx: &'a mut UnboundedSender, parent_scope: &'a AnyScope, hydratable: bool, ) -> LocalBoxFuture<'a, ()> { @@ -244,10 +242,10 @@ mod feat_ssr { use crate::html::AnyScope; impl VComp { - pub(crate) async fn render_into_stream<'a>( - &'a self, - tx: &'a mut UnboundedSender>, - parent_scope: &'a AnyScope, + pub(crate) async fn render_into_stream( + &self, + tx: &mut UnboundedSender, + parent_scope: &AnyScope, hydratable: bool, ) { self.mountable diff --git a/packages/yew/src/virtual_dom/vlist.rs b/packages/yew/src/virtual_dom/vlist.rs index d133aa83f59..53708c7781d 100644 --- a/packages/yew/src/virtual_dom/vlist.rs +++ b/packages/yew/src/virtual_dom/vlist.rs @@ -146,8 +146,6 @@ mod test { #[cfg(feature = "ssr")] mod feat_ssr { - use std::borrow::Cow; - use futures::channel::mpsc::{self, UnboundedSender}; use futures::stream::{FuturesOrdered, StreamExt}; @@ -155,32 +153,31 @@ mod feat_ssr { use crate::html::AnyScope; impl VList { - pub(crate) async fn render_into_stream<'a>( - &'a self, - tx: &'a mut UnboundedSender>, - parent_scope: &'a AnyScope, + pub(crate) async fn render_into_stream( + &self, + tx: &mut UnboundedSender, + parent_scope: &AnyScope, hydratable: bool, ) { // Concurrently render all children. - let mut children_f: FuturesOrdered<_> = self + let children: FuturesOrdered<_> = self .children .iter() .map(|m| async move { - let (mut inner_tx, inner_rx) = mpsc::unbounded(); + let (mut tx, rx) = mpsc::unbounded(); - m.render_into_stream(&mut inner_tx, parent_scope, hydratable) + m.render_into_stream(&mut tx, parent_scope, hydratable) .await; - drop(inner_tx); - - let s: String = inner_rx.collect().await; + drop(tx); - s + rx }) .collect(); + let mut children = children.flatten(); - while let Some(m) = children_f.next().await { - let _ = tx.unbounded_send(m.into()); + while let Some(m) = children.next().await { + let _ = tx.unbounded_send(m); } } } diff --git a/packages/yew/src/virtual_dom/vnode.rs b/packages/yew/src/virtual_dom/vnode.rs index f3e1af8d341..771e063add8 100644 --- a/packages/yew/src/virtual_dom/vnode.rs +++ b/packages/yew/src/virtual_dom/vnode.rs @@ -147,8 +147,6 @@ impl PartialEq for VNode { #[cfg(feature = "ssr")] mod feat_ssr { - use std::borrow::Cow; - use futures::channel::mpsc::UnboundedSender; use futures::future::{FutureExt, LocalBoxFuture}; @@ -158,7 +156,7 @@ mod feat_ssr { impl VNode { pub(crate) fn render_into_stream<'a>( &'a self, - tx: &'a mut UnboundedSender>, + tx: &'a mut UnboundedSender, parent_scope: &'a AnyScope, hydratable: bool, ) -> LocalBoxFuture<'a, ()> { diff --git a/packages/yew/src/virtual_dom/vsuspense.rs b/packages/yew/src/virtual_dom/vsuspense.rs index 60594b6a057..7e54428dd2b 100644 --- a/packages/yew/src/virtual_dom/vsuspense.rs +++ b/packages/yew/src/virtual_dom/vsuspense.rs @@ -26,8 +26,6 @@ impl VSuspense { #[cfg(feature = "ssr")] mod feat_ssr { - use std::borrow::Cow; - use futures::channel::mpsc::UnboundedSender; use super::*; @@ -35,10 +33,10 @@ mod feat_ssr { use crate::virtual_dom::Collectable; impl VSuspense { - pub(crate) async fn render_into_stream<'a>( - &'a self, - tx: &'a mut UnboundedSender>, - parent_scope: &'a AnyScope, + pub(crate) async fn render_into_stream( + &self, + tx: &mut UnboundedSender, + parent_scope: &AnyScope, hydratable: bool, ) { let collectable = Collectable::Suspense; diff --git a/packages/yew/src/virtual_dom/vtag.rs b/packages/yew/src/virtual_dom/vtag.rs index 0064d62819b..c58887f8a20 100644 --- a/packages/yew/src/virtual_dom/vtag.rs +++ b/packages/yew/src/virtual_dom/vtag.rs @@ -443,10 +443,10 @@ mod feat_ssr { ]; impl VTag { - pub(crate) async fn render_into_stream<'a>( - &'a self, - tx: &'a mut UnboundedSender>, - parent_scope: &'a AnyScope, + pub(crate) async fn render_into_stream( + &self, + tx: &mut UnboundedSender, + parent_scope: &AnyScope, hydratable: bool, ) { let mut start_tag = "<".to_string(); @@ -475,7 +475,7 @@ mod feat_ssr { } start_tag.push('>'); - let _ = tx.unbounded_send(start_tag.into()); + let _ = tx.unbounded_send(start_tag); match self.inner { VTagInner::Input(_) => {} @@ -498,7 +498,7 @@ mod feat_ssr { .render_into_stream(tx, parent_scope, hydratable) .await; - let _ = tx.unbounded_send(format!("", tag).into()); + let _ = tx.unbounded_send(format!("", tag)); } else { // We don't write children of void elements nor closing tags. debug_assert!(children.is_empty(), "{} cannot have any children!", tag); diff --git a/packages/yew/src/virtual_dom/vtext.rs b/packages/yew/src/virtual_dom/vtext.rs index d77904612d7..9ac1dae7d97 100644 --- a/packages/yew/src/virtual_dom/vtext.rs +++ b/packages/yew/src/virtual_dom/vtext.rs @@ -34,24 +34,22 @@ impl PartialEq for VText { #[cfg(feature = "ssr")] mod feat_ssr { - use std::borrow::Cow; - use futures::channel::mpsc::UnboundedSender; use super::*; use crate::html::AnyScope; impl VText { - pub(crate) async fn render_into_stream<'a>( - &'a self, - tx: &'a mut UnboundedSender>, - _parent_scope: &'a AnyScope, + pub(crate) async fn render_into_stream( + &self, + tx: &mut UnboundedSender, + _parent_scope: &AnyScope, _hydratable: bool, ) { let mut s = String::with_capacity(self.text.len()); html_escape::encode_text_to_string(&self.text, &mut s); - let _ = tx.unbounded_send(s.into()); + let _ = tx.unbounded_send(s); } } } From 9f82906ffbc43be5d1c8b339591057a631e4e823 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 22 May 2022 21:19:54 +0900 Subject: [PATCH 07/50] Fix MSRV. --- .github/workflows/main-checks.yml | 4 ++-- examples/simple_ssr/src/bin/simple_ssr_server.rs | 2 +- examples/ssr_router/src/bin/ssr_router_server.rs | 2 +- packages/yew-macro/Cargo.toml | 2 +- packages/yew-macro/Makefile.toml | 2 +- packages/yew-macro/tests/classes_macro_test.rs | 2 +- packages/yew-macro/tests/derive_props_test.rs | 2 +- packages/yew-macro/tests/function_attr_test.rs | 2 +- packages/yew-macro/tests/hook_attr_test.rs | 2 +- packages/yew-macro/tests/html_macro_test.rs | 2 +- packages/yew-macro/tests/props_macro_test.rs | 2 +- packages/yew-router-macro/Cargo.toml | 2 +- packages/yew-router-macro/Makefile.toml | 2 +- .../yew-router-macro/tests/routable_derive_test.rs | 2 +- packages/yew-router/Cargo.toml | 2 +- packages/yew/src/platform.rs | 6 +++--- packages/yew/src/server_renderer.rs | 10 +++++----- 17 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.github/workflows/main-checks.yml b/.github/workflows/main-checks.yml index 818d33e47da..51a0e3d42c8 100644 --- a/.github/workflows/main-checks.yml +++ b/.github/workflows/main-checks.yml @@ -130,7 +130,7 @@ jobs: matrix: toolchain: # anyway to dynamically grep the MSRV from Cargo.toml? - - 1.56.0 # MSRV + - 1.60.0 # MSRV - stable steps: @@ -175,7 +175,7 @@ jobs: matrix: toolchain: # anyway to dynamically grep the MSRV from Cargo.toml? - - 1.56.0 # MSRV + - 1.60.0 # MSRV - stable - nightly diff --git a/examples/simple_ssr/src/bin/simple_ssr_server.rs b/examples/simple_ssr/src/bin/simple_ssr_server.rs index fef68668ef0..a6127c68bb9 100644 --- a/examples/simple_ssr/src/bin/simple_ssr_server.rs +++ b/examples/simple_ssr/src/bin/simple_ssr_server.rs @@ -25,7 +25,7 @@ async fn render( Box::new( stream::once(async move { index_html_before }) - .chain(renderer.render_streamed().await) + .chain(renderer.render_stream().await) .chain(stream::once(async move { index_html_after })) .map(|m| Result::<_, BoxedError>::Ok(m.into())), ) diff --git a/examples/ssr_router/src/bin/ssr_router_server.rs b/examples/ssr_router/src/bin/ssr_router_server.rs index fb7e4b153b6..6f8f0fb6901 100644 --- a/examples/ssr_router/src/bin/ssr_router_server.rs +++ b/examples/ssr_router/src/bin/ssr_router_server.rs @@ -36,7 +36,7 @@ async fn render( StreamBody::new( stream::once(async move { index_html_before }) - .chain(renderer.render_streamed().await) + .chain(renderer.render_stream().await) .chain(stream::once(async move { index_html_after })) .map(Result::<_, Infallible>::Ok), ) diff --git a/packages/yew-macro/Cargo.toml b/packages/yew-macro/Cargo.toml index 45aa3a314a1..0d7eee1573f 100644 --- a/packages/yew-macro/Cargo.toml +++ b/packages/yew-macro/Cargo.toml @@ -10,7 +10,7 @@ license = "MIT OR Apache-2.0" keywords = ["web", "wasm", "frontend", "webasm", "webassembly"] categories = ["gui", "web-programming", "wasm"] description = "A framework for making client-side single-page apps" -rust-version = "1.56.0" +rust-version = "1.60.0" [lib] proc-macro = true diff --git a/packages/yew-macro/Makefile.toml b/packages/yew-macro/Makefile.toml index 205f12b085f..999a235920b 100644 --- a/packages/yew-macro/Makefile.toml +++ b/packages/yew-macro/Makefile.toml @@ -1,6 +1,6 @@ [tasks.test] clear = true -toolchain = "1.56.0" +toolchain = "1.60.0" command = "cargo" # test target can be optionally specified like `cargo make test html_macro`, args = ["test", "${@}"] diff --git a/packages/yew-macro/tests/classes_macro_test.rs b/packages/yew-macro/tests/classes_macro_test.rs index 01ea7f34d9c..84b1b5fda48 100644 --- a/packages/yew-macro/tests/classes_macro_test.rs +++ b/packages/yew-macro/tests/classes_macro_test.rs @@ -1,5 +1,5 @@ #[allow(dead_code)] -#[rustversion::attr(stable(1.56), test)] +#[rustversion::attr(stable(1.60), test)] fn classes_macro() { let t = trybuild::TestCases::new(); t.pass("tests/classes_macro/*-pass.rs"); diff --git a/packages/yew-macro/tests/derive_props_test.rs b/packages/yew-macro/tests/derive_props_test.rs index bf6569f20e4..c4d8892e03c 100644 --- a/packages/yew-macro/tests/derive_props_test.rs +++ b/packages/yew-macro/tests/derive_props_test.rs @@ -1,5 +1,5 @@ #[allow(dead_code)] -#[rustversion::attr(stable(1.56), test)] +#[rustversion::attr(stable(1.60), test)] fn derive_props() { let t = trybuild::TestCases::new(); t.pass("tests/derive_props/pass.rs"); diff --git a/packages/yew-macro/tests/function_attr_test.rs b/packages/yew-macro/tests/function_attr_test.rs index 9d943680f6e..d9409490389 100644 --- a/packages/yew-macro/tests/function_attr_test.rs +++ b/packages/yew-macro/tests/function_attr_test.rs @@ -1,5 +1,5 @@ #[allow(dead_code)] -#[rustversion::attr(stable(1.56), test)] +#[rustversion::attr(stable(1.60), test)] fn tests() { let t = trybuild::TestCases::new(); t.pass("tests/function_component_attr/*-pass.rs"); diff --git a/packages/yew-macro/tests/hook_attr_test.rs b/packages/yew-macro/tests/hook_attr_test.rs index e632dfceea0..dae90940ee7 100644 --- a/packages/yew-macro/tests/hook_attr_test.rs +++ b/packages/yew-macro/tests/hook_attr_test.rs @@ -1,5 +1,5 @@ #[allow(dead_code)] -#[rustversion::attr(stable(1.56), test)] +#[rustversion::attr(stable(1.60), test)] fn tests() { let t = trybuild::TestCases::new(); t.pass("tests/hook_attr/*-pass.rs"); diff --git a/packages/yew-macro/tests/html_macro_test.rs b/packages/yew-macro/tests/html_macro_test.rs index e242ebb87e4..b802002897c 100644 --- a/packages/yew-macro/tests/html_macro_test.rs +++ b/packages/yew-macro/tests/html_macro_test.rs @@ -1,7 +1,7 @@ use yew::{html, html_nested}; #[allow(dead_code)] -#[rustversion::attr(stable(1.56), test)] +#[rustversion::attr(stable(1.60), test)] fn html_macro() { let t = trybuild::TestCases::new(); diff --git a/packages/yew-macro/tests/props_macro_test.rs b/packages/yew-macro/tests/props_macro_test.rs index e36eef63d0d..83234005a47 100644 --- a/packages/yew-macro/tests/props_macro_test.rs +++ b/packages/yew-macro/tests/props_macro_test.rs @@ -1,5 +1,5 @@ #[allow(dead_code)] -#[rustversion::attr(stable(1.56), test)] +#[rustversion::attr(stable(1.60), test)] fn props_macro() { let t = trybuild::TestCases::new(); t.pass("tests/props_macro/*-pass.rs"); diff --git a/packages/yew-router-macro/Cargo.toml b/packages/yew-router-macro/Cargo.toml index f44eafdb9bd..e0f5662e354 100644 --- a/packages/yew-router-macro/Cargo.toml +++ b/packages/yew-router-macro/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" license = "MIT OR Apache-2.0" description = "Contains macros used with yew-router" repository = "https://github.com/yewstack/yew" -rust-version = "1.56.0" +rust-version = "1.60.0" [lib] proc-macro = true diff --git a/packages/yew-router-macro/Makefile.toml b/packages/yew-router-macro/Makefile.toml index 7a0101ef374..7b2d8fcba8a 100644 --- a/packages/yew-router-macro/Makefile.toml +++ b/packages/yew-router-macro/Makefile.toml @@ -1,6 +1,6 @@ [tasks.test] clear = true -toolchain = "1.56.0" +toolchain = "1.60.0" command = "cargo" args = ["test"] diff --git a/packages/yew-router-macro/tests/routable_derive_test.rs b/packages/yew-router-macro/tests/routable_derive_test.rs index 1cb913dce17..5c2ef260863 100644 --- a/packages/yew-router-macro/tests/routable_derive_test.rs +++ b/packages/yew-router-macro/tests/routable_derive_test.rs @@ -1,5 +1,5 @@ #[allow(dead_code)] -#[rustversion::attr(stable(1.56), test)] +#[rustversion::attr(stable(1.60), test)] fn tests() { let t = trybuild::TestCases::new(); t.pass("tests/routable_derive/*-pass.rs"); diff --git a/packages/yew-router/Cargo.toml b/packages/yew-router/Cargo.toml index 200f02e2d01..cade55b1e70 100644 --- a/packages/yew-router/Cargo.toml +++ b/packages/yew-router/Cargo.toml @@ -9,7 +9,7 @@ keywords = ["web", "yew", "router"] categories = ["gui", "web-programming"] description = "A router implementation for the Yew framework" repository = "https://github.com/yewstack/yew" -rust-version = "1.56.0" +rust-version = "1.60.0" [dependencies] yew = { version = "0.19.3", path = "../yew", default-features= false } diff --git a/packages/yew/src/platform.rs b/packages/yew/src/platform.rs index 84101c16239..cc03f1c5bfc 100644 --- a/packages/yew/src/platform.rs +++ b/packages/yew/src/platform.rs @@ -1,5 +1,5 @@ -//! module that provides io compatibility over browser tasks and other asyncio runtimes (e.g.: -//! tokio) +//! This module provides io compatibility over browser tasks and other asyncio runtimes (e.g.: +//! tokio). use std::future::Future; @@ -36,7 +36,7 @@ mod arch { use tokio_util::task::LocalPoolHandle; static POOL_HANDLE: Lazy = - Lazy::new(|| LocalPoolHandle::new(num_cpus::get())); + Lazy::new(|| LocalPoolHandle::new(num_cpus::get() * 2)); POOL_HANDLE .spawn_pinned(create_task) diff --git a/packages/yew/src/server_renderer.rs b/packages/yew/src/server_renderer.rs index 5f658f6febb..97dc8eb8c1e 100644 --- a/packages/yew/src/server_renderer.rs +++ b/packages/yew/src/server_renderer.rs @@ -71,7 +71,7 @@ where /// Renders Yew Application to a String. pub async fn render_to_string(self, w: &mut String) { - let mut s = self.render_streamed().await; + let mut s = self.render_stream().await; while let Some(m) = s.next().await { w.push_str(&m); @@ -81,7 +81,7 @@ where /// Renders Yew Applications into a string Stream // Whilst not required to be async here, this function is async to keep the same function // signature as the ServerRenderer. - pub async fn render_streamed(self) -> impl Stream { + pub async fn render_stream(self) -> impl Stream { let (mut tx, rx) = mpsc::unbounded::(); let scope = Scope::::new(None); @@ -168,7 +168,7 @@ where /// Renders Yew Application to a String. pub async fn render_to_string(self, w: &mut String) { - let mut s = self.render_streamed().await; + let mut s = self.render_stream().await; while let Some(m) = s.next().await { w.push_str(&m); @@ -176,13 +176,13 @@ where } /// Renders Yew Applications into a string Stream. - pub async fn render_streamed(self) -> impl Stream { + pub async fn render_stream(self) -> impl Stream { let Self { props, hydratable } = self; run_pinned(move || async move { LocalServerRenderer::::with_props(props) .hydratable(hydratable) - .render_streamed() + .render_stream() .await }) .await From 3eb9f3b50fb342a3a58bdf771c4ccd920e887aa5 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 22 May 2022 21:37:32 +0900 Subject: [PATCH 08/50] Fix trybuild. --- .../tests/classes_macro/classes-fail.stderr | 122 +++++++++------- .../yew-macro/tests/derive_props/fail.stderr | 60 +++++--- .../bad-return-type-fail.stderr | 5 - .../generic-props-fail.stderr | 29 ++-- .../hook_location-fail.stderr | 28 ++-- .../tests/hook_attr/hook_location-fail.stderr | 28 ++-- .../tests/hook_attr/hook_macro-fail.stderr | 28 ++-- .../tests/html_macro/block-fail.stderr | 50 ++++--- .../tests/html_macro/component-fail.stderr | 73 +++++++--- .../component-unimplemented-fail.stderr | 29 ++-- .../tests/html_macro/element-fail.stderr | 130 ++++++------------ .../tests/html_macro/iterable-fail.stderr | 33 ++--- .../tests/html_macro/node-fail.stderr | 42 +++--- 13 files changed, 354 insertions(+), 303 deletions(-) diff --git a/packages/yew-macro/tests/classes_macro/classes-fail.stderr b/packages/yew-macro/tests/classes_macro/classes-fail.stderr index 4ae13819a9a..9e1ec462e91 100644 --- a/packages/yew-macro/tests/classes_macro/classes-fail.stderr +++ b/packages/yew-macro/tests/classes_macro/classes-fail.stderr @@ -1,62 +1,77 @@ error: expected `,` - --> $DIR/classes-fail.rs:7:20 + --> tests/classes_macro/classes-fail.rs:7:20 | 7 | classes!("one" "two"); | ^^^^^ error: string literals must not contain more than one class (hint: use `"two", "three"`) - --> $DIR/classes-fail.rs:18:21 + --> tests/classes_macro/classes-fail.rs:18:21 | 18 | classes!("one", "two three", "four"); | ^^^^^^^^^^^ error[E0277]: the trait bound `Classes: From<{integer}>` is not satisfied - --> $DIR/classes-fail.rs:4:14 - | -4 | classes!(42); - | ^^ the trait `From<{integer}>` is not implemented for `Classes` - | - = help: the following implementations were found: - > - >> - > - > - and 4 others - = note: required because of the requirements on the impl of `Into` for `{integer}` + --> tests/classes_macro/classes-fail.rs:4:14 + | +4 | classes!(42); + | ^^ the trait `From<{integer}>` is not implemented for `Classes` + | + = help: the following implementations were found: + > + >> + > + > + and 4 others + = note: required because of the requirements on the impl of `Into` for `{integer}` +note: required by a bound in `Classes::push` + --> $WORKSPACE/packages/yew/src/html/classes.rs + | + | pub fn push>(&mut self, class: T) { + | ^^^^^^^^^^ required by this bound in `Classes::push` error[E0277]: the trait bound `Classes: From<{float}>` is not satisfied - --> $DIR/classes-fail.rs:5:14 - | -5 | classes!(42.0); - | ^^^^ the trait `From<{float}>` is not implemented for `Classes` - | - = help: the following implementations were found: - > - >> - > - > - and 4 others - = note: required because of the requirements on the impl of `Into` for `{float}` + --> tests/classes_macro/classes-fail.rs:5:14 + | +5 | classes!(42.0); + | ^^^^ the trait `From<{float}>` is not implemented for `Classes` + | + = help: the following implementations were found: + > + >> + > + > + and 4 others + = note: required because of the requirements on the impl of `Into` for `{float}` +note: required by a bound in `Classes::push` + --> $WORKSPACE/packages/yew/src/html/classes.rs + | + | pub fn push>(&mut self, class: T) { + | ^^^^^^^^^^ required by this bound in `Classes::push` error[E0277]: the trait bound `Classes: From<{integer}>` is not satisfied - --> $DIR/classes-fail.rs:9:14 - | -9 | classes!(vec![42]); - | ^^^ the trait `From<{integer}>` is not implemented for `Classes` - | - = help: the following implementations were found: - > - >> - > - > - and 4 others - = note: required because of the requirements on the impl of `Into` for `{integer}` - = note: required because of the requirements on the impl of `From>` for `Classes` - = note: 1 redundant requirements hidden - = note: required because of the requirements on the impl of `Into` for `Vec<{integer}>` + --> tests/classes_macro/classes-fail.rs:9:14 + | +9 | classes!(vec![42]); + | ^^^ the trait `From<{integer}>` is not implemented for `Classes` + | + = help: the following implementations were found: + > + >> + > + > + and 4 others + = note: required because of the requirements on the impl of `Into` for `{integer}` + = note: required because of the requirements on the impl of `From>` for `Classes` + = note: 1 redundant requirement hidden + = note: required because of the requirements on the impl of `Into` for `Vec<{integer}>` +note: required by a bound in `Classes::push` + --> $WORKSPACE/packages/yew/src/html/classes.rs + | + | pub fn push>(&mut self, class: T) { + | ^^^^^^^^^^ required by this bound in `Classes::push` error[E0277]: the trait bound `Classes: From<{integer}>` is not satisfied - --> $DIR/classes-fail.rs:13:14 + --> tests/classes_macro/classes-fail.rs:13:14 | 13 | classes!(some); | ^^^^ the trait `From<{integer}>` is not implemented for `Classes` @@ -69,11 +84,16 @@ error[E0277]: the trait bound `Classes: From<{integer}>` is not satisfied and 4 others = note: required because of the requirements on the impl of `Into` for `{integer}` = note: required because of the requirements on the impl of `From>` for `Classes` - = note: 1 redundant requirements hidden + = note: 1 redundant requirement hidden = note: required because of the requirements on the impl of `Into` for `Option<{integer}>` +note: required by a bound in `Classes::push` + --> $WORKSPACE/packages/yew/src/html/classes.rs + | + | pub fn push>(&mut self, class: T) { + | ^^^^^^^^^^ required by this bound in `Classes::push` error[E0277]: the trait bound `Classes: From` is not satisfied - --> $DIR/classes-fail.rs:14:14 + --> tests/classes_macro/classes-fail.rs:14:14 | 14 | classes!(none); | ^^^^ the trait `From` is not implemented for `Classes` @@ -86,11 +106,16 @@ error[E0277]: the trait bound `Classes: From` is not satisfied and 4 others = note: required because of the requirements on the impl of `Into` for `u32` = note: required because of the requirements on the impl of `From>` for `Classes` - = note: 1 redundant requirements hidden + = note: 1 redundant requirement hidden = note: required because of the requirements on the impl of `Into` for `Option` +note: required by a bound in `Classes::push` + --> $WORKSPACE/packages/yew/src/html/classes.rs + | + | pub fn push>(&mut self, class: T) { + | ^^^^^^^^^^ required by this bound in `Classes::push` error[E0277]: the trait bound `Classes: From<{integer}>` is not satisfied - --> $DIR/classes-fail.rs:16:21 + --> tests/classes_macro/classes-fail.rs:16:21 | 16 | classes!("one", 42); | ^^ the trait `From<{integer}>` is not implemented for `Classes` @@ -102,3 +127,8 @@ error[E0277]: the trait bound `Classes: From<{integer}>` is not satisfied > and 4 others = note: required because of the requirements on the impl of `Into` for `{integer}` +note: required by a bound in `Classes::push` + --> $WORKSPACE/packages/yew/src/html/classes.rs + | + | pub fn push>(&mut self, class: T) { + | ^^^^^^^^^^ required by this bound in `Classes::push` diff --git a/packages/yew-macro/tests/derive_props/fail.stderr b/packages/yew-macro/tests/derive_props/fail.stderr index a56dbe6ccd0..3b243d6e386 100644 --- a/packages/yew-macro/tests/derive_props/fail.stderr +++ b/packages/yew-macro/tests/derive_props/fail.stderr @@ -11,17 +11,19 @@ error: cannot find attribute `props` in this scope | ^^^^^ error[E0425]: cannot find value `foo` in this scope - --> tests/derive_props/fail.rs:87:24 - | -87 | #[prop_or_else(foo)] - | ^^^ not found in this scope - | -help: consider importing one of these items - | -83 | use crate::t10::foo; - | -83 | use crate::t9::foo; - | + --> tests/derive_props/fail.rs:87:24 + | +87 | #[prop_or_else(foo)] + | ^^^ not found in this scope + | +note: these functions exist but are inaccessible + --> tests/derive_props/fail.rs:101:5 + | +101 | fn foo(bar: i32) -> String { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ `crate::t9::foo`: not accessible +... +115 | fn foo() -> i32 { + | ^^^^^^^^^^^^^^^ `crate::t10::foo`: not accessible error[E0599]: no method named `build` found for struct `t3::PropsBuilder` in the current scope --> tests/derive_props/fail.rs:35:26 @@ -45,13 +47,13 @@ error[E0599]: no method named `b` found for struct `t4::PropsBuilder tests/derive_props/fail.rs:9:21 - | -9 | #[derive(Clone, Properties, PartialEq)] - | ^^^^^^^^^^ the trait `Default` is not implemented for `Value` - | -note: required by `Option::::unwrap_or_default` - = note: this error originates in the derive macro `Properties` (in Nightly builds, run with -Z macro-backtrace for more info) + --> tests/derive_props/fail.rs:9:21 + | +9 | #[derive(Clone, Properties, PartialEq)] + | ^^^^^^^^^^ the trait `Default` is not implemented for `Value` + | +note: required by a bound in `Option::::unwrap_or_default` + = note: this error originates in the derive macro `Properties` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0369]: binary operation `==` cannot be applied to type `Value` --> tests/derive_props/fail.rs:13:9 @@ -62,8 +64,16 @@ error[E0369]: binary operation `==` cannot be applied to type `Value` 13 | value: Value, | ^^^^^^^^^^^^ | - = note: an implementation of `std::cmp::PartialEq` might be missing for `Value` +note: an implementation of `PartialEq<_>` might be missing for `Value` + --> tests/derive_props/fail.rs:8:5 + | +8 | struct Value; + | ^^^^^^^^^^^^^ must implement `PartialEq<_>` = note: this error originates in the derive macro `PartialEq` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider annotating `Value` with `#[derive(PartialEq)]` + | +8 | #[derive(PartialEq)] + | error[E0369]: binary operation `!=` cannot be applied to type `Value` --> tests/derive_props/fail.rs:13:9 @@ -74,8 +84,16 @@ error[E0369]: binary operation `!=` cannot be applied to type `Value` 13 | value: Value, | ^^^^^^^^^^^^ | - = note: an implementation of `std::cmp::PartialEq` might be missing for `Value` +note: an implementation of `PartialEq<_>` might be missing for `Value` + --> tests/derive_props/fail.rs:8:5 + | +8 | struct Value; + | ^^^^^^^^^^^^^ must implement `PartialEq<_>` = note: this error originates in the derive macro `PartialEq` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider annotating `Value` with `#[derive(PartialEq)]` + | +8 | #[derive(PartialEq)] + | error[E0308]: mismatched types --> tests/derive_props/fail.rs:67:19 @@ -117,4 +135,4 @@ error[E0271]: type mismatch resolving ` i32 {t10::foo} as FnOnce<()>>::O 111 | #[prop_or_else(foo)] | ^^^ expected struct `String`, found `i32` | -note: required by `Option::::unwrap_or_else` +note: required by a bound in `Option::::unwrap_or_else` diff --git a/packages/yew-macro/tests/function_component_attr/bad-return-type-fail.stderr b/packages/yew-macro/tests/function_component_attr/bad-return-type-fail.stderr index 2e1fff24f1c..6e99e7d6473 100644 --- a/packages/yew-macro/tests/function_component_attr/bad-return-type-fail.stderr +++ b/packages/yew-macro/tests/function_component_attr/bad-return-type-fail.stderr @@ -10,9 +10,4 @@ error[E0277]: the trait bound `u32: IntoHtmlResult` is not satisfied 11 | #[function_component(Comp)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IntoHtmlResult` is not implemented for `u32` | -note: required by `into_html_result` - --> $WORKSPACE/packages/yew/src/html/mod.rs - | - | fn into_html_result(self) -> HtmlResult; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: this error originates in the attribute macro `function_component` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/packages/yew-macro/tests/function_component_attr/generic-props-fail.stderr b/packages/yew-macro/tests/function_component_attr/generic-props-fail.stderr index 1e9c02b2da9..e24faf7e600 100644 --- a/packages/yew-macro/tests/function_component_attr/generic-props-fail.stderr +++ b/packages/yew-macro/tests/function_component_attr/generic-props-fail.stderr @@ -29,16 +29,27 @@ error[E0277]: the trait bound `Comp: yew::BaseComponent` is n as yew::BaseComponent> error[E0599]: the function or associated item `new` exists for struct `VChild>`, but its trait bounds were not satisfied - --> tests/function_component_attr/generic-props-fail.rs:27:14 - | -8 | #[function_component(Comp)] - | --------------------------- doesn't satisfy `Comp: yew::BaseComponent` + --> tests/function_component_attr/generic-props-fail.rs:27:14 + | +8 | #[function_component(Comp)] + | --------------------------- doesn't satisfy `Comp: yew::BaseComponent` ... -27 | html! { /> }; - | ^^^^ function or associated item cannot be called on `VChild>` due to unsatisfied trait bounds - | - = note: the following trait bounds were not satisfied: - `Comp: yew::BaseComponent` +27 | html! { /> }; + | ^^^^ function or associated item cannot be called on `VChild>` due to unsatisfied trait bounds + | + = note: the following trait bounds were not satisfied: + `Comp: yew::BaseComponent` +note: the following trait must be implemented + --> $WORKSPACE/packages/yew/src/html/component/mod.rs + | + | / pub trait BaseComponent: Sized + 'static { + | | /// The Component's Message. + | | type Message: 'static; + | | +... | + | | fn destroy(&mut self, ctx: &Context); + | | } + | |_^ error[E0277]: the trait bound `MissingTypeBounds: yew::Properties` is not satisfied --> tests/function_component_attr/generic-props-fail.rs:27:14 diff --git a/packages/yew-macro/tests/function_component_attr/hook_location-fail.stderr b/packages/yew-macro/tests/function_component_attr/hook_location-fail.stderr index d40b4cfef08..7c65da37aaa 100644 --- a/packages/yew-macro/tests/function_component_attr/hook_location-fail.stderr +++ b/packages/yew-macro/tests/function_component_attr/hook_location-fail.stderr @@ -1,7 +1,7 @@ error: hooks cannot be called at this position. - = help: move hooks to the top-level of your function. - = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks + = help: move hooks to the top-level of your function. + = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks --> tests/function_component_attr/hook_location-fail.rs:9:9 | @@ -10,8 +10,8 @@ error: hooks cannot be called at this position. error: hooks cannot be called at this position. - = help: move hooks to the top-level of your function. - = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks + = help: move hooks to the top-level of your function. + = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks --> tests/function_component_attr/hook_location-fail.rs:14:9 | @@ -20,8 +20,8 @@ error: hooks cannot be called at this position. error: hooks cannot be called at this position. - = help: move hooks to the top-level of your function. - = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks + = help: move hooks to the top-level of your function. + = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks --> tests/function_component_attr/hook_location-fail.rs:19:9 | @@ -30,8 +30,8 @@ error: hooks cannot be called at this position. error: hooks cannot be called at this position. - = help: move hooks to the top-level of your function. - = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks + = help: move hooks to the top-level of your function. + = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks --> tests/function_component_attr/hook_location-fail.rs:22:26 | @@ -40,8 +40,8 @@ error: hooks cannot be called at this position. error: hooks cannot be called at this position. - = help: move hooks to the top-level of your function. - = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks + = help: move hooks to the top-level of your function. + = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks --> tests/function_component_attr/hook_location-fail.rs:23:9 | @@ -50,8 +50,8 @@ error: hooks cannot be called at this position. error: hooks cannot be called at this position. - = help: move hooks to the top-level of your function. - = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks + = help: move hooks to the top-level of your function. + = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks --> tests/function_component_attr/hook_location-fail.rs:27:20 | @@ -60,8 +60,8 @@ error: hooks cannot be called at this position. error: hooks cannot be called at this position. - = help: move hooks to the top-level of your function. - = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks + = help: move hooks to the top-level of your function. + = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks --> tests/function_component_attr/hook_location-fail.rs:34:9 | diff --git a/packages/yew-macro/tests/hook_attr/hook_location-fail.stderr b/packages/yew-macro/tests/hook_attr/hook_location-fail.stderr index 1f9d4697c6b..0836a6a1ac7 100644 --- a/packages/yew-macro/tests/hook_attr/hook_location-fail.stderr +++ b/packages/yew-macro/tests/hook_attr/hook_location-fail.stderr @@ -1,7 +1,7 @@ error: hooks cannot be called at this position. - = help: move hooks to the top-level of your function. - = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks + = help: move hooks to the top-level of your function. + = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks --> tests/hook_attr/hook_location-fail.rs:9:9 | @@ -10,8 +10,8 @@ error: hooks cannot be called at this position. error: hooks cannot be called at this position. - = help: move hooks to the top-level of your function. - = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks + = help: move hooks to the top-level of your function. + = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks --> tests/hook_attr/hook_location-fail.rs:14:9 | @@ -20,8 +20,8 @@ error: hooks cannot be called at this position. error: hooks cannot be called at this position. - = help: move hooks to the top-level of your function. - = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks + = help: move hooks to the top-level of your function. + = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks --> tests/hook_attr/hook_location-fail.rs:19:9 | @@ -30,8 +30,8 @@ error: hooks cannot be called at this position. error: hooks cannot be called at this position. - = help: move hooks to the top-level of your function. - = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks + = help: move hooks to the top-level of your function. + = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks --> tests/hook_attr/hook_location-fail.rs:22:26 | @@ -40,8 +40,8 @@ error: hooks cannot be called at this position. error: hooks cannot be called at this position. - = help: move hooks to the top-level of your function. - = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks + = help: move hooks to the top-level of your function. + = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks --> tests/hook_attr/hook_location-fail.rs:23:9 | @@ -50,8 +50,8 @@ error: hooks cannot be called at this position. error: hooks cannot be called at this position. - = help: move hooks to the top-level of your function. - = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks + = help: move hooks to the top-level of your function. + = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks --> tests/hook_attr/hook_location-fail.rs:27:20 | @@ -60,8 +60,8 @@ error: hooks cannot be called at this position. error: hooks cannot be called at this position. - = help: move hooks to the top-level of your function. - = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks + = help: move hooks to the top-level of your function. + = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks --> tests/hook_attr/hook_location-fail.rs:34:9 | diff --git a/packages/yew-macro/tests/hook_attr/hook_macro-fail.stderr b/packages/yew-macro/tests/hook_attr/hook_macro-fail.stderr index ca7561ea43c..13e8c3ba574 100644 --- a/packages/yew-macro/tests/hook_attr/hook_macro-fail.stderr +++ b/packages/yew-macro/tests/hook_attr/hook_macro-fail.stderr @@ -1,7 +1,7 @@ error: hooks cannot be called at this position. - = help: move hooks to the top-level of your function. - = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks + = help: move hooks to the top-level of your function. + = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks --> tests/hook_attr/hook_macro-fail.rs:20:9 | @@ -10,24 +10,18 @@ error: hooks cannot be called at this position. error: hooks cannot be called at this position. - = help: move hooks to the top-level of your function. - = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks + = help: move hooks to the top-level of your function. + = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks --> tests/hook_attr/hook_macro-fail.rs:22:9 | 22 | use_some_macro!("b") | ^^^^^^^^^^^^^^ -warning: unused macro definition - --> tests/hook_attr/hook_macro-fail.rs:8:1 - | -8 | / macro_rules! use_some_macro { -9 | | () => { -10 | | use_some_macro_inner("default str") -11 | | }; -... | -14 | | }; -15 | | } - | |_^ - | - = note: `#[warn(unused_macros)]` on by default +warning: unused macro definition: `use_some_macro` + --> tests/hook_attr/hook_macro-fail.rs:8:14 + | +8 | macro_rules! use_some_macro { + | ^^^^^^^^^^^^^^ + | + = note: `#[warn(unused_macros)]` on by default diff --git a/packages/yew-macro/tests/html_macro/block-fail.stderr b/packages/yew-macro/tests/html_macro/block-fail.stderr index 0f7099ee5b9..8fcaeb95fcf 100644 --- a/packages/yew-macro/tests/html_macro/block-fail.stderr +++ b/packages/yew-macro/tests/html_macro/block-fail.stderr @@ -1,32 +1,30 @@ error[E0277]: `()` doesn't implement `std::fmt::Display` - --> tests/html_macro/block-fail.rs:6:15 - | -6 | { () } - | ^^ `()` cannot be formatted with the default formatter - | - = help: the trait `std::fmt::Display` is not implemented for `()` - = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead - = note: required because of the requirements on the impl of `ToString` for `()` - = note: required because of the requirements on the impl of `From<()>` for `VNode` - = note: required because of the requirements on the impl of `Into` for `()` - = note: 2 redundant requirements hidden - = note: required because of the requirements on the impl of `Into>` for `()` -note: required by `into` + --> tests/html_macro/block-fail.rs:6:15 + | +6 | { () } + | ^^ `()` cannot be formatted with the default formatter + | + = help: the trait `std::fmt::Display` is not implemented for `()` + = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead + = note: required because of the requirements on the impl of `ToString` for `()` + = note: required because of the requirements on the impl of `From<()>` for `VNode` + = note: required because of the requirements on the impl of `Into` for `()` + = note: 2 redundant requirements hidden + = note: required because of the requirements on the impl of `Into>` for `()` error[E0277]: `()` doesn't implement `std::fmt::Display` - --> tests/html_macro/block-fail.rs:12:16 - | -12 |
{ not_tree() }
- | ^^^^^^^^ `()` cannot be formatted with the default formatter - | - = help: the trait `std::fmt::Display` is not implemented for `()` - = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead - = note: required because of the requirements on the impl of `ToString` for `()` - = note: required because of the requirements on the impl of `From<()>` for `VNode` - = note: required because of the requirements on the impl of `Into` for `()` - = note: 2 redundant requirements hidden - = note: required because of the requirements on the impl of `Into>` for `()` -note: required by `into` + --> tests/html_macro/block-fail.rs:12:16 + | +12 |
{ not_tree() }
+ | ^^^^^^^^ `()` cannot be formatted with the default formatter + | + = help: the trait `std::fmt::Display` is not implemented for `()` + = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead + = note: required because of the requirements on the impl of `ToString` for `()` + = note: required because of the requirements on the impl of `From<()>` for `VNode` + = note: required because of the requirements on the impl of `Into` for `()` + = note: 2 redundant requirements hidden + = note: required because of the requirements on the impl of `Into>` for `()` error[E0277]: `()` doesn't implement `std::fmt::Display` --> tests/html_macro/block-fail.rs:15:17 diff --git a/packages/yew-macro/tests/html_macro/component-fail.stderr b/packages/yew-macro/tests/html_macro/component-fail.stderr index 5374e76ba40..721dc2c98e9 100644 --- a/packages/yew-macro/tests/html_macro/component-fail.stderr +++ b/packages/yew-macro/tests/html_macro/component-fail.stderr @@ -160,10 +160,10 @@ error: expected identifier, found keyword `type` 71 | html! { }; | ^^^^ expected identifier, found keyword | -help: you can escape reserved keywords to use them as identifiers +help: escape `type` to use it as an identifier | 71 | html! { }; - | ~~~~~~ + | ++ error: the property value must be either a literal or enclosed in braces. Consider adding braces around your expression. --> tests/html_macro/component-fail.rs:72:24 @@ -333,33 +333,55 @@ error[E0277]: the trait bound `(): IntoPropValue` is not satisfied --> tests/html_macro/component-fail.rs:77:33 | 77 | html! { }; - | ^^ the trait `IntoPropValue` is not implemented for `()` + | ------ ^^ the trait `IntoPropValue` is not implemented for `()` + | | + | required by a bound introduced by this call + | +note: required by a bound in `ChildPropertiesBuilder::::string` + --> tests/html_macro/component-fail.rs:4:17 + | +4 | #[derive(Clone, Properties, PartialEq)] + | ^^^^^^^^^^ required by this bound in `ChildPropertiesBuilder::::string` +... +7 | pub string: String, + | ------ required by a bound in this + = note: this error originates in the derive macro `Properties` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `{integer}: IntoPropValue` is not satisfied --> tests/html_macro/component-fail.rs:78:33 | 78 | html! { }; - | ^ the trait `IntoPropValue` is not implemented for `{integer}` + | ------ ^ the trait `IntoPropValue` is not implemented for `{integer}` + | | + | required by a bound introduced by this call | - = help: the following implementations were found: - <&'static str as IntoPropValue> - <&'static str as IntoPropValue> - <&'static str as IntoPropValue>> - <&'static str as IntoPropValue>> - and 18 others +note: required by a bound in `ChildPropertiesBuilder::::string` + --> tests/html_macro/component-fail.rs:4:17 + | +4 | #[derive(Clone, Properties, PartialEq)] + | ^^^^^^^^^^ required by this bound in `ChildPropertiesBuilder::::string` +... +7 | pub string: String, + | ------ required by a bound in this + = note: this error originates in the derive macro `Properties` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `{integer}: IntoPropValue` is not satisfied --> tests/html_macro/component-fail.rs:79:34 | 79 | html! { }; - | ^ the trait `IntoPropValue` is not implemented for `{integer}` + | ------ ^ the trait `IntoPropValue` is not implemented for `{integer}` + | | + | required by a bound introduced by this call | - = help: the following implementations were found: - <&'static str as IntoPropValue> - <&'static str as IntoPropValue> - <&'static str as IntoPropValue>> - <&'static str as IntoPropValue>> - and 18 others +note: required by a bound in `ChildPropertiesBuilder::::string` + --> tests/html_macro/component-fail.rs:4:17 + | +4 | #[derive(Clone, Properties, PartialEq)] + | ^^^^^^^^^^ required by this bound in `ChildPropertiesBuilder::::string` +... +7 | pub string: String, + | ------ required by a bound in this + = note: this error originates in the derive macro `Properties` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0308]: mismatched types --> tests/html_macro/component-fail.rs:80:31 @@ -371,7 +393,19 @@ error[E0277]: the trait bound `u32: IntoPropValue` is not satisfied --> tests/html_macro/component-fail.rs:82:24 | 82 | html! { }; - | ^^^^ the trait `IntoPropValue` is not implemented for `u32` + | --- ^^^^ the trait `IntoPropValue` is not implemented for `u32` + | | + | required by a bound introduced by this call + | +note: required by a bound in `ChildPropertiesBuilder::::int` + --> tests/html_macro/component-fail.rs:4:17 + | +4 | #[derive(Clone, Properties, PartialEq)] + | ^^^^^^^^^^ required by this bound in `ChildPropertiesBuilder::::int` +... +8 | pub int: i32, + | --- required by a bound in this + = note: this error originates in the derive macro `Properties` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0599]: no method named `string` found for struct `ChildPropertiesBuilder` in the current scope --> tests/html_macro/component-fail.rs:83:20 @@ -441,7 +475,6 @@ error[E0277]: the trait bound `VChild: From` is | ^^^^^^^^^^^^^ the trait `From` is not implemented for `VChild` | = note: required because of the requirements on the impl of `Into>` for `yew::virtual_dom::VText` -note: required by `into` error[E0277]: the trait bound `VChild: From` is not satisfied --> tests/html_macro/component-fail.rs:102:29 @@ -450,7 +483,6 @@ error[E0277]: the trait bound `VChild: From` is not satisfied | ^ the trait `From` is not implemented for `VChild` | = note: required because of the requirements on the impl of `Into>` for `VNode` -note: required by `into` error[E0277]: the trait bound `VChild: From` is not satisfied --> tests/html_macro/component-fail.rs:103:30 @@ -459,4 +491,3 @@ error[E0277]: the trait bound `VChild: From` is not satisfied | ^^^^^ the trait `From` is not implemented for `VChild` | = note: required because of the requirements on the impl of `Into>` for `VNode` -note: required by `into` diff --git a/packages/yew-macro/tests/html_macro/component-unimplemented-fail.stderr b/packages/yew-macro/tests/html_macro/component-unimplemented-fail.stderr index 4b062ceeda5..3ef5318c72c 100644 --- a/packages/yew-macro/tests/html_macro/component-unimplemented-fail.stderr +++ b/packages/yew-macro/tests/html_macro/component-unimplemented-fail.stderr @@ -7,13 +7,24 @@ error[E0277]: the trait bound `Unimplemented: yew::Component` is not satisfied = note: required because of the requirements on the impl of `BaseComponent` for `Unimplemented` error[E0599]: the function or associated item `new` exists for struct `VChild`, but its trait bounds were not satisfied - --> tests/html_macro/component-unimplemented-fail.rs:6:14 - | -3 | struct Unimplemented; - | --------------------- doesn't satisfy `Unimplemented: BaseComponent` + --> tests/html_macro/component-unimplemented-fail.rs:6:14 + | +3 | struct Unimplemented; + | --------------------- doesn't satisfy `Unimplemented: BaseComponent` ... -6 | html! { }; - | ^^^^^^^^^^^^^ function or associated item cannot be called on `VChild` due to unsatisfied trait bounds - | - = note: the following trait bounds were not satisfied: - `Unimplemented: BaseComponent` +6 | html! { }; + | ^^^^^^^^^^^^^ function or associated item cannot be called on `VChild` due to unsatisfied trait bounds + | + = note: the following trait bounds were not satisfied: + `Unimplemented: BaseComponent` +note: the following trait must be implemented + --> $WORKSPACE/packages/yew/src/html/component/mod.rs + | + | / pub trait BaseComponent: Sized + 'static { + | | /// The Component's Message. + | | type Message: 'static; + | | +... | + | | fn destroy(&mut self, ctx: &Context); + | | } + | |_^ diff --git a/packages/yew-macro/tests/html_macro/element-fail.stderr b/packages/yew-macro/tests/html_macro/element-fail.stderr index 61b85a3fc60..cc306914f70 100644 --- a/packages/yew-macro/tests/html_macro/element-fail.stderr +++ b/packages/yew-macro/tests/html_macro/element-fail.stderr @@ -251,54 +251,33 @@ error[E0277]: the trait bound `(): IntoPropValue>` is not sati | 43 | html! { }; | ^^ the trait `IntoPropValue>` is not implemented for `()` - | -note: required by `into_prop_value` - --> $WORKSPACE/packages/yew/src/html/conversion.rs - | - | fn into_prop_value(self) -> T; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0277]: the trait bound `(): IntoPropValue>` is not satisfied --> tests/html_macro/element-fail.rs:44:27 | 44 | html! { }; | ^^ the trait `IntoPropValue>` is not implemented for `()` - | -note: required by `into_prop_value` - --> $WORKSPACE/packages/yew/src/html/conversion.rs - | - | fn into_prop_value(self) -> T; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0277]: the trait bound `(): IntoPropValue>` is not satisfied --> tests/html_macro/element-fail.rs:45:22 | 45 | html! { }; | ^^ the trait `IntoPropValue>` is not implemented for `()` - | -note: required by `into_prop_value` - --> $WORKSPACE/packages/yew/src/html/conversion.rs - | - | fn into_prop_value(self) -> T; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0277]: the trait bound `NotToString: IntoPropValue>` is not satisfied --> tests/html_macro/element-fail.rs:46:28 | 46 | html! { }; | ^^^^^^^^^^^ the trait `IntoPropValue>` is not implemented for `NotToString` - | -note: required by `into_prop_value` - --> $WORKSPACE/packages/yew/src/html/conversion.rs - | - | fn into_prop_value(self) -> T; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0277]: the trait bound `Option: IntoPropValue>` is not satisfied --> tests/html_macro/element-fail.rs:47:23 | 47 | html! { }; - | ^^^^^^^^^^^^^^^^^ the trait `IntoPropValue>` is not implemented for `Option` + | ----^^^^^^^^^^^^^ + | | + | the trait `IntoPropValue>` is not implemented for `Option` + | required by a bound introduced by this call | = help: the following implementations were found: as IntoPropValue>> @@ -306,17 +285,15 @@ error[E0277]: the trait bound `Option: IntoPropValue as IntoPropValue>>> as IntoPropValue>> > as IntoPropValue>> -note: required by `into_prop_value` - --> $WORKSPACE/packages/yew/src/html/conversion.rs - | - | fn into_prop_value(self) -> T; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0277]: the trait bound `Option<{integer}>: IntoPropValue>` is not satisfied --> tests/html_macro/element-fail.rs:48:22 | 48 | html! { }; - | ^^^^^^^ the trait `IntoPropValue>` is not implemented for `Option<{integer}>` + | ----^^^ + | | + | the trait `IntoPropValue>` is not implemented for `Option<{integer}>` + | required by a bound introduced by this call | = help: the following implementations were found: as IntoPropValue>> @@ -324,17 +301,15 @@ error[E0277]: the trait bound `Option<{integer}>: IntoPropValue as IntoPropValue>>> as IntoPropValue>> > as IntoPropValue>> -note: required by `into_prop_value` - --> $WORKSPACE/packages/yew/src/html/conversion.rs - | - | fn into_prop_value(self) -> T; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0277]: expected a `Fn<(MouseEvent,)>` closure, found `{integer}` --> tests/html_macro/element-fail.rs:51:28 | 51 | html! { }; - | ^ expected an `Fn<(MouseEvent,)>` closure, found `{integer}` + | -----------------------^----- + | | | + | | expected an `Fn<(MouseEvent,)>` closure, found `{integer}` + | required by a bound introduced by this call | = help: the trait `Fn<(MouseEvent,)>` is not implemented for `{integer}` = note: required because of the requirements on the impl of `IntoEventCallback` for `{integer}` @@ -355,12 +330,12 @@ error[E0277]: expected a `Fn<(MouseEvent,)>` closure, found `yew::Callback tests/html_macro/element-fail.rs:52:29 | 52 | html! { }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | | - | expected an implementor of trait `IntoEventCallback` - | help: consider borrowing here: `&Callback::from(|a: String| ())` + | ------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^------ + | | | + | | expected an `Fn<(MouseEvent,)>` closure, found `yew::Callback` + | required by a bound introduced by this call | - = note: the trait bound `yew::Callback: IntoEventCallback` is not satisfied + = help: the trait `Fn<(MouseEvent,)>` is not implemented for `yew::Callback` = note: required because of the requirements on the impl of `IntoEventCallback` for `yew::Callback` note: required by a bound in `yew::html::onclick::Wrapper::__macro_new` --> $WORKSPACE/packages/yew/src/html/listener/events.rs @@ -379,7 +354,10 @@ error[E0277]: the trait bound `Option<{integer}>: IntoEventCallback` --> tests/html_macro/element-fail.rs:53:29 | 53 | html! { }; - | ^^^^^^^ the trait `IntoEventCallback` is not implemented for `Option<{integer}>` + | ------------------------^^^^^^^------ + | | | + | | the trait `IntoEventCallback` is not implemented for `Option<{integer}>` + | required by a bound introduced by this call | = help: the following implementations were found: as IntoEventCallback> @@ -402,18 +380,15 @@ error[E0277]: the trait bound `(): IntoPropValue` is not satisfied | 56 | html! { }; | ^^ the trait `IntoPropValue` is not implemented for `()` - | -note: required by `into_prop_value` - --> $WORKSPACE/packages/yew/src/html/conversion.rs - | - | fn into_prop_value(self) -> T; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0277]: the trait bound `Option: IntoPropValue` is not satisfied --> tests/html_macro/element-fail.rs:57:25 | 57 | html! { }; - | ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IntoPropValue` is not implemented for `Option` + | ----^^^^^^^^^^^^^^^^^^^^ + | | + | the trait `IntoPropValue` is not implemented for `Option` + | required by a bound introduced by this call | = help: the following implementations were found: as IntoPropValue>> @@ -421,22 +396,17 @@ error[E0277]: the trait bound `Option: IntoPropValue as IntoPropValue>>> as IntoPropValue>> > as IntoPropValue>> -note: required by `into_prop_value` - --> $WORKSPACE/packages/yew/src/html/conversion.rs - | - | fn into_prop_value(self) -> T; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0277]: expected a `Fn<(MouseEvent,)>` closure, found `yew::Callback` --> tests/html_macro/element-fail.rs:58:29 | 58 | html! { }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | | - | expected an implementor of trait `IntoEventCallback` - | help: consider borrowing here: `&Callback::from(|a: String| ())` + | ------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^------ + | | | + | | expected an `Fn<(MouseEvent,)>` closure, found `yew::Callback` + | required by a bound introduced by this call | - = note: the trait bound `yew::Callback: IntoEventCallback` is not satisfied + = help: the trait `Fn<(MouseEvent,)>` is not implemented for `yew::Callback` = note: required because of the requirements on the impl of `IntoEventCallback` for `yew::Callback` note: required by a bound in `yew::html::onclick::Wrapper::__macro_new` --> $WORKSPACE/packages/yew/src/html/listener/events.rs @@ -456,36 +426,26 @@ error[E0277]: the trait bound `NotToString: IntoPropValue>` is | 60 | html! { }; | ^^^^^^^^^^^ the trait `IntoPropValue>` is not implemented for `NotToString` - | -note: required by `into_prop_value` - --> $WORKSPACE/packages/yew/src/html/conversion.rs - | - | fn into_prop_value(self) -> T; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0277]: the trait bound `(): IntoPropValue` is not satisfied --> tests/html_macro/element-fail.rs:62:25 | 62 | html! { }; | ^^ the trait `IntoPropValue` is not implemented for `()` - | -note: required by `into_prop_value` - --> $WORKSPACE/packages/yew/src/html/conversion.rs - | - | fn into_prop_value(self) -> T; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0277]: the trait bound `Cow<'static, str>: From<{integer}>` is not satisfied - --> tests/html_macro/element-fail.rs:77:15 - | -77 | html! { <@{55}> }; - | ^^^^ the trait `From<{integer}>` is not implemented for `Cow<'static, str>` - | - = help: the following implementations were found: - as From<&'a CStr>> - as From<&'a CString>> - as From> - as From<&'a OsStr>> - and 11 others - = note: required because of the requirements on the impl of `Into>` for `{integer}` -note: required by `into` + --> tests/html_macro/element-fail.rs:77:15 + | +77 | html! { <@{55}> }; + | ^--^ + | || + | |this tail expression is of type `_` + | the trait `From<{integer}>` is not implemented for `Cow<'static, str>` + | + = help: the following implementations were found: + as From<&'a CStr>> + as From<&'a CString>> + as From> + as From<&'a OsStr>> + and 11 others + = note: required because of the requirements on the impl of `Into>` for `{integer}` diff --git a/packages/yew-macro/tests/html_macro/iterable-fail.stderr b/packages/yew-macro/tests/html_macro/iterable-fail.stderr index b1702551117..f2ebfc0daac 100644 --- a/packages/yew-macro/tests/html_macro/iterable-fail.stderr +++ b/packages/yew-macro/tests/html_macro/iterable-fail.stderr @@ -5,24 +5,25 @@ error: expected an expression after the keyword `for` | ^^^ error[E0277]: `()` is not an iterator - --> tests/html_macro/iterable-fail.rs:5:17 - | -5 | html! { for () }; - | ^^ `()` is not an iterator - | - = help: the trait `Iterator` is not implemented for `()` - = note: required because of the requirements on the impl of `IntoIterator` for `()` -note: required by `into_iter` + --> tests/html_macro/iterable-fail.rs:5:17 + | +5 | html! { for () }; + | ^^ `()` is not an iterator + | + = help: the trait `Iterator` is not implemented for `()` + = note: required because of the requirements on the impl of `IntoIterator` for `()` error[E0277]: `()` is not an iterator - --> tests/html_macro/iterable-fail.rs:6:17 - | -6 | html! { for {()} }; - | ^^^^ `()` is not an iterator - | - = help: the trait `Iterator` is not implemented for `()` - = note: required because of the requirements on the impl of `IntoIterator` for `()` -note: required by `into_iter` + --> tests/html_macro/iterable-fail.rs:6:17 + | +6 | html! { for {()} }; + | ^--^ + | || + | |this tail expression is of type `_` + | `()` is not an iterator + | + = help: the trait `Iterator` is not implemented for `()` + = note: required because of the requirements on the impl of `IntoIterator` for `()` error[E0277]: `()` doesn't implement `std::fmt::Display` --> tests/html_macro/iterable-fail.rs:7:17 diff --git a/packages/yew-macro/tests/html_macro/node-fail.stderr b/packages/yew-macro/tests/html_macro/node-fail.stderr index 5fb050c2ba1..ea203719ea6 100644 --- a/packages/yew-macro/tests/html_macro/node-fail.stderr +++ b/packages/yew-macro/tests/html_macro/node-fail.stderr @@ -41,25 +41,27 @@ error[E0425]: cannot find value `invalid` in this scope | ^^^^^^^ not found in this scope error[E0277]: `()` doesn't implement `std::fmt::Display` - --> tests/html_macro/node-fail.rs:6:13 - | -6 | html! { () }; - | ^^ `()` cannot be formatted with the default formatter - | - = help: the trait `std::fmt::Display` is not implemented for `()` - = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead - = note: required because of the requirements on the impl of `ToString` for `()` - = note: required because of the requirements on the impl of `From<()>` for `VNode` -note: required by `from` + --> tests/html_macro/node-fail.rs:6:5 + | +6 | html! { () }; + | ^^^^^^^^^^^^ `()` cannot be formatted with the default formatter + | + = help: the trait `std::fmt::Display` is not implemented for `()` + = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead + = note: required because of the requirements on the impl of `ToString` for `()` + = note: required because of the requirements on the impl of `From<()>` for `VNode` + = note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: `()` doesn't implement `std::fmt::Display` - --> tests/html_macro/node-fail.rs:17:9 - | -17 | not_node() - | ^^^^^^^^^^ `()` cannot be formatted with the default formatter - | - = help: the trait `std::fmt::Display` is not implemented for `()` - = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead - = note: required because of the requirements on the impl of `ToString` for `()` - = note: required because of the requirements on the impl of `From<()>` for `VNode` -note: required by `from` + --> tests/html_macro/node-fail.rs:16:5 + | +16 | / html! { +17 | | not_node() +18 | | }; + | |_____^ `()` cannot be formatted with the default formatter + | + = help: the trait `std::fmt::Display` is not implemented for `()` + = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead + = note: required because of the requirements on the impl of `ToString` for `()` + = note: required because of the requirements on the impl of `From<()>` for `VNode` + = note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info) From bcf41829f34970c1f70db19af0b9dd74a7a5fb14 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 22 May 2022 23:25:54 +0900 Subject: [PATCH 09/50] Optimise Memory Allocation. --- packages/yew/src/virtual_dom/vcomp.rs | 3 ++- packages/yew/src/virtual_dom/vlist.rs | 20 +++++++++++++++----- packages/yew/src/virtual_dom/vtag.rs | 4 +++- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/packages/yew/src/virtual_dom/vcomp.rs b/packages/yew/src/virtual_dom/vcomp.rs index 3e3ae189a55..d10d87be241 100644 --- a/packages/yew/src/virtual_dom/vcomp.rs +++ b/packages/yew/src/virtual_dom/vcomp.rs @@ -133,8 +133,9 @@ impl Mountable for PropsWrapper { parent_scope: &'a AnyScope, hydratable: bool, ) -> LocalBoxFuture<'a, ()> { + let scope: Scope = Scope::new(Some(parent_scope.clone())); + async move { - let scope: Scope = Scope::new(Some(parent_scope.clone())); scope .render_into_stream(tx, self.props.clone(), hydratable) .await; diff --git a/packages/yew/src/virtual_dom/vlist.rs b/packages/yew/src/virtual_dom/vlist.rs index 53708c7781d..99bdfab88c0 100644 --- a/packages/yew/src/virtual_dom/vlist.rs +++ b/packages/yew/src/virtual_dom/vlist.rs @@ -159,8 +159,20 @@ mod feat_ssr { parent_scope: &AnyScope, hydratable: bool, ) { + if self.children.len() < 2 { + match self.children.first() { + Some(m) => { + m.render_into_stream(tx, parent_scope, hydratable).await; + } + + None => {} + } + + return; + } + // Concurrently render all children. - let children: FuturesOrdered<_> = self + let mut children: FuturesOrdered<_> = self .children .iter() .map(|m| async move { @@ -168,14 +180,12 @@ mod feat_ssr { m.render_into_stream(&mut tx, parent_scope, hydratable) .await; - drop(tx); - rx + let s: String = rx.collect().await; + s }) .collect(); - let mut children = children.flatten(); - while let Some(m) = children.next().await { let _ = tx.unbounded_send(m); } diff --git a/packages/yew/src/virtual_dom/vtag.rs b/packages/yew/src/virtual_dom/vtag.rs index c58887f8a20..f8813d29418 100644 --- a/packages/yew/src/virtual_dom/vtag.rs +++ b/packages/yew/src/virtual_dom/vtag.rs @@ -449,7 +449,9 @@ mod feat_ssr { parent_scope: &AnyScope, hydratable: bool, ) { - let mut start_tag = "<".to_string(); + // Preallocate a String that is big enough for most elements. + let mut start_tag = String::with_capacity(50); + start_tag.push('<'); start_tag.push_str(self.tag()); let write_attr = |w: &mut String, name: &str, val: Option<&str>| { From ff0ad940812061ef85603358f96a4b96cf08cee1 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 22 May 2022 23:53:27 +0900 Subject: [PATCH 10/50] More optimisation. --- packages/yew/src/virtual_dom/vcomp.rs | 1 + packages/yew/src/virtual_dom/vnode.rs | 13 ++++++++++--- packages/yew/src/virtual_dom/vtag.rs | 19 ++++++++++++++----- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/packages/yew/src/virtual_dom/vcomp.rs b/packages/yew/src/virtual_dom/vcomp.rs index d10d87be241..9716cb2df3a 100644 --- a/packages/yew/src/virtual_dom/vcomp.rs +++ b/packages/yew/src/virtual_dom/vcomp.rs @@ -243,6 +243,7 @@ mod feat_ssr { use crate::html::AnyScope; impl VComp { + #[inline] pub(crate) async fn render_into_stream( &self, tx: &mut UnboundedSender, diff --git a/packages/yew/src/virtual_dom/vnode.rs b/packages/yew/src/virtual_dom/vnode.rs index 771e063add8..6b5a7ad39e1 100644 --- a/packages/yew/src/virtual_dom/vnode.rs +++ b/packages/yew/src/virtual_dom/vnode.rs @@ -160,8 +160,13 @@ mod feat_ssr { parent_scope: &'a AnyScope, hydratable: bool, ) -> LocalBoxFuture<'a, ()> { - async move { - match self { + async fn render_into_stream_( + this: &VNode, + tx: &mut UnboundedSender, + parent_scope: &AnyScope, + hydratable: bool, + ) { + match this { VNode::VTag(vtag) => { vtag.render_into_stream(tx, parent_scope, hydratable).await } @@ -191,7 +196,9 @@ mod feat_ssr { } } } - .boxed_local() + + async move { render_into_stream_(self, tx, parent_scope, hydratable).await } + .boxed_local() } } } diff --git a/packages/yew/src/virtual_dom/vtag.rs b/packages/yew/src/virtual_dom/vtag.rs index f8813d29418..503a65ae3c4 100644 --- a/packages/yew/src/virtual_dom/vtag.rs +++ b/packages/yew/src/virtual_dom/vtag.rs @@ -450,16 +450,25 @@ mod feat_ssr { hydratable: bool, ) { // Preallocate a String that is big enough for most elements. - let mut start_tag = String::with_capacity(50); + let mut start_tag = String::with_capacity(64); start_tag.push('<'); start_tag.push_str(self.tag()); let write_attr = |w: &mut String, name: &str, val: Option<&str>| { - write!(w, " {}", name).unwrap(); - - if let Some(m) = val { - write!(w, "=\"{}\"", html_escape::encode_double_quoted_attribute(m)).unwrap(); + match val { + Some(val) => { + write!( + w, + "{}=\"{}\"", + name, + html_escape::encode_double_quoted_attribute(val) + ) + } + None => { + write!(w, " {}", name) + } } + .unwrap(); }; if let VTagInner::Input(_) = self.inner { From e5b76a7ece9625f40feaf4f1f9d50687070bafa6 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Mon, 23 May 2022 01:28:24 +0900 Subject: [PATCH 11/50] BufWriter. --- packages/yew/src/html/component/scope.rs | 18 +++---- packages/yew/src/server_renderer.rs | 63 +++++++++++++++++++++-- packages/yew/src/virtual_dom/mod.rs | 36 +++++-------- packages/yew/src/virtual_dom/vcomp.rs | 14 ++--- packages/yew/src/virtual_dom/vlist.rs | 23 +++++---- packages/yew/src/virtual_dom/vnode.rs | 20 ++++--- packages/yew/src/virtual_dom/vsuspense.rs | 11 ++-- packages/yew/src/virtual_dom/vtag.rs | 57 +++++++++----------- packages/yew/src/virtual_dom/vtext.rs | 6 +-- 9 files changed, 141 insertions(+), 107 deletions(-) diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index d59dcd30730..2d33150d6b4 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -260,7 +260,6 @@ impl Scope { #[cfg(feature = "ssr")] mod feat_ssr { - use futures::channel::mpsc::UnboundedSender; use futures::channel::oneshot; use super::*; @@ -268,19 +267,18 @@ mod feat_ssr { ComponentRenderState, CreateRunner, DestroyRunner, RenderRunner, }; use crate::scheduler; + use crate::server_renderer::BufWriter; use crate::virtual_dom::Collectable; impl Scope { pub(crate) async fn render_into_stream( &self, - tx: &mut UnboundedSender, + w: &mut BufWriter, props: Rc, hydratable: bool, ) { - let (html_tx, html_rx) = oneshot::channel(); - let state = ComponentRenderState::Ssr { - sender: Some(html_tx), - }; + let (tx, rx) = oneshot::channel(); + let state = ComponentRenderState::Ssr { sender: Some(tx) }; scheduler::push_component_create( self.id, @@ -298,17 +296,17 @@ mod feat_ssr { let collectable = Collectable::for_component::(); if hydratable { - collectable.write_open_tag(tx); + collectable.write_open_tag(w); } - let html = html_rx.await.unwrap(); + let html = rx.await.unwrap(); let self_any_scope = AnyScope::from(self.clone()); - html.render_into_stream(tx, &self_any_scope, hydratable) + html.render_into_stream(w, &self_any_scope, hydratable) .await; if hydratable { - collectable.write_close_tag(tx); + collectable.write_close_tag(w); } scheduler::push_component_destroy(Box::new(DestroyRunner { diff --git a/packages/yew/src/server_renderer.rs b/packages/yew/src/server_renderer.rs index 97dc8eb8c1e..fbb67a44214 100644 --- a/packages/yew/src/server_renderer.rs +++ b/packages/yew/src/server_renderer.rs @@ -1,9 +1,66 @@ -use futures::channel::mpsc; +use std::borrow::Cow; + +use futures::channel::mpsc::{self, UnboundedSender}; use futures::stream::{Stream, StreamExt}; use crate::html::{BaseComponent, Scope}; use crate::platform::{run_pinned, spawn_local}; +pub(crate) struct BufWriter { + buf: String, + tx: UnboundedSender, +} + +impl BufWriter { + pub fn new() -> (Self, impl Stream) { + let (tx, rx) = mpsc::unbounded::(); + + let this = Self { + buf: String::with_capacity(4096), + tx, + }; + + (this, rx) + } + + /// Writes a string into the buffer, optionally drains the buffer. + pub fn write(&mut self, s: Cow<'_, str>) { + if s.len() > 4096 { + // if the next chunk is more than 4096, we drain the buffer and the next + // chunk. + if !self.buf.is_empty() { + let mut buf = String::with_capacity(4096); + std::mem::swap(&mut buf, &mut self.buf); + let _ = self.tx.unbounded_send(buf); + } + + let _ = self.tx.unbounded_send(s.into_owned()); + } else if self.buf.len() + s.len() < 4096 { + // The length of current chunk and the next part is less than 4096, we push + // it on to the buffer. + self.buf.push_str(&s); + } else { + // The length of current chunk and the next part is more than 4096, we send + // the current buffer and make the next chunk the new buffer. + let mut buf = s.into_owned(); + + buf.reserve(4096); + std::mem::swap(&mut buf, &mut self.buf); + let _ = self.tx.unbounded_send(buf); + } + } +} + +impl Drop for BufWriter { + fn drop(&mut self) { + if !self.buf.is_empty() { + let mut buf = "".to_string(); + std::mem::swap(&mut buf, &mut self.buf); + let _ = self.tx.unbounded_send(buf); + } + } +} + /// A Yew Server-side Renderer that renders on the current thread. #[cfg_attr(documenting, doc(cfg(feature = "ssr")))] #[derive(Debug)] @@ -82,12 +139,12 @@ where // Whilst not required to be async here, this function is async to keep the same function // signature as the ServerRenderer. pub async fn render_stream(self) -> impl Stream { - let (mut tx, rx) = mpsc::unbounded::(); + let (mut w, rx) = BufWriter::new(); let scope = Scope::::new(None); spawn_local(async move { scope - .render_into_stream(&mut tx, self.props.into(), self.hydratable) + .render_into_stream(&mut w, self.props.into(), self.hydratable) .await; }); diff --git a/packages/yew/src/virtual_dom/mod.rs b/packages/yew/src/virtual_dom/mod.rs index fad826bd973..0eb099172ce 100644 --- a/packages/yew/src/virtual_dom/mod.rs +++ b/packages/yew/src/virtual_dom/mod.rs @@ -265,46 +265,36 @@ pub(crate) use feat_ssr_hydration::*; #[cfg(feature = "ssr")] mod feat_ssr { - use futures::channel::mpsc::UnboundedSender; - use super::*; + use crate::server_renderer::BufWriter; impl Collectable { - pub fn write_open_tag(&self, tx: &mut UnboundedSender) { - // - let mut w = String::with_capacity(11); - - w.push_str(""); - - let _ = tx.unbounded_send(w); + w.write(self.end_mark().into()); + w.write("-->".into()); } - pub fn write_close_tag(&self, tx: &mut UnboundedSender) { - // - let mut w = String::with_capacity(12); - w.push_str(""); - - let _ = tx.unbounded_send(w); + w.write(self.end_mark().into()); + w.write("-->".into()); } } } diff --git a/packages/yew/src/virtual_dom/vcomp.rs b/packages/yew/src/virtual_dom/vcomp.rs index 9716cb2df3a..b8ee8df4da9 100644 --- a/packages/yew/src/virtual_dom/vcomp.rs +++ b/packages/yew/src/virtual_dom/vcomp.rs @@ -4,8 +4,6 @@ use std::any::TypeId; use std::fmt; use std::rc::Rc; -#[cfg(feature = "ssr")] -use futures::channel::mpsc::UnboundedSender; #[cfg(feature = "ssr")] use futures::future::{FutureExt, LocalBoxFuture}; #[cfg(feature = "csr")] @@ -21,6 +19,8 @@ use crate::html::Scoped; #[cfg(any(feature = "ssr", feature = "csr"))] use crate::html::{AnyScope, Scope}; use crate::html::{BaseComponent, NodeRef}; +#[cfg(feature = "ssr")] +use crate::server_renderer::BufWriter; /// A virtual component. pub struct VComp { @@ -71,7 +71,7 @@ pub(crate) trait Mountable { #[cfg(feature = "ssr")] fn render_into_stream<'a>( &'a self, - w: &'a mut UnboundedSender, + w: &'a mut BufWriter, parent_scope: &'a AnyScope, hydratable: bool, ) -> LocalBoxFuture<'a, ()>; @@ -129,7 +129,7 @@ impl Mountable for PropsWrapper { #[cfg(feature = "ssr")] fn render_into_stream<'a>( &'a self, - tx: &'a mut UnboundedSender, + w: &'a mut BufWriter, parent_scope: &'a AnyScope, hydratable: bool, ) -> LocalBoxFuture<'a, ()> { @@ -137,7 +137,7 @@ impl Mountable for PropsWrapper { async move { scope - .render_into_stream(tx, self.props.clone(), hydratable) + .render_into_stream(w, self.props.clone(), hydratable) .await; } .boxed_local() @@ -246,13 +246,13 @@ mod feat_ssr { #[inline] pub(crate) async fn render_into_stream( &self, - tx: &mut UnboundedSender, + w: &mut BufWriter, parent_scope: &AnyScope, hydratable: bool, ) { self.mountable .as_ref() - .render_into_stream(tx, parent_scope, hydratable) + .render_into_stream(w, parent_scope, hydratable) .await; } } diff --git a/packages/yew/src/virtual_dom/vlist.rs b/packages/yew/src/virtual_dom/vlist.rs index 99bdfab88c0..3e40a11284b 100644 --- a/packages/yew/src/virtual_dom/vlist.rs +++ b/packages/yew/src/virtual_dom/vlist.rs @@ -146,23 +146,23 @@ mod test { #[cfg(feature = "ssr")] mod feat_ssr { - use futures::channel::mpsc::{self, UnboundedSender}; use futures::stream::{FuturesOrdered, StreamExt}; use super::*; use crate::html::AnyScope; + use crate::server_renderer::BufWriter; impl VList { pub(crate) async fn render_into_stream( &self, - tx: &mut UnboundedSender, + w: &mut BufWriter, parent_scope: &AnyScope, hydratable: bool, ) { if self.children.len() < 2 { match self.children.first() { Some(m) => { - m.render_into_stream(tx, parent_scope, hydratable).await; + m.render_into_stream(w, parent_scope, hydratable).await; } None => {} @@ -176,18 +176,19 @@ mod feat_ssr { .children .iter() .map(|m| async move { - let (mut tx, rx) = mpsc::unbounded(); + let (mut w, rx) = BufWriter::new(); - m.render_into_stream(&mut tx, parent_scope, hydratable) - .await; - drop(tx); + m.render_into_stream(&mut w, parent_scope, hydratable).await; + drop(w); - let s: String = rx.collect().await; - s + rx }) .collect(); - while let Some(m) = children.next().await { - let _ = tx.unbounded_send(m); + + while let Some(mut rx) = children.next().await { + while let Some(next_chunk) = rx.next().await { + w.write(next_chunk.into()); + } } } } diff --git a/packages/yew/src/virtual_dom/vnode.rs b/packages/yew/src/virtual_dom/vnode.rs index 6b5a7ad39e1..40d862f7fc4 100644 --- a/packages/yew/src/virtual_dom/vnode.rs +++ b/packages/yew/src/virtual_dom/vnode.rs @@ -147,37 +147,35 @@ impl PartialEq for VNode { #[cfg(feature = "ssr")] mod feat_ssr { - use futures::channel::mpsc::UnboundedSender; use futures::future::{FutureExt, LocalBoxFuture}; use super::*; use crate::html::AnyScope; + use crate::server_renderer::BufWriter; impl VNode { pub(crate) fn render_into_stream<'a>( &'a self, - tx: &'a mut UnboundedSender, + w: &'a mut BufWriter, parent_scope: &'a AnyScope, hydratable: bool, ) -> LocalBoxFuture<'a, ()> { async fn render_into_stream_( this: &VNode, - tx: &mut UnboundedSender, + w: &mut BufWriter, parent_scope: &AnyScope, hydratable: bool, ) { match this { - VNode::VTag(vtag) => { - vtag.render_into_stream(tx, parent_scope, hydratable).await - } + VNode::VTag(vtag) => vtag.render_into_stream(w, parent_scope, hydratable).await, VNode::VText(vtext) => { - vtext.render_into_stream(tx, parent_scope, hydratable).await + vtext.render_into_stream(w, parent_scope, hydratable).await } VNode::VComp(vcomp) => { - vcomp.render_into_stream(tx, parent_scope, hydratable).await + vcomp.render_into_stream(w, parent_scope, hydratable).await } VNode::VList(vlist) => { - vlist.render_into_stream(tx, parent_scope, hydratable).await + vlist.render_into_stream(w, parent_scope, hydratable).await } // We are pretty safe here as it's not possible to get a web_sys::Node without // DOM support in the first place. @@ -191,13 +189,13 @@ mod feat_ssr { VNode::VPortal(_) => {} VNode::VSuspense(vsuspense) => { vsuspense - .render_into_stream(tx, parent_scope, hydratable) + .render_into_stream(w, parent_scope, hydratable) .await } } } - async move { render_into_stream_(self, tx, parent_scope, hydratable).await } + async move { render_into_stream_(self, w, parent_scope, hydratable).await } .boxed_local() } } diff --git a/packages/yew/src/virtual_dom/vsuspense.rs b/packages/yew/src/virtual_dom/vsuspense.rs index 7e54428dd2b..1c9479e492f 100644 --- a/packages/yew/src/virtual_dom/vsuspense.rs +++ b/packages/yew/src/virtual_dom/vsuspense.rs @@ -26,32 +26,31 @@ impl VSuspense { #[cfg(feature = "ssr")] mod feat_ssr { - use futures::channel::mpsc::UnboundedSender; - use super::*; use crate::html::AnyScope; + use crate::server_renderer::BufWriter; use crate::virtual_dom::Collectable; impl VSuspense { pub(crate) async fn render_into_stream( &self, - tx: &mut UnboundedSender, + w: &mut BufWriter, parent_scope: &AnyScope, hydratable: bool, ) { let collectable = Collectable::Suspense; if hydratable { - collectable.write_open_tag(tx); + collectable.write_open_tag(w); } // always render children on the server side. self.children - .render_into_stream(tx, parent_scope, hydratable) + .render_into_stream(w, parent_scope, hydratable) .await; if hydratable { - collectable.write_close_tag(tx); + collectable.write_close_tag(w); } } } diff --git a/packages/yew/src/virtual_dom/vtag.rs b/packages/yew/src/virtual_dom/vtag.rs index 503a65ae3c4..6895e72d189 100644 --- a/packages/yew/src/virtual_dom/vtag.rs +++ b/packages/yew/src/virtual_dom/vtag.rs @@ -428,12 +428,9 @@ impl PartialEq for VTag { #[cfg(feature = "ssr")] mod feat_ssr { - use std::fmt::Write; - - use futures::channel::mpsc::UnboundedSender; - use super::*; use crate::html::AnyScope; + use crate::server_renderer::BufWriter; use crate::virtual_dom::VText; // Elements that cannot have any child elements. @@ -445,59 +442,53 @@ mod feat_ssr { impl VTag { pub(crate) async fn render_into_stream( &self, - tx: &mut UnboundedSender, + w: &mut BufWriter, parent_scope: &AnyScope, hydratable: bool, ) { - // Preallocate a String that is big enough for most elements. - let mut start_tag = String::with_capacity(64); - start_tag.push('<'); - start_tag.push_str(self.tag()); - - let write_attr = |w: &mut String, name: &str, val: Option<&str>| { - match val { - Some(val) => { - write!( - w, - "{}=\"{}\"", - name, - html_escape::encode_double_quoted_attribute(val) - ) - } - None => { - write!(w, " {}", name) - } + w.write("<".into()); + w.write(self.tag().into()); + + let write_attr = |w: &mut BufWriter, name: &str, val: Option<&str>| match val { + Some(val) => w.write( + format!( + "{}=\"{}\"", + name, + html_escape::encode_double_quoted_attribute(val) + ) + .into(), + ), + None => { + w.write(name.into()); } - .unwrap(); }; if let VTagInner::Input(_) = self.inner { if let Some(m) = self.value() { - write_attr(&mut start_tag, "value", Some(m)); + write_attr(w, "value", Some(m)); } if self.checked() { - write_attr(&mut start_tag, "checked", None); + write_attr(w, "checked", None); } } for (k, v) in self.attributes.iter() { - write_attr(&mut start_tag, k, Some(v)); + write_attr(w, k, Some(v)); } - start_tag.push('>'); - let _ = tx.unbounded_send(start_tag); + w.write(">".into()); match self.inner { VTagInner::Input(_) => {} VTagInner::Textarea { .. } => { if let Some(m) = self.value() { VText::new(m.to_owned()) - .render_into_stream(tx, parent_scope, hydratable) + .render_into_stream(w, parent_scope, hydratable) .await; } - let _ = tx.unbounded_send("".into()); + w.write("".into()); } VTagInner::Other { ref tag, @@ -506,10 +497,10 @@ mod feat_ssr { } => { if !VOID_ELEMENTS.contains(&tag.as_ref()) { children - .render_into_stream(tx, parent_scope, hydratable) + .render_into_stream(w, parent_scope, hydratable) .await; - let _ = tx.unbounded_send(format!("", tag)); + w.write(format!("", tag).into()); } else { // We don't write children of void elements nor closing tags. debug_assert!(children.is_empty(), "{} cannot have any children!", tag); diff --git a/packages/yew/src/virtual_dom/vtext.rs b/packages/yew/src/virtual_dom/vtext.rs index 9ac1dae7d97..f0bcb2fb684 100644 --- a/packages/yew/src/virtual_dom/vtext.rs +++ b/packages/yew/src/virtual_dom/vtext.rs @@ -34,22 +34,22 @@ impl PartialEq for VText { #[cfg(feature = "ssr")] mod feat_ssr { - use futures::channel::mpsc::UnboundedSender; use super::*; use crate::html::AnyScope; + use crate::server_renderer::BufWriter; impl VText { pub(crate) async fn render_into_stream( &self, - tx: &mut UnboundedSender, + w: &mut BufWriter, _parent_scope: &AnyScope, _hydratable: bool, ) { let mut s = String::with_capacity(self.text.len()); html_escape::encode_text_to_string(&self.text, &mut s); - let _ = tx.unbounded_send(s); + w.write(s.into()); } } } From d4b15f6322f2ff0599715d4c76524f1f38698824 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Mon, 23 May 2022 01:37:58 +0900 Subject: [PATCH 12/50] Fix tests. --- packages/yew/src/virtual_dom/vtag.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/yew/src/virtual_dom/vtag.rs b/packages/yew/src/virtual_dom/vtag.rs index 6895e72d189..22b4cc43056 100644 --- a/packages/yew/src/virtual_dom/vtag.rs +++ b/packages/yew/src/virtual_dom/vtag.rs @@ -452,13 +452,14 @@ mod feat_ssr { let write_attr = |w: &mut BufWriter, name: &str, val: Option<&str>| match val { Some(val) => w.write( format!( - "{}=\"{}\"", + " {}=\"{}\"", name, html_escape::encode_double_quoted_attribute(val) ) .into(), ), None => { + w.write(" ".into()); w.write(name.into()); } }; From 39f1878fe104a10a8e6e1f7a2619828c72dcb969 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Tue, 24 May 2022 19:39:02 +0900 Subject: [PATCH 13/50] Optimise BufWriter. --- packages/yew/src/server_renderer.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/yew/src/server_renderer.rs b/packages/yew/src/server_renderer.rs index fbb67a44214..3eb8453b981 100644 --- a/packages/yew/src/server_renderer.rs +++ b/packages/yew/src/server_renderer.rs @@ -35,16 +35,15 @@ impl BufWriter { } let _ = self.tx.unbounded_send(s.into_owned()); - } else if self.buf.len() + s.len() < 4096 { - // The length of current chunk and the next part is less than 4096, we push - // it on to the buffer. + } else if self.buf.capacity() >= s.len() { + // There is enough capacity, we push it on to the buffer. self.buf.push_str(&s); } else { // The length of current chunk and the next part is more than 4096, we send - // the current buffer and make the next chunk the new buffer. - let mut buf = s.into_owned(); + // the current buffer and make a new buffer. + let mut buf = String::with_capacity(4096); + buf.push_str(&s); - buf.reserve(4096); std::mem::swap(&mut buf, &mut self.buf); let _ = self.tx.unbounded_send(buf); } From 511b1c8c6ea598ae75b6a0f40a4de955a4747381 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Tue, 24 May 2022 19:56:09 +0900 Subject: [PATCH 14/50] Remove more allocations. --- packages/yew/src/virtual_dom/vtag.rs | 24 +++++++++++------------- packages/yew/src/virtual_dom/vtext.rs | 6 ++---- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/packages/yew/src/virtual_dom/vtag.rs b/packages/yew/src/virtual_dom/vtag.rs index 22b4cc43056..ba718925d42 100644 --- a/packages/yew/src/virtual_dom/vtag.rs +++ b/packages/yew/src/virtual_dom/vtag.rs @@ -449,18 +449,14 @@ mod feat_ssr { w.write("<".into()); w.write(self.tag().into()); - let write_attr = |w: &mut BufWriter, name: &str, val: Option<&str>| match val { - Some(val) => w.write( - format!( - " {}=\"{}\"", - name, - html_escape::encode_double_quoted_attribute(val) - ) - .into(), - ), - None => { - w.write(" ".into()); - w.write(name.into()); + let write_attr = |w: &mut BufWriter, name: &str, val: Option<&str>| { + w.write(" ".into()); + w.write(name.into()); + + if let Some(m) = val { + w.write("=\"".into()); + w.write(html_escape::encode_double_quoted_attribute(m)); + w.write("\"".into()); } }; @@ -501,7 +497,9 @@ mod feat_ssr { .render_into_stream(w, parent_scope, hydratable) .await; - w.write(format!("", tag).into()); + w.write(Cow::Borrowed("")); } else { // We don't write children of void elements nor closing tags. debug_assert!(children.is_empty(), "{} cannot have any children!", tag); diff --git a/packages/yew/src/virtual_dom/vtext.rs b/packages/yew/src/virtual_dom/vtext.rs index f0bcb2fb684..0db82384a00 100644 --- a/packages/yew/src/virtual_dom/vtext.rs +++ b/packages/yew/src/virtual_dom/vtext.rs @@ -46,10 +46,8 @@ mod feat_ssr { _parent_scope: &AnyScope, _hydratable: bool, ) { - let mut s = String::with_capacity(self.text.len()); - html_escape::encode_text_to_string(&self.text, &mut s); - - w.write(s.into()); + let s = html_escape::encode_text(&self.text); + w.write(s); } } } From 13827e9cc161e9f889efc21bd0af3b4a51e412b4 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Tue, 24 May 2022 20:26:13 +0900 Subject: [PATCH 15/50] Allow setting of buffer capacity. --- packages/yew/Cargo.toml | 2 +- .../use_prepared_state/feat_hydration.rs | 2 +- .../hooks/use_prepared_state/feat_ssr.rs | 2 +- packages/yew/src/html/component/scope.rs | 6 +-- packages/yew/src/server_renderer.rs | 51 ++++++++++++++++--- packages/yew/src/virtual_dom/vlist.rs | 4 +- tools/changelog/Cargo.toml | 2 +- 7 files changed, 53 insertions(+), 16 deletions(-) diff --git a/packages/yew/Cargo.toml b/packages/yew/Cargo.toml index 57c06f555ab..7ed8c9fe2d9 100644 --- a/packages/yew/Cargo.toml +++ b/packages/yew/Cargo.toml @@ -97,7 +97,7 @@ features = [ tokio = ["dep:tokio", "dep:num_cpus", "dep:tokio-util"] ssr = ["dep:futures", "dep:html-escape", "dep:base64ct", "dep:bincode"] csr = [] -hydration = ["csr", "bincode"] +hydration = ["csr", "dep:bincode"] default = [] [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] diff --git a/packages/yew/src/functional/hooks/use_prepared_state/feat_hydration.rs b/packages/yew/src/functional/hooks/use_prepared_state/feat_hydration.rs index 597c0c4c790..c6e6fde3ccf 100644 --- a/packages/yew/src/functional/hooks/use_prepared_state/feat_hydration.rs +++ b/packages/yew/src/functional/hooks/use_prepared_state/feat_hydration.rs @@ -9,7 +9,7 @@ use wasm_bindgen::JsValue; use super::PreparedStateBase; use crate::functional::{use_state, Hook, HookContext}; -use crate::io_coop::spawn_local; +use crate::platform::spawn_local; use crate::suspense::{Suspension, SuspensionResult}; #[cfg(target_arch = "wasm32")] diff --git a/packages/yew/src/functional/hooks/use_prepared_state/feat_ssr.rs b/packages/yew/src/functional/hooks/use_prepared_state/feat_ssr.rs index 47d08179e4c..fab2331e0ae 100644 --- a/packages/yew/src/functional/hooks/use_prepared_state/feat_ssr.rs +++ b/packages/yew/src/functional/hooks/use_prepared_state/feat_ssr.rs @@ -9,7 +9,7 @@ use serde::Serialize; use super::PreparedStateBase; use crate::functional::{use_memo, use_state, Hook, HookContext}; -use crate::io_coop::spawn_local; +use crate::platform::spawn_local; use crate::suspense::{Suspension, SuspensionResult}; #[doc(hidden)] diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index 0e904852d43..0a26c1ac6ac 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -308,9 +308,9 @@ mod feat_ssr { .await; if let Some(prepared_state) = self.get_component().unwrap().prepare_state() { - w.push_str(r#""#); + w.write(r#""#.into()); } if hydratable { diff --git a/packages/yew/src/server_renderer.rs b/packages/yew/src/server_renderer.rs index 3eb8453b981..3c6e282327e 100644 --- a/packages/yew/src/server_renderer.rs +++ b/packages/yew/src/server_renderer.rs @@ -6,30 +6,38 @@ use futures::stream::{Stream, StreamExt}; use crate::html::{BaseComponent, Scope}; use crate::platform::{run_pinned, spawn_local}; +const DEFAULT_BUF_SIZE: usize = 8 * 1024; + pub(crate) struct BufWriter { buf: String, tx: UnboundedSender, + capacity: usize, } impl BufWriter { - pub fn new() -> (Self, impl Stream) { + pub fn with_capacity(capacity: usize) -> (Self, impl Stream) { let (tx, rx) = mpsc::unbounded::(); let this = Self { - buf: String::with_capacity(4096), + buf: String::with_capacity(capacity), tx, + capacity, }; (this, rx) } + pub fn capacity(&self) -> usize { + self.capacity + } + /// Writes a string into the buffer, optionally drains the buffer. pub fn write(&mut self, s: Cow<'_, str>) { if s.len() > 4096 { - // if the next chunk is more than 4096, we drain the buffer and the next + // if the next chunk is more than buffer size, we drain the buffer and the next // chunk. if !self.buf.is_empty() { - let mut buf = String::with_capacity(4096); + let mut buf = String::with_capacity(self.capacity); std::mem::swap(&mut buf, &mut self.buf); let _ = self.tx.unbounded_send(buf); } @@ -41,7 +49,7 @@ impl BufWriter { } else { // The length of current chunk and the next part is more than 4096, we send // the current buffer and make a new buffer. - let mut buf = String::with_capacity(4096); + let mut buf = String::with_capacity(self.capacity); buf.push_str(&s); std::mem::swap(&mut buf, &mut self.buf); @@ -69,6 +77,7 @@ where { props: COMP::Properties, hydratable: bool, + capacity: usize, } impl Default for LocalServerRenderer @@ -101,9 +110,19 @@ where Self { props, hydratable: true, + capacity: DEFAULT_BUF_SIZE, } } + /// Sets the capacity of renderer buffer. + /// + /// Default: `8192` + pub fn capacity(mut self, capacity: usize) -> Self { + self.capacity = capacity; + + self + } + /// Sets whether an the rendered result is hydratable. /// /// Defaults to `true`. @@ -138,7 +157,7 @@ where // Whilst not required to be async here, this function is async to keep the same function // signature as the ServerRenderer. pub async fn render_stream(self) -> impl Stream { - let (mut w, rx) = BufWriter::new(); + let (mut w, rx) = BufWriter::with_capacity(self.capacity); let scope = Scope::::new(None); spawn_local(async move { @@ -165,6 +184,7 @@ where { props: COMP::Properties, hydratable: bool, + capacity: usize, } impl Default for ServerRenderer @@ -198,9 +218,19 @@ where Self { props, hydratable: true, + capacity: DEFAULT_BUF_SIZE, } } + /// Sets the capacity of renderer buffer. + /// + /// Default: `8192` + pub fn capacity(mut self, capacity: usize) -> Self { + self.capacity = capacity; + + self + } + /// Sets whether an the rendered result is hydratable. /// /// Defaults to `true`. @@ -233,11 +263,16 @@ where /// Renders Yew Applications into a string Stream. pub async fn render_stream(self) -> impl Stream { - let Self { props, hydratable } = self; - run_pinned(move || async move { + let Self { + props, + hydratable, + capacity, + } = self; + LocalServerRenderer::::with_props(props) .hydratable(hydratable) + .capacity(capacity) .render_stream() .await }) diff --git a/packages/yew/src/virtual_dom/vlist.rs b/packages/yew/src/virtual_dom/vlist.rs index 3e40a11284b..c140c303ac0 100644 --- a/packages/yew/src/virtual_dom/vlist.rs +++ b/packages/yew/src/virtual_dom/vlist.rs @@ -171,12 +171,14 @@ mod feat_ssr { return; } + let buf_capacity = w.capacity(); + // Concurrently render all children. let mut children: FuturesOrdered<_> = self .children .iter() .map(|m| async move { - let (mut w, rx) = BufWriter::new(); + let (mut w, rx) = BufWriter::with_capacity(buf_capacity); m.render_into_stream(&mut w, parent_scope, hydratable).await; drop(w); diff --git a/tools/changelog/Cargo.toml b/tools/changelog/Cargo.toml index e114ad5fe69..0bed984d901 100644 --- a/tools/changelog/Cargo.toml +++ b/tools/changelog/Cargo.toml @@ -9,7 +9,7 @@ edition = "2021" [dependencies] anyhow = "1" chrono = "0.4" -git2 = "=0.14.2" # see https://github.com/rust-lang/git2-rs/issues/838 fixed with MSRV 1.60 +git2 = "0.14" regex = "1" reqwest = { version = "0.11", features = ["blocking", "json"] } serde = { version = "1", features = ["derive"] } From 8b159fc898b9df16352cca0fa4241a7bc42d5456 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Tue, 24 May 2022 20:44:24 +0900 Subject: [PATCH 16/50] Fix capacity size. --- packages/yew/src/server_renderer.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/yew/src/server_renderer.rs b/packages/yew/src/server_renderer.rs index 3c6e282327e..8896cdd36d6 100644 --- a/packages/yew/src/server_renderer.rs +++ b/packages/yew/src/server_renderer.rs @@ -6,8 +6,9 @@ use futures::stream::{Stream, StreamExt}; use crate::html::{BaseComponent, Scope}; use crate::platform::{run_pinned, spawn_local}; -const DEFAULT_BUF_SIZE: usize = 8 * 1024; +const DEFAULT_BUF_SIZE: usize = 4 * 1024; +/// A [`futures::io::BufWriter`], but operates over string and yields into a Stream. pub(crate) struct BufWriter { buf: String, tx: UnboundedSender, @@ -27,15 +28,15 @@ impl BufWriter { (this, rx) } - pub fn capacity(&self) -> usize { + pub const fn capacity(&self) -> usize { self.capacity } /// Writes a string into the buffer, optionally drains the buffer. pub fn write(&mut self, s: Cow<'_, str>) { - if s.len() > 4096 { - // if the next chunk is more than buffer size, we drain the buffer and the next - // chunk. + if s.len() > self.capacity { + // if the next part is more than buffer size, we drain the buffer and the next + // part. if !self.buf.is_empty() { let mut buf = String::with_capacity(self.capacity); std::mem::swap(&mut buf, &mut self.buf); @@ -47,7 +48,7 @@ impl BufWriter { // There is enough capacity, we push it on to the buffer. self.buf.push_str(&s); } else { - // The length of current chunk and the next part is more than 4096, we send + // The next part is not going to fit into the buffer, we send // the current buffer and make a new buffer. let mut buf = String::with_capacity(self.capacity); buf.push_str(&s); From 65a4c2075d74af92031162c870665665ec54691c Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Tue, 24 May 2022 20:45:10 +0900 Subject: [PATCH 17/50] Fix capacity size. --- packages/yew/src/server_renderer.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/yew/src/server_renderer.rs b/packages/yew/src/server_renderer.rs index 8896cdd36d6..eb009dffa3a 100644 --- a/packages/yew/src/server_renderer.rs +++ b/packages/yew/src/server_renderer.rs @@ -6,7 +6,8 @@ use futures::stream::{Stream, StreamExt}; use crate::html::{BaseComponent, Scope}; use crate::platform::{run_pinned, spawn_local}; -const DEFAULT_BUF_SIZE: usize = 4 * 1024; +// Same as std::io::BufWriter and futures::io::BufWriter. +const DEFAULT_BUF_SIZE: usize = 8 * 1024; /// A [`futures::io::BufWriter`], but operates over string and yields into a Stream. pub(crate) struct BufWriter { From 03e10a6b009559519b39b0f14c097831f8913663 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Tue, 24 May 2022 20:50:37 +0900 Subject: [PATCH 18/50] Remove unneeded const notation. --- packages/yew/src/server_renderer.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/yew/src/server_renderer.rs b/packages/yew/src/server_renderer.rs index eb009dffa3a..112ba4f1f80 100644 --- a/packages/yew/src/server_renderer.rs +++ b/packages/yew/src/server_renderer.rs @@ -29,7 +29,8 @@ impl BufWriter { (this, rx) } - pub const fn capacity(&self) -> usize { + #[inline] + pub fn capacity(&self) -> usize { self.capacity } From f8f198bfbd1e667b41826fdf955dfb7f1918a320 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Tue, 24 May 2022 21:05:20 +0900 Subject: [PATCH 19/50] Fix macro tests. --- .../tests/function_component_attr/generic-props-fail.stderr | 2 +- .../tests/html_macro/component-unimplemented-fail.stderr | 2 +- packages/yew/src/platform.rs | 6 +++++- packages/yew/src/server_renderer.rs | 5 ++--- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/yew-macro/tests/function_component_attr/generic-props-fail.stderr b/packages/yew-macro/tests/function_component_attr/generic-props-fail.stderr index e24faf7e600..b1bcc168d68 100644 --- a/packages/yew-macro/tests/function_component_attr/generic-props-fail.stderr +++ b/packages/yew-macro/tests/function_component_attr/generic-props-fail.stderr @@ -47,7 +47,7 @@ note: the following trait must be implemented | | type Message: 'static; | | ... | - | | fn destroy(&mut self, ctx: &Context); + | | fn prepare_state(&self) -> Option; | | } | |_^ diff --git a/packages/yew-macro/tests/html_macro/component-unimplemented-fail.stderr b/packages/yew-macro/tests/html_macro/component-unimplemented-fail.stderr index 3ef5318c72c..6d7bdf7f39d 100644 --- a/packages/yew-macro/tests/html_macro/component-unimplemented-fail.stderr +++ b/packages/yew-macro/tests/html_macro/component-unimplemented-fail.stderr @@ -25,6 +25,6 @@ note: the following trait must be implemented | | type Message: 'static; | | ... | - | | fn destroy(&mut self, ctx: &Context); + | | fn prepare_state(&self) -> Option; | | } | |_^ diff --git a/packages/yew/src/platform.rs b/packages/yew/src/platform.rs index cc03f1c5bfc..9a4bbfddc66 100644 --- a/packages/yew/src/platform.rs +++ b/packages/yew/src/platform.rs @@ -87,9 +87,13 @@ where arch::spawn_local(f); } -/// Runs a task with it pinned onto a worker thread. +/// Runs a task with it pinned onto a local worker thread. /// /// This can be used to execute non-Send futures without blocking the current thread. +/// +/// It maintains an internal thread pool dedicated to executing local futures. +/// +/// [`spawn_local`] is available with tasks executed with `run_pinned`. #[inline(always)] pub async fn run_pinned(create_task: F) -> Fut::Output where diff --git a/packages/yew/src/server_renderer.rs b/packages/yew/src/server_renderer.rs index 112ba4f1f80..e3e193f0339 100644 --- a/packages/yew/src/server_renderer.rs +++ b/packages/yew/src/server_renderer.rs @@ -175,9 +175,8 @@ where /// A Yew Server-side Renderer. /// -/// For runtimes with multi-threading support, -/// Yew manages a default worker pool with the number of worker thread equal to the CPU running -/// cores. You may override the spawning logic with +/// The renderer spawns the rendering task with [`run_pinned`] which maintains an internal worker +/// pool. #[cfg_attr(documenting, doc(cfg(feature = "ssr")))] #[derive(Debug)] pub struct ServerRenderer From 45d218c037f3516118e5e3a8d7ac8f5712867b98 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sat, 28 May 2022 00:29:45 +0900 Subject: [PATCH 20/50] Slightly optimises BufWriter committing logic. --- packages/yew/src/server_renderer.rs | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/packages/yew/src/server_renderer.rs b/packages/yew/src/server_renderer.rs index e3e193f0339..17d02fefca8 100644 --- a/packages/yew/src/server_renderer.rs +++ b/packages/yew/src/server_renderer.rs @@ -39,13 +39,30 @@ impl BufWriter { if s.len() > self.capacity { // if the next part is more than buffer size, we drain the buffer and the next // part. - if !self.buf.is_empty() { - let mut buf = String::with_capacity(self.capacity); - std::mem::swap(&mut buf, &mut self.buf); - let _ = self.tx.unbounded_send(buf); - } - let _ = self.tx.unbounded_send(s.into_owned()); + match s { + // When the next part is borrowed, we push it onto the current buffer and send the + // buffer. + Cow::Borrowed(s) => { + let mut buf = String::with_capacity(self.capacity); + std::mem::swap(&mut buf, &mut self.buf); + + buf.push_str(s); + + let _ = self.tx.unbounded_send(buf); + } + + // When the next part is owned, we send both the buffer and the next part. + Cow::Owned(s) => { + if !self.buf.is_empty() { + let mut buf = String::with_capacity(self.capacity); + std::mem::swap(&mut buf, &mut self.buf); + let _ = self.tx.unbounded_send(buf); + } + + let _ = self.tx.unbounded_send(s); + } + } } else if self.buf.capacity() >= s.len() { // There is enough capacity, we push it on to the buffer. self.buf.push_str(&s); From 12c0e38a6bd3d04911fe6a577cb92b68681b4162 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 12 Jun 2022 17:49:36 +0900 Subject: [PATCH 21/50] Optimise Implementation. --- packages/yew/src/server_renderer.rs | 73 +++++++++++++++-------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/packages/yew/src/server_renderer.rs b/packages/yew/src/server_renderer.rs index 17d02fefca8..af747a5aace 100644 --- a/packages/yew/src/server_renderer.rs +++ b/packages/yew/src/server_renderer.rs @@ -16,6 +16,19 @@ pub(crate) struct BufWriter { capacity: usize, } +// Implementation Notes: +// +// When jemalloc is used, performance of the BufWriter is related to the number of allocations +// instead of the amount of memory that is allocated. +// +// A bytes::Bytes-based implementation is also tested, and yielded a similar performance. +// +// Having a String-based buffer avoids `unsafe { str::from_utf8_unchecked(..) }` or performance +// penalty by `str::from_utf8(..)` when converting back to String when text based content is needed +// (e.g.: post-processing). +// +// `Bytes::from` can be used to convert a `String` to `Bytes` if the web server asks for an +// `impl Stream`. This conversion incurs no memory allocation. impl BufWriter { pub fn with_capacity(capacity: usize) -> (Self, impl Stream) { let (tx, rx) = mpsc::unbounded::(); @@ -34,46 +47,34 @@ impl BufWriter { self.capacity } + fn drain(&mut self) { + let _ = self.tx.unbounded_send(self.buf.drain(..).collect()); + self.buf.reserve(self.capacity); + } + /// Writes a string into the buffer, optionally drains the buffer. pub fn write(&mut self, s: Cow<'_, str>) { - if s.len() > self.capacity { - // if the next part is more than buffer size, we drain the buffer and the next - // part. - - match s { - // When the next part is borrowed, we push it onto the current buffer and send the - // buffer. - Cow::Borrowed(s) => { - let mut buf = String::with_capacity(self.capacity); - std::mem::swap(&mut buf, &mut self.buf); - - buf.push_str(s); - - let _ = self.tx.unbounded_send(buf); - } - - // When the next part is owned, we send both the buffer and the next part. - Cow::Owned(s) => { - if !self.buf.is_empty() { - let mut buf = String::with_capacity(self.capacity); - std::mem::swap(&mut buf, &mut self.buf); - let _ = self.tx.unbounded_send(buf); - } - - let _ = self.tx.unbounded_send(s); - } - } - } else if self.buf.capacity() >= s.len() { - // There is enough capacity, we push it on to the buffer. + if self.buf.capacity() < s.len() { + // There isn't enough capacity, we drain the buffer. + self.drain(); + } + + // It's important to check self.buf.capacity() >= s.len(): + // + // 1. self.buf.reserve() may choose to over reserve than capacity. + // 2. When self.buf.capacity() == s.len(), the previous buffer is not drained. So it needs + // to push onto the buffer instead of sending. + if self.buf.capacity() >= s.len() { + // The next part is going to fit into the buffer, we push it onto the buffer. self.buf.push_str(&s); } else { - // The next part is not going to fit into the buffer, we send - // the current buffer and make a new buffer. - let mut buf = String::with_capacity(self.capacity); - buf.push_str(&s); + // if the next part is more than buffer size, we send the next part. - std::mem::swap(&mut buf, &mut self.buf); - let _ = self.tx.unbounded_send(buf); + // We don't need to drain the buffer here as the self.buf.capacity() only changes if + // the buffer was drained. If the buffer capacity didn't change, then it means + // self.buf.capacity() > s.len() which will be guaranteed to be matched by + // self.buf.capacity() >= s.len(). + let _ = self.tx.unbounded_send(s.into_owned()); } } } @@ -81,7 +82,7 @@ impl BufWriter { impl Drop for BufWriter { fn drop(&mut self) { if !self.buf.is_empty() { - let mut buf = "".to_string(); + let mut buf = String::new(); std::mem::swap(&mut buf, &mut self.buf); let _ = self.tx.unbounded_send(buf); } From 49bbd5593dc0577f2e68ffbca17404ad1ea30228 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 12 Jun 2022 18:19:46 +0900 Subject: [PATCH 22/50] Move BufWriter to a separate file. --- packages/yew/src/html/component/scope.rs | 6 +- packages/yew/src/io.rs | 89 +++++++++++++++++++++++ packages/yew/src/lib.rs | 3 + packages/yew/src/server_renderer.rs | 87 +--------------------- packages/yew/src/virtual_dom/mod.rs | 2 +- packages/yew/src/virtual_dom/vcomp.rs | 2 +- packages/yew/src/virtual_dom/vlist.rs | 2 +- packages/yew/src/virtual_dom/vnode.rs | 2 +- packages/yew/src/virtual_dom/vsuspense.rs | 2 +- packages/yew/src/virtual_dom/vtag.rs | 2 +- packages/yew/src/virtual_dom/vtext.rs | 2 +- 11 files changed, 105 insertions(+), 94 deletions(-) create mode 100644 packages/yew/src/io.rs diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index 0a26c1ac6ac..ff8c9d3aedb 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -266,8 +266,8 @@ mod feat_ssr { use crate::html::component::lifecycle::{ ComponentRenderState, CreateRunner, DestroyRunner, RenderRunner, }; + use crate::io::BufWriter; use crate::scheduler; - use crate::server_renderer::BufWriter; use crate::virtual_dom::Collectable; impl Scope { @@ -277,6 +277,10 @@ mod feat_ssr { props: Rc, hydratable: bool, ) { + // Rust's Future implementation is stack-allocated and incurs zero runtime-cost. + // + // If the content of this channel is ready before it is awaited, it is + // similar to taking the value from a mutex lock. let (tx, rx) = oneshot::channel(); let state = ComponentRenderState::Ssr { sender: Some(tx) }; diff --git a/packages/yew/src/io.rs b/packages/yew/src/io.rs new file mode 100644 index 00000000000..fe08d24e995 --- /dev/null +++ b/packages/yew/src/io.rs @@ -0,0 +1,89 @@ +//! This module contains types for I/O funtionality. + +use std::borrow::Cow; + +use futures::channel::mpsc::{self, UnboundedSender}; +use futures::stream::Stream; + +// Same as std::io::BufWriter and futures::io::BufWriter. +pub(crate) const DEFAULT_BUF_SIZE: usize = 8 * 1024; + +/// A [`futures::io::BufWriter`], but operates over string and yields into a Stream. +pub(crate) struct BufWriter { + buf: String, + tx: UnboundedSender, + capacity: usize, +} + +// Implementation Notes: +// +// When jemalloc is used, performance of the BufWriter is related to the number of allocations +// instead of the amount of memory that is allocated. +// +// A bytes::Bytes-based implementation is also tested, and yielded a similar performance. +// +// Having a String-based buffer avoids `unsafe { str::from_utf8_unchecked(..) }` or performance +// penalty by `str::from_utf8(..)` when converting back to String when text based content is needed +// (e.g.: post-processing). +// +// `Bytes::from` can be used to convert a `String` to `Bytes` if the web server asks for an +// `impl Stream`. This conversion incurs no memory allocation. +impl BufWriter { + pub fn with_capacity(capacity: usize) -> (Self, impl Stream) { + let (tx, rx) = mpsc::unbounded::(); + + let this = Self { + buf: String::with_capacity(capacity), + tx, + capacity, + }; + + (this, rx) + } + + #[inline] + pub fn capacity(&self) -> usize { + self.capacity + } + + fn drain(&mut self) { + let _ = self.tx.unbounded_send(self.buf.drain(..).collect()); + self.buf.reserve(self.capacity); + } + + /// Writes a string into the buffer, optionally drains the buffer. + pub fn write(&mut self, s: Cow<'_, str>) { + if self.buf.capacity() < s.len() { + // There isn't enough capacity, we drain the buffer. + self.drain(); + } + + // It's important to check self.buf.capacity() >= s.len(): + // + // 1. self.buf.reserve() may choose to over reserve than capacity. + // 2. When self.buf.capacity() == s.len(), the previous buffer is not drained. So it needs + // to push onto the buffer instead of sending. + if self.buf.capacity() >= s.len() { + // The next part is going to fit into the buffer, we push it onto the buffer. + self.buf.push_str(&s); + } else { + // if the next part is more than buffer size, we send the next part. + + // We don't need to drain the buffer here as the self.buf.capacity() only changes if + // the buffer was drained. If the buffer capacity didn't change, then it means + // self.buf.capacity() > s.len() which will be guaranteed to be matched by + // self.buf.capacity() >= s.len(). + let _ = self.tx.unbounded_send(s.into_owned()); + } + } +} + +impl Drop for BufWriter { + fn drop(&mut self) { + if !self.buf.is_empty() { + let mut buf = String::new(); + std::mem::swap(&mut buf, &mut self.buf); + let _ = self.tx.unbounded_send(buf); + } + } +} diff --git a/packages/yew/src/lib.rs b/packages/yew/src/lib.rs index 933efd28bee..69faa32f291 100644 --- a/packages/yew/src/lib.rs +++ b/packages/yew/src/lib.rs @@ -289,6 +289,9 @@ pub mod virtual_dom; #[cfg(feature = "ssr")] pub use server_renderer::*; +#[cfg(feature = "ssr")] +mod io; + #[cfg(feature = "csr")] mod app_handle; #[cfg(feature = "csr")] diff --git a/packages/yew/src/server_renderer.rs b/packages/yew/src/server_renderer.rs index af747a5aace..5d82dcc2020 100644 --- a/packages/yew/src/server_renderer.rs +++ b/packages/yew/src/server_renderer.rs @@ -1,94 +1,9 @@ -use std::borrow::Cow; - -use futures::channel::mpsc::{self, UnboundedSender}; use futures::stream::{Stream, StreamExt}; use crate::html::{BaseComponent, Scope}; +use crate::io::{BufWriter, DEFAULT_BUF_SIZE}; use crate::platform::{run_pinned, spawn_local}; -// Same as std::io::BufWriter and futures::io::BufWriter. -const DEFAULT_BUF_SIZE: usize = 8 * 1024; - -/// A [`futures::io::BufWriter`], but operates over string and yields into a Stream. -pub(crate) struct BufWriter { - buf: String, - tx: UnboundedSender, - capacity: usize, -} - -// Implementation Notes: -// -// When jemalloc is used, performance of the BufWriter is related to the number of allocations -// instead of the amount of memory that is allocated. -// -// A bytes::Bytes-based implementation is also tested, and yielded a similar performance. -// -// Having a String-based buffer avoids `unsafe { str::from_utf8_unchecked(..) }` or performance -// penalty by `str::from_utf8(..)` when converting back to String when text based content is needed -// (e.g.: post-processing). -// -// `Bytes::from` can be used to convert a `String` to `Bytes` if the web server asks for an -// `impl Stream`. This conversion incurs no memory allocation. -impl BufWriter { - pub fn with_capacity(capacity: usize) -> (Self, impl Stream) { - let (tx, rx) = mpsc::unbounded::(); - - let this = Self { - buf: String::with_capacity(capacity), - tx, - capacity, - }; - - (this, rx) - } - - #[inline] - pub fn capacity(&self) -> usize { - self.capacity - } - - fn drain(&mut self) { - let _ = self.tx.unbounded_send(self.buf.drain(..).collect()); - self.buf.reserve(self.capacity); - } - - /// Writes a string into the buffer, optionally drains the buffer. - pub fn write(&mut self, s: Cow<'_, str>) { - if self.buf.capacity() < s.len() { - // There isn't enough capacity, we drain the buffer. - self.drain(); - } - - // It's important to check self.buf.capacity() >= s.len(): - // - // 1. self.buf.reserve() may choose to over reserve than capacity. - // 2. When self.buf.capacity() == s.len(), the previous buffer is not drained. So it needs - // to push onto the buffer instead of sending. - if self.buf.capacity() >= s.len() { - // The next part is going to fit into the buffer, we push it onto the buffer. - self.buf.push_str(&s); - } else { - // if the next part is more than buffer size, we send the next part. - - // We don't need to drain the buffer here as the self.buf.capacity() only changes if - // the buffer was drained. If the buffer capacity didn't change, then it means - // self.buf.capacity() > s.len() which will be guaranteed to be matched by - // self.buf.capacity() >= s.len(). - let _ = self.tx.unbounded_send(s.into_owned()); - } - } -} - -impl Drop for BufWriter { - fn drop(&mut self) { - if !self.buf.is_empty() { - let mut buf = String::new(); - std::mem::swap(&mut buf, &mut self.buf); - let _ = self.tx.unbounded_send(buf); - } - } -} - /// A Yew Server-side Renderer that renders on the current thread. #[cfg_attr(documenting, doc(cfg(feature = "ssr")))] #[derive(Debug)] diff --git a/packages/yew/src/virtual_dom/mod.rs b/packages/yew/src/virtual_dom/mod.rs index 0eb099172ce..45a4484a449 100644 --- a/packages/yew/src/virtual_dom/mod.rs +++ b/packages/yew/src/virtual_dom/mod.rs @@ -266,7 +266,7 @@ pub(crate) use feat_ssr_hydration::*; #[cfg(feature = "ssr")] mod feat_ssr { use super::*; - use crate::server_renderer::BufWriter; + use crate::io::BufWriter; impl Collectable { pub(crate) fn write_open_tag(&self, w: &mut BufWriter) { diff --git a/packages/yew/src/virtual_dom/vcomp.rs b/packages/yew/src/virtual_dom/vcomp.rs index b8ee8df4da9..8fbb52c2c08 100644 --- a/packages/yew/src/virtual_dom/vcomp.rs +++ b/packages/yew/src/virtual_dom/vcomp.rs @@ -20,7 +20,7 @@ use crate::html::Scoped; use crate::html::{AnyScope, Scope}; use crate::html::{BaseComponent, NodeRef}; #[cfg(feature = "ssr")] -use crate::server_renderer::BufWriter; +use crate::io::BufWriter; /// A virtual component. pub struct VComp { diff --git a/packages/yew/src/virtual_dom/vlist.rs b/packages/yew/src/virtual_dom/vlist.rs index 254e01756b7..a4bb6da3d2c 100644 --- a/packages/yew/src/virtual_dom/vlist.rs +++ b/packages/yew/src/virtual_dom/vlist.rs @@ -160,7 +160,7 @@ mod feat_ssr { use super::*; use crate::html::AnyScope; - use crate::server_renderer::BufWriter; + use crate::io::BufWriter; impl VList { pub(crate) async fn render_into_stream( diff --git a/packages/yew/src/virtual_dom/vnode.rs b/packages/yew/src/virtual_dom/vnode.rs index 40d862f7fc4..34cf4adb6b2 100644 --- a/packages/yew/src/virtual_dom/vnode.rs +++ b/packages/yew/src/virtual_dom/vnode.rs @@ -151,7 +151,7 @@ mod feat_ssr { use super::*; use crate::html::AnyScope; - use crate::server_renderer::BufWriter; + use crate::io::BufWriter; impl VNode { pub(crate) fn render_into_stream<'a>( diff --git a/packages/yew/src/virtual_dom/vsuspense.rs b/packages/yew/src/virtual_dom/vsuspense.rs index 1c9479e492f..5a40b174b0f 100644 --- a/packages/yew/src/virtual_dom/vsuspense.rs +++ b/packages/yew/src/virtual_dom/vsuspense.rs @@ -28,7 +28,7 @@ impl VSuspense { mod feat_ssr { use super::*; use crate::html::AnyScope; - use crate::server_renderer::BufWriter; + use crate::io::BufWriter; use crate::virtual_dom::Collectable; impl VSuspense { diff --git a/packages/yew/src/virtual_dom/vtag.rs b/packages/yew/src/virtual_dom/vtag.rs index ba718925d42..4483412be96 100644 --- a/packages/yew/src/virtual_dom/vtag.rs +++ b/packages/yew/src/virtual_dom/vtag.rs @@ -430,7 +430,7 @@ impl PartialEq for VTag { mod feat_ssr { use super::*; use crate::html::AnyScope; - use crate::server_renderer::BufWriter; + use crate::io::BufWriter; use crate::virtual_dom::VText; // Elements that cannot have any child elements. diff --git a/packages/yew/src/virtual_dom/vtext.rs b/packages/yew/src/virtual_dom/vtext.rs index 0db82384a00..c85fd77a509 100644 --- a/packages/yew/src/virtual_dom/vtext.rs +++ b/packages/yew/src/virtual_dom/vtext.rs @@ -37,7 +37,7 @@ mod feat_ssr { use super::*; use crate::html::AnyScope; - use crate::server_renderer::BufWriter; + use crate::io::BufWriter; impl VText { pub(crate) async fn render_into_stream( From c878adb7fc336b27942bfea2324bd84b4a671fa5 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 12 Jun 2022 18:41:58 +0900 Subject: [PATCH 23/50] Additional Implementation Note. --- packages/yew/src/io.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/yew/src/io.rs b/packages/yew/src/io.rs index fe08d24e995..862ca4c99f0 100644 --- a/packages/yew/src/io.rs +++ b/packages/yew/src/io.rs @@ -28,6 +28,13 @@ pub(crate) struct BufWriter { // // `Bytes::from` can be used to convert a `String` to `Bytes` if the web server asks for an // `impl Stream`. This conversion incurs no memory allocation. +// +// Yielding the output with a Stream provides a couple advantages: +// +// 1. All children of a VList can be rendered concurrently. +// 2. If a fixed buffer is used, the rendering process can become blocked if the buffer is filled +// up. Using a stream avoids this side effect and allows the renderer to finish rendering without +// being pulled. impl BufWriter { pub fn with_capacity(capacity: usize) -> (Self, impl Stream) { let (tx, rx) = mpsc::unbounded::(); From 88085ac073a02d0c62cccbdac5cbb2bbcaa92f94 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sat, 25 Jun 2022 02:32:35 +0900 Subject: [PATCH 24/50] Adjust API so it matches `std::channel::mpsc::channel`. --- packages/yew/src/io.rs | 46 ++++++++++++++------------- packages/yew/src/server_renderer.rs | 6 ++-- packages/yew/src/virtual_dom/mod.rs | 4 ++- packages/yew/src/virtual_dom/vlist.rs | 10 +++--- 4 files changed, 35 insertions(+), 31 deletions(-) diff --git a/packages/yew/src/io.rs b/packages/yew/src/io.rs index 862ca4c99f0..23f1725ab04 100644 --- a/packages/yew/src/io.rs +++ b/packages/yew/src/io.rs @@ -15,39 +15,41 @@ pub(crate) struct BufWriter { capacity: usize, } +/// Creates a Buffer pair. +pub(crate) fn buffer(capacity: usize) -> (BufWriter, impl Stream) { + let (tx, rx) = mpsc::unbounded::(); + + let tx = BufWriter { + buf: String::with_capacity(capacity), + tx, + capacity, + }; + + (tx, rx) +} + // Implementation Notes: // -// When jemalloc is used, performance of the BufWriter is related to the number of allocations +// When jemalloc is used and a reasonable buffer is chosen, +// performance of this buffer is related to the number of allocations // instead of the amount of memory that is allocated. // -// A bytes::Bytes-based implementation is also tested, and yielded a similar performance. +// A Bytes-based implementation is also tested, and yielded a similar performance to String-based +// buffer. // -// Having a String-based buffer avoids `unsafe { str::from_utf8_unchecked(..) }` or performance -// penalty by `str::from_utf8(..)` when converting back to String when text based content is needed -// (e.g.: post-processing). +// Having a String-based buffer avoids unsafe / cost of conversion between String and Bytes +// when text based content is needed (e.g.: post-processing). // -// `Bytes::from` can be used to convert a `String` to `Bytes` if the web server asks for an +// `Bytes::from` can be used to convert a `String` to `Bytes` if web server asks for an // `impl Stream`. This conversion incurs no memory allocation. // // Yielding the output with a Stream provides a couple advantages: // -// 1. All children of a VList can be rendered concurrently. -// 2. If a fixed buffer is used, the rendering process can become blocked if the buffer is filled -// up. Using a stream avoids this side effect and allows the renderer to finish rendering without -// being pulled. +// 1. All child components of a VList can have their own buffer and be rendered concurrently. +// 2. If a fixed buffer is used, the rendering process can become blocked if the buffer is filled. +// Using a stream avoids this side effect and allows the renderer to finish rendering +// without being actively polled. impl BufWriter { - pub fn with_capacity(capacity: usize) -> (Self, impl Stream) { - let (tx, rx) = mpsc::unbounded::(); - - let this = Self { - buf: String::with_capacity(capacity), - tx, - capacity, - }; - - (this, rx) - } - #[inline] pub fn capacity(&self) -> usize { self.capacity diff --git a/packages/yew/src/server_renderer.rs b/packages/yew/src/server_renderer.rs index 5d82dcc2020..3a422352c68 100644 --- a/packages/yew/src/server_renderer.rs +++ b/packages/yew/src/server_renderer.rs @@ -1,7 +1,7 @@ use futures::stream::{Stream, StreamExt}; use crate::html::{BaseComponent, Scope}; -use crate::io::{BufWriter, DEFAULT_BUF_SIZE}; +use crate::io::{self, DEFAULT_BUF_SIZE}; use crate::platform::{run_pinned, spawn_local}; /// A Yew Server-side Renderer that renders on the current thread. @@ -93,7 +93,7 @@ where // Whilst not required to be async here, this function is async to keep the same function // signature as the ServerRenderer. pub async fn render_stream(self) -> impl Stream { - let (mut w, rx) = BufWriter::with_capacity(self.capacity); + let (mut w, r) = io::buffer(self.capacity); let scope = Scope::::new(None); spawn_local(async move { @@ -102,7 +102,7 @@ where .await; }); - rx + r } } diff --git a/packages/yew/src/virtual_dom/mod.rs b/packages/yew/src/virtual_dom/mod.rs index 7782d9daeef..1a1bdbe3649 100644 --- a/packages/yew/src/virtual_dom/mod.rs +++ b/packages/yew/src/virtual_dom/mod.rs @@ -52,6 +52,8 @@ mod feat_ssr_hydration { #[cfg(not(debug_assertions))] type ComponentName = (); + use std::borrow::Cow; + /// A collectable. /// /// This indicates a kind that can be collected from fragment to be processed at a later time @@ -91,7 +93,7 @@ mod feat_ssr_hydration { } #[cfg(feature = "hydration")] - pub fn name(&self) -> super::Cow<'static, str> { + pub fn name(&self) -> Cow<'static, str> { match self { #[cfg(debug_assertions)] Self::Component(m) => format!("Component({})", m).into(), diff --git a/packages/yew/src/virtual_dom/vlist.rs b/packages/yew/src/virtual_dom/vlist.rs index a4bb6da3d2c..19bb9b139c2 100644 --- a/packages/yew/src/virtual_dom/vlist.rs +++ b/packages/yew/src/virtual_dom/vlist.rs @@ -160,7 +160,7 @@ mod feat_ssr { use super::*; use crate::html::AnyScope; - use crate::io::BufWriter; + use crate::io::{self, BufWriter}; impl VList { pub(crate) async fn render_into_stream( @@ -188,17 +188,17 @@ mod feat_ssr { .children .iter() .map(|m| async move { - let (mut w, rx) = BufWriter::with_capacity(buf_capacity); + let (mut w, r) = io::buffer(buf_capacity); m.render_into_stream(&mut w, parent_scope, hydratable).await; drop(w); - rx + r }) .collect(); - while let Some(mut rx) = children.next().await { - while let Some(next_chunk) = rx.next().await { + while let Some(mut r) = children.next().await { + while let Some(next_chunk) = r.next().await { w.write(next_chunk.into()); } } From 08e3bade771d7a6cf440e033563b9aa4b9bd0a6d Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sat, 25 Jun 2022 02:35:50 +0900 Subject: [PATCH 25/50] Fix feature soundness. --- packages/yew/src/virtual_dom/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/yew/src/virtual_dom/mod.rs b/packages/yew/src/virtual_dom/mod.rs index 1a1bdbe3649..8b2cd3f8a83 100644 --- a/packages/yew/src/virtual_dom/mod.rs +++ b/packages/yew/src/virtual_dom/mod.rs @@ -52,6 +52,7 @@ mod feat_ssr_hydration { #[cfg(not(debug_assertions))] type ComponentName = (); + #[cfg(feature = "hydration")] use std::borrow::Cow; /// A collectable. From 05d97da28f1e07d63da2199f6d7798f48832a4dc Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sat, 25 Jun 2022 22:16:13 +0900 Subject: [PATCH 26/50] Make a compatibility layer on channels. --- packages/yew/Cargo.toml | 6 +- packages/yew/Makefile.toml | 2 + packages/yew/src/html/component/scope.rs | 3 +- packages/yew/src/io.rs | 9 +- packages/yew/src/platform.rs | 106 ------------------ packages/yew/src/platform/mod.rs | 45 ++++++++ packages/yew/src/platform/rt_null/mod.rs | 29 +++++ packages/yew/src/platform/rt_null/sync/mod.rs | 1 + .../yew/src/platform/rt_null/sync/mpsc/mod.rs | 48 ++++++++ packages/yew/src/platform/rt_tokio/mod.rs | 34 ++++++ .../yew/src/platform/rt_tokio/sync/mod.rs | 1 + .../src/platform/rt_tokio/sync/mpsc/mod.rs | 59 ++++++++++ .../yew/src/platform/rt_wasm_bindgen/mod.rs | 15 +++ .../src/platform/rt_wasm_bindgen/sync/mod.rs | 1 + .../platform/rt_wasm_bindgen/sync/mpsc/mod.rs | 61 ++++++++++ packages/yew/src/platform/sync/mod.rs | 4 + packages/yew/src/platform/sync/mpsc/mod.rs | 78 +++++++++++++ packages/yew/src/server_renderer.rs | 12 +- packages/yew/src/virtual_dom/vlist.rs | 56 +++++---- 19 files changed, 421 insertions(+), 149 deletions(-) delete mode 100644 packages/yew/src/platform.rs create mode 100644 packages/yew/src/platform/mod.rs create mode 100644 packages/yew/src/platform/rt_null/mod.rs create mode 100644 packages/yew/src/platform/rt_null/sync/mod.rs create mode 100644 packages/yew/src/platform/rt_null/sync/mpsc/mod.rs create mode 100644 packages/yew/src/platform/rt_tokio/mod.rs create mode 100644 packages/yew/src/platform/rt_tokio/sync/mod.rs create mode 100644 packages/yew/src/platform/rt_tokio/sync/mpsc/mod.rs create mode 100644 packages/yew/src/platform/rt_wasm_bindgen/mod.rs create mode 100644 packages/yew/src/platform/rt_wasm_bindgen/sync/mod.rs create mode 100644 packages/yew/src/platform/rt_wasm_bindgen/sync/mpsc/mod.rs create mode 100644 packages/yew/src/platform/sync/mod.rs create mode 100644 packages/yew/src/platform/sync/mpsc/mod.rs diff --git a/packages/yew/Cargo.toml b/packages/yew/Cargo.toml index d69f6db81c9..e54edd3abcb 100644 --- a/packages/yew/Cargo.toml +++ b/packages/yew/Cargo.toml @@ -33,6 +33,7 @@ implicit-clone = { version = "0.2", features = ["map"] } base64ct = { version = "1.5.0", features = ["std"], optional = true } bincode = { version = "1.3.3", optional = true } serde = { version = "1", features = ["derive"] } +pin-project = { version = "1.0.10", optional = true } [dependencies.web-sys] version = "0.3" @@ -78,6 +79,7 @@ tokio = { version = "1.15.0", features = ["rt"], optional = true } num_cpus = { version = "1.13", optional = true } tokio-util = { version = "0.7", features = ["rt"], optional = true } once_cell = "1" +tokio-stream = { version = "0.1.9", optional = true } [dev-dependencies] wasm-bindgen-test = "0.3" @@ -95,8 +97,8 @@ features = [ ] [features] -tokio = ["dep:tokio", "dep:num_cpus", "dep:tokio-util"] -ssr = ["dep:futures", "dep:html-escape", "dep:base64ct", "dep:bincode"] +tokio = ["dep:tokio", "dep:num_cpus", "dep:tokio-util", "dep:tokio-stream"] +ssr = ["dep:futures", "dep:html-escape", "dep:base64ct", "dep:bincode", "dep:pin-project"] csr = [] hydration = ["csr", "dep:bincode"] nightly = ["yew-macro/nightly"] diff --git a/packages/yew/Makefile.toml b/packages/yew/Makefile.toml index d2126987048..9948520c47c 100644 --- a/packages/yew/Makefile.toml +++ b/packages/yew/Makefile.toml @@ -27,12 +27,14 @@ script = ''' set -ex cargo clippy -- --deny=warnings cargo clippy --features=ssr -- --deny=warnings +cargo clippy --features=ssr,tokio -- --deny=warnings cargo clippy --features=csr -- --deny=warnings cargo clippy --features=hydration -- --deny=warnings cargo clippy --all-features --all-targets -- --deny=warnings cargo clippy --release -- --deny=warnings cargo clippy --release --features=ssr -- --deny=warnings +cargo clippy --release --features=ssr,tokio -- --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 diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index 72ad0b8703e..7fd6a8789e5 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -260,13 +260,12 @@ impl Scope { #[cfg(feature = "ssr")] mod feat_ssr { - use futures::channel::oneshot; - use super::*; use crate::html::component::lifecycle::{ ComponentRenderState, CreateRunner, DestroyRunner, RenderRunner, }; use crate::io::BufWriter; + use crate::platform::sync::oneshot; use crate::scheduler; use crate::virtual_dom::Collectable; diff --git a/packages/yew/src/io.rs b/packages/yew/src/io.rs index 23f1725ab04..62b49d07561 100644 --- a/packages/yew/src/io.rs +++ b/packages/yew/src/io.rs @@ -2,9 +2,10 @@ use std::borrow::Cow; -use futures::channel::mpsc::{self, UnboundedSender}; use futures::stream::Stream; +use crate::platform::sync::mpsc::{self, UnboundedSender}; + // Same as std::io::BufWriter and futures::io::BufWriter. pub(crate) const DEFAULT_BUF_SIZE: usize = 8 * 1024; @@ -56,7 +57,7 @@ impl BufWriter { } fn drain(&mut self) { - let _ = self.tx.unbounded_send(self.buf.drain(..).collect()); + let _ = self.tx.send(self.buf.drain(..).collect()); self.buf.reserve(self.capacity); } @@ -82,7 +83,7 @@ impl BufWriter { // the buffer was drained. If the buffer capacity didn't change, then it means // self.buf.capacity() > s.len() which will be guaranteed to be matched by // self.buf.capacity() >= s.len(). - let _ = self.tx.unbounded_send(s.into_owned()); + let _ = self.tx.send(s.into_owned()); } } } @@ -92,7 +93,7 @@ impl Drop for BufWriter { if !self.buf.is_empty() { let mut buf = String::new(); std::mem::swap(&mut buf, &mut self.buf); - let _ = self.tx.unbounded_send(buf); + let _ = self.tx.send(buf); } } } diff --git a/packages/yew/src/platform.rs b/packages/yew/src/platform.rs deleted file mode 100644 index 9a4bbfddc66..00000000000 --- a/packages/yew/src/platform.rs +++ /dev/null @@ -1,106 +0,0 @@ -//! This module provides io compatibility over browser tasks and other asyncio runtimes (e.g.: -//! tokio). - -use std::future::Future; - -#[cfg(target_arch = "wasm32")] -mod arch { - pub(super) use wasm_bindgen_futures::spawn_local; - - use super::*; - - pub(super) async fn run_pinned(create_task: F) -> Fut::Output - where - F: FnOnce() -> Fut, - F: Send + 'static, - Fut: Future + 'static, - Fut::Output: Send + 'static, - { - create_task().await - } -} - -#[cfg(not(target_arch = "wasm32"))] -mod arch { - use super::*; - - #[cfg(feature = "tokio")] - pub(super) async fn run_pinned(create_task: F) -> Fut::Output - where - F: FnOnce() -> Fut, - F: Send + 'static, - Fut: Future + 'static, - Fut::Output: Send + 'static, - { - use once_cell::sync::Lazy; - use tokio_util::task::LocalPoolHandle; - - static POOL_HANDLE: Lazy = - Lazy::new(|| LocalPoolHandle::new(num_cpus::get() * 2)); - - POOL_HANDLE - .spawn_pinned(create_task) - .await - .expect("future has panicked!") - } - - #[inline(always)] - pub(super) fn spawn_local(f: F) - where - F: Future + 'static, - { - #[cfg(feature = "tokio")] - ::tokio::task::spawn_local(f); - #[cfg(not(feature = "tokio"))] - { - let _ = f; - panic!( - r#"No runtime configured for this platform, features that requires task spawning can't be used. - Either compile with `target_arch = "wasm32", or enable the `tokio` feature."# - ); - } - } - - #[cfg(not(feature = "tokio"))] - pub(crate) async fn run_pinned(create_task: F) -> Fut::Output - where - F: FnOnce() -> Fut, - F: Send + 'static, - Fut: Future + 'static, - Fut::Output: Send + 'static, - { - let _ = create_task; - - panic!( - r#"No runtime configured for this platform, features that requires task spawning can't be used. - Either compile with `target_arch = "wasm32", or enable the `tokio` feature."# - ) - } -} - -/// Spawns a task on current thread. -#[inline(always)] -pub fn spawn_local(f: F) -where - F: Future + 'static, -{ - arch::spawn_local(f); -} - -/// Runs a task with it pinned onto a local worker thread. -/// -/// This can be used to execute non-Send futures without blocking the current thread. -/// -/// It maintains an internal thread pool dedicated to executing local futures. -/// -/// [`spawn_local`] is available with tasks executed with `run_pinned`. -#[inline(always)] -pub async fn run_pinned(create_task: F) -> Fut::Output -where - F: FnOnce() -> Fut, - F: Send + 'static, - Fut: Future + 'static, - Fut::Output: Send + 'static, -{ - arch::run_pinned(create_task).await -} diff --git a/packages/yew/src/platform/mod.rs b/packages/yew/src/platform/mod.rs new file mode 100644 index 00000000000..6fd3d087e75 --- /dev/null +++ b/packages/yew/src/platform/mod.rs @@ -0,0 +1,45 @@ +//! This module provides io compatibility over browser tasks and other asyncio runtimes (e.g.: +//! tokio). + +use std::future::Future; + +#[cfg(feature = "ssr")] +pub(crate) mod sync; + +#[cfg(not(any(feature = "tokio", target_arch = "wasm32")))] +#[path = "rt_null/mod.rs"] +mod imp; +#[cfg(all(not(target_arch = "wasm32"), feature = "tokio"))] +#[path = "rt_tokio/mod.rs"] +mod imp; +#[cfg(target_arch = "wasm32")] +#[path = "rt_wasm_bindgen/mod.rs"] +mod imp; + +/// Spawns a task on current thread. +#[inline(always)] +pub fn spawn_local(f: F) +where + F: Future + 'static, +{ + imp::spawn_local(f); +} + +/// Runs a task with it pinned onto a local worker thread. +/// +/// This can be used to execute non-Send futures without blocking the current thread. +/// +/// It maintains an internal thread pool dedicated to executing local futures. +/// +/// [`spawn_local`] is available with tasks executed with `run_pinned`. +#[inline(always)] +#[cfg(feature = "ssr")] +pub(crate) async fn run_pinned(create_task: F) -> Fut::Output +where + F: FnOnce() -> Fut, + F: Send + 'static, + Fut: Future + 'static, + Fut::Output: Send + 'static, +{ + imp::run_pinned(create_task).await +} diff --git a/packages/yew/src/platform/rt_null/mod.rs b/packages/yew/src/platform/rt_null/mod.rs new file mode 100644 index 00000000000..f8ecb83bfde --- /dev/null +++ b/packages/yew/src/platform/rt_null/mod.rs @@ -0,0 +1,29 @@ +use std::future::Future; + +#[cfg(feature = "ssr")] +pub(crate) mod sync; + +#[inline(always)] +pub(super) fn spawn_local(_f: F) +where + F: Future + 'static, +{ + panic!( + r#"No runtime configured for this platform, features that requires task spawning can't be used. + Either compile with `target_arch = "wasm32", or enable the `tokio` feature."# + ); +} + +#[cfg(feature = "ssr")] +pub(crate) async fn run_pinned(_create_task: F) -> Fut::Output +where + F: FnOnce() -> Fut, + F: Send + 'static, + Fut: Future + 'static, + Fut::Output: Send + 'static, +{ + panic!( + r#"No runtime configured for this platform, features that requires task spawning can't be used. + Either compile with `target_arch = "wasm32", or enable the `tokio` feature."# + ) +} diff --git a/packages/yew/src/platform/rt_null/sync/mod.rs b/packages/yew/src/platform/rt_null/sync/mod.rs new file mode 100644 index 00000000000..44f9c4390e1 --- /dev/null +++ b/packages/yew/src/platform/rt_null/sync/mod.rs @@ -0,0 +1 @@ +pub(crate) mod mpsc; diff --git a/packages/yew/src/platform/rt_null/sync/mpsc/mod.rs b/packages/yew/src/platform/rt_null/sync/mpsc/mod.rs new file mode 100644 index 00000000000..86c45c89899 --- /dev/null +++ b/packages/yew/src/platform/rt_null/sync/mpsc/mod.rs @@ -0,0 +1,48 @@ +use std::marker::PhantomData; +use std::pin::Pin; +use std::task::{Context, Poll}; + +use futures::stream::Stream; + +use crate::platform::sync::mpsc::SendError; + +#[derive(Debug)] +pub(crate) struct UnboundedSender { + inner: PhantomData, +} + +#[derive(Debug)] +pub(crate) struct UnboundedReceiver { + inner: PhantomData, +} + +impl Clone for UnboundedSender { + fn clone(&self) -> Self { + Self { inner: PhantomData } + } +} + +pub(crate) fn unbounded() -> (UnboundedSender, UnboundedReceiver) { + panic!( + r#"No runtime configured for this platform, features that requires task spawning can't be used. + Either compile with `target_arch = "wasm32", or enable the `tokio` feature."# + ); +} + +impl UnboundedSender { + pub fn send(&self, _value: T) -> Result<(), SendError> { + unimplemented!(); + } +} + +impl Stream for UnboundedReceiver { + type Item = T; + + fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + unimplemented!(); + } + + fn size_hint(&self) -> (usize, Option) { + unimplemented!(); + } +} diff --git a/packages/yew/src/platform/rt_tokio/mod.rs b/packages/yew/src/platform/rt_tokio/mod.rs new file mode 100644 index 00000000000..a6a6f3bab21 --- /dev/null +++ b/packages/yew/src/platform/rt_tokio/mod.rs @@ -0,0 +1,34 @@ +//! Tokio Implementation + +use std::future::Future; + +#[cfg(feature = "ssr")] +pub(crate) mod sync; + +#[cfg(feature = "ssr")] +pub(super) async fn run_pinned(create_task: F) -> Fut::Output +where + F: FnOnce() -> Fut, + F: Send + 'static, + Fut: Future + 'static, + Fut::Output: Send + 'static, +{ + use once_cell::sync::Lazy; + use tokio_util::task::LocalPoolHandle; + + static POOL_HANDLE: Lazy = + Lazy::new(|| LocalPoolHandle::new(num_cpus::get() * 2)); + + POOL_HANDLE + .spawn_pinned(create_task) + .await + .expect("future has panicked!") +} + +#[inline(always)] +pub(super) fn spawn_local(f: F) +where + F: Future + 'static, +{ + tokio::task::spawn_local(f); +} diff --git a/packages/yew/src/platform/rt_tokio/sync/mod.rs b/packages/yew/src/platform/rt_tokio/sync/mod.rs new file mode 100644 index 00000000000..44f9c4390e1 --- /dev/null +++ b/packages/yew/src/platform/rt_tokio/sync/mod.rs @@ -0,0 +1 @@ +pub(crate) mod mpsc; diff --git a/packages/yew/src/platform/rt_tokio/sync/mpsc/mod.rs b/packages/yew/src/platform/rt_tokio/sync/mpsc/mod.rs new file mode 100644 index 00000000000..b1e21e49aaf --- /dev/null +++ b/packages/yew/src/platform/rt_tokio/sync/mpsc/mod.rs @@ -0,0 +1,59 @@ +use std::pin::Pin; +use std::task::{Context, Poll}; + +use futures::stream::Stream; +use pin_project::pin_project; +use tokio::sync::mpsc as imp; +use tokio_stream::wrappers::UnboundedReceiverStream; + +use crate::platform::sync::mpsc::SendError; + +#[derive(Debug)] +pub(crate) struct UnboundedSender { + inner: imp::UnboundedSender, +} + +#[derive(Debug)] +#[pin_project] +pub(crate) struct UnboundedReceiver { + #[pin] + inner: UnboundedReceiverStream, +} + +impl Clone for UnboundedSender { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + } + } +} + +pub(crate) fn unbounded() -> (UnboundedSender, UnboundedReceiver) { + let (inner_tx, inner_rx) = imp::unbounded_channel(); + + let tx = UnboundedSender { inner: inner_tx }; + let rx = UnboundedReceiver { + inner: UnboundedReceiverStream::new(inner_rx), + }; + + (tx, rx) +} + +impl UnboundedSender { + pub fn send(&self, value: T) -> Result<(), SendError> { + self.inner.send(value).map_err(|e| SendError(e.0)) + } +} + +impl Stream for UnboundedReceiver { + type Item = T; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.project(); + this.inner.poll_next(cx) + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} diff --git a/packages/yew/src/platform/rt_wasm_bindgen/mod.rs b/packages/yew/src/platform/rt_wasm_bindgen/mod.rs new file mode 100644 index 00000000000..65f1582afb9 --- /dev/null +++ b/packages/yew/src/platform/rt_wasm_bindgen/mod.rs @@ -0,0 +1,15 @@ +#[cfg(feature = "ssr")] +pub(crate) mod sync; + +pub(super) use wasm_bindgen_futures::spawn_local; + +#[cfg(feature = "ssr")] +pub(crate) async fn run_pinned(create_task: F) -> Fut::Output +where + F: FnOnce() -> Fut, + F: Send + 'static, + Fut: Future + 'static, + Fut::Output: Send + 'static, +{ + create_task().await +} diff --git a/packages/yew/src/platform/rt_wasm_bindgen/sync/mod.rs b/packages/yew/src/platform/rt_wasm_bindgen/sync/mod.rs new file mode 100644 index 00000000000..44f9c4390e1 --- /dev/null +++ b/packages/yew/src/platform/rt_wasm_bindgen/sync/mod.rs @@ -0,0 +1 @@ +pub(crate) mod mpsc; diff --git a/packages/yew/src/platform/rt_wasm_bindgen/sync/mpsc/mod.rs b/packages/yew/src/platform/rt_wasm_bindgen/sync/mpsc/mod.rs new file mode 100644 index 00000000000..f2447763305 --- /dev/null +++ b/packages/yew/src/platform/rt_wasm_bindgen/sync/mpsc/mod.rs @@ -0,0 +1,61 @@ +use std::marker::PhantomData; +use std::pin::Pin; +use std::task::{Context, Poll}; + +use futures::channel::mpsc as imp; +use futures::stream::Stream; +use pin_project::pin_project; + +use crate::platform::sync::mpsc::SendError; + +#[derive(Debug)] +pub(crate) struct UnboundedSender { + inner: imp::UnboundedSender, +} + +#[derive(Debug)] +#[pin_project] +pub(crate) struct UnboundedReceiver { + #[pin] + inner: imp::UnboundedReceiver, +} + +impl Clone for UnboundedSender { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + } + } +} + +pub(crate) fn unbounded() -> (UnboundedSender, UnboundedReceiver) { + let (inner_tx, inner_rx) = imp::unbounded_channel(); + + let tx = UnboundedSender { inner: inner_tx }; + let rx = UnboundedReceiver { + inner: UnboundedReceiverStream::new(inner_rx), + }; + + (tx, rx) +} + +impl UnboundedSender { + pub fn send(&self, value: T) -> Result<(), SendError> { + self.inner + .unbounded_send(value) + .map_err(|e| SendError(e.into_inner())) + } +} + +impl Stream for UnboundedReceiver { + type Item = T; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.project(); + this.inner.poll_next(cx) + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} diff --git a/packages/yew/src/platform/sync/mod.rs b/packages/yew/src/platform/sync/mod.rs new file mode 100644 index 00000000000..33a48c3ebde --- /dev/null +++ b/packages/yew/src/platform/sync/mod.rs @@ -0,0 +1,4 @@ +//! A module that provides task synchronization primitives. + +pub mod mpsc; +pub use futures::channel::oneshot; diff --git a/packages/yew/src/platform/sync/mpsc/mod.rs b/packages/yew/src/platform/sync/mpsc/mod.rs new file mode 100644 index 00000000000..96460e06f68 --- /dev/null +++ b/packages/yew/src/platform/sync/mpsc/mod.rs @@ -0,0 +1,78 @@ +//! A multi-producer, single-receiver channel; + +use std::error::Error; +use std::fmt; +use std::pin::Pin; +use std::task::{Context, Poll}; + +use futures::stream::Stream; +use pin_project::pin_project; + +use crate::platform::imp::sync::mpsc as imp; + +/// The channel has closed when attempting sending. +#[derive(Debug)] +pub struct SendError(pub T); + +impl fmt::Display for SendError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(fmt, "channel closed") + } +} + +impl Error for SendError {} + +/// An unbounded sender for a multi-producer, single receiver channel. +#[derive(Debug)] +pub struct UnboundedSender { + inner: imp::UnboundedSender, +} + +/// An unbounded receiver for a multi-producer, single receiver channel. +#[derive(Debug)] +#[pin_project] +pub struct UnboundedReceiver { + #[pin] + inner: imp::UnboundedReceiver, +} + +impl Clone for UnboundedSender { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + } + } +} + +/// Creates an unbounded channel. +pub fn unbounded() -> (UnboundedSender, UnboundedReceiver) { + let (inner_tx, inner_rx) = imp::unbounded(); + + let tx = UnboundedSender { inner: inner_tx }; + let rx = UnboundedReceiver { inner: inner_rx }; + + (tx, rx) +} + +impl UnboundedSender { + /// Send the value to the receiver. + #[inline] + pub fn send(&self, value: T) -> Result<(), SendError> { + self.inner.send(value) + } +} + +impl Stream for UnboundedReceiver { + type Item = T; + + #[inline] + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.project(); + this.inner.poll_next(cx) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} diff --git a/packages/yew/src/server_renderer.rs b/packages/yew/src/server_renderer.rs index 3a422352c68..78075fbbc92 100644 --- a/packages/yew/src/server_renderer.rs +++ b/packages/yew/src/server_renderer.rs @@ -82,7 +82,7 @@ where /// Renders Yew Application to a String. pub async fn render_to_string(self, w: &mut String) { - let mut s = self.render_stream().await; + let mut s = self.render_stream(); while let Some(m) = s.next().await { w.push_str(&m); @@ -90,9 +90,7 @@ where } /// Renders Yew Applications into a string Stream - // Whilst not required to be async here, this function is async to keep the same function - // signature as the ServerRenderer. - pub async fn render_stream(self) -> impl Stream { + pub fn render_stream(self) -> impl Stream { let (mut w, r) = io::buffer(self.capacity); let scope = Scope::::new(None); @@ -197,7 +195,12 @@ where } /// Renders Yew Applications into a string Stream. + /// + /// # Note + /// + /// Unlike [`LocalServerRenderer::render_stream`], this method is `async fn`. pub async fn render_stream(self) -> impl Stream { + // We use run_pinned to switch to our runtime. run_pinned(move || async move { let Self { props, @@ -209,7 +212,6 @@ where .hydratable(hydratable) .capacity(capacity) .render_stream() - .await }) .await } diff --git a/packages/yew/src/virtual_dom/vlist.rs b/packages/yew/src/virtual_dom/vlist.rs index 19bb9b139c2..11d56c007ed 100644 --- a/packages/yew/src/virtual_dom/vlist.rs +++ b/packages/yew/src/virtual_dom/vlist.rs @@ -169,37 +169,33 @@ mod feat_ssr { parent_scope: &AnyScope, hydratable: bool, ) { - if self.children.len() < 2 { - match self.children.first() { - Some(m) => { - m.render_into_stream(w, parent_scope, hydratable).await; - } - - None => {} + match &self.children[..] { + [] => {} + [child] => { + child.render_into_stream(w, parent_scope, hydratable).await; } - - return; - } - - let buf_capacity = w.capacity(); - - // Concurrently render all children. - let mut children: FuturesOrdered<_> = self - .children - .iter() - .map(|m| async move { - let (mut w, r) = io::buffer(buf_capacity); - - m.render_into_stream(&mut w, parent_scope, hydratable).await; - drop(w); - - r - }) - .collect(); - - while let Some(mut r) = children.next().await { - while let Some(next_chunk) = r.next().await { - w.write(next_chunk.into()); + _ => { + let buf_capacity = w.capacity(); + + // Concurrently render all children. + let mut children: FuturesOrdered<_> = self + .children + .iter() + .map(|m| async move { + let (mut w, r) = io::buffer(buf_capacity); + + m.render_into_stream(&mut w, parent_scope, hydratable).await; + drop(w); + + r + }) + .collect(); + + while let Some(mut r) = children.next().await { + while let Some(next_chunk) = r.next().await { + w.write(next_chunk.into()); + } + } } } } From 6904da49cf7fa50169f7b4753205462115bc3271 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sat, 25 Jun 2022 22:29:39 +0900 Subject: [PATCH 27/50] Fix clippy. --- .github/workflows/main-checks.yml | 16 ++++++++-------- packages/yew/src/platform/rt_wasm_bindgen/mod.rs | 2 ++ .../platform/rt_wasm_bindgen/sync/mpsc/mod.rs | 7 ++----- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/.github/workflows/main-checks.yml b/.github/workflows/main-checks.yml index 46e8059b10e..44c951bdd18 100644 --- a/.github/workflows/main-checks.yml +++ b/.github/workflows/main-checks.yml @@ -29,10 +29,10 @@ jobs: - name: Lint feature soundness run: | - 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-targets -- --deny=warnings + cargo clippy --features=ssr --all-targets -- --deny=warnings + cargo clippy --features=csr --all-targets -- --deny=warnings + cargo clippy --features=hydration --all-targets -- --deny=warnings cargo clippy --features "csr,ssr,hydration,tokio" --all-targets -- --deny=warnings working-directory: packages/yew @@ -59,10 +59,10 @@ jobs: - name: Lint feature soundness run: | - 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-targets-- --all-targets --deny=warnings + cargo clippy --release --features=ssr --all-targets -- --deny=warnings + cargo clippy --release --features=csr --all-targets -- --deny=warnings + cargo clippy --release --features=hydration --all-targets -- --deny=warnings cargo clippy --release --features "csr,ssr,hydration,tokio" --all-targets -- --deny=warnings working-directory: packages/yew diff --git a/packages/yew/src/platform/rt_wasm_bindgen/mod.rs b/packages/yew/src/platform/rt_wasm_bindgen/mod.rs index 65f1582afb9..ad3f77a163a 100644 --- a/packages/yew/src/platform/rt_wasm_bindgen/mod.rs +++ b/packages/yew/src/platform/rt_wasm_bindgen/mod.rs @@ -1,3 +1,5 @@ +use std::future::Future; + #[cfg(feature = "ssr")] pub(crate) mod sync; diff --git a/packages/yew/src/platform/rt_wasm_bindgen/sync/mpsc/mod.rs b/packages/yew/src/platform/rt_wasm_bindgen/sync/mpsc/mod.rs index f2447763305..95f6d3c4044 100644 --- a/packages/yew/src/platform/rt_wasm_bindgen/sync/mpsc/mod.rs +++ b/packages/yew/src/platform/rt_wasm_bindgen/sync/mpsc/mod.rs @@ -1,4 +1,3 @@ -use std::marker::PhantomData; use std::pin::Pin; use std::task::{Context, Poll}; @@ -29,12 +28,10 @@ impl Clone for UnboundedSender { } pub(crate) fn unbounded() -> (UnboundedSender, UnboundedReceiver) { - let (inner_tx, inner_rx) = imp::unbounded_channel(); + let (inner_tx, inner_rx) = imp::unbounded(); let tx = UnboundedSender { inner: inner_tx }; - let rx = UnboundedReceiver { - inner: UnboundedReceiverStream::new(inner_rx), - }; + let rx = UnboundedReceiver { inner: inner_rx }; (tx, rx) } From 848f8f742541b8d353c138f5182373fa8a43e966 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sat, 25 Jun 2022 22:41:18 +0900 Subject: [PATCH 28/50] Fix feature soundness. --- packages/yew/src/virtual_dom/vlist.rs | 1 + packages/yew/src/virtual_dom/vsuspense.rs | 1 + packages/yew/src/virtual_dom/vtag.rs | 1 + packages/yew/src/virtual_dom/vtext.rs | 1 + 4 files changed, 4 insertions(+) diff --git a/packages/yew/src/virtual_dom/vlist.rs b/packages/yew/src/virtual_dom/vlist.rs index 11d56c007ed..449748bc7a7 100644 --- a/packages/yew/src/virtual_dom/vlist.rs +++ b/packages/yew/src/virtual_dom/vlist.rs @@ -203,6 +203,7 @@ mod feat_ssr { } #[cfg(not(target_arch = "wasm32"))] +#[cfg(feature = "ssr")] #[cfg(test)] mod ssr_tests { use tokio::test; diff --git a/packages/yew/src/virtual_dom/vsuspense.rs b/packages/yew/src/virtual_dom/vsuspense.rs index 5a40b174b0f..e1ee1be22cc 100644 --- a/packages/yew/src/virtual_dom/vsuspense.rs +++ b/packages/yew/src/virtual_dom/vsuspense.rs @@ -57,6 +57,7 @@ mod feat_ssr { } #[cfg(not(target_arch = "wasm32"))] +#[cfg(feature = "ssr")] #[cfg(test)] mod ssr_tests { use std::rc::Rc; diff --git a/packages/yew/src/virtual_dom/vtag.rs b/packages/yew/src/virtual_dom/vtag.rs index 4483412be96..a779043a8b3 100644 --- a/packages/yew/src/virtual_dom/vtag.rs +++ b/packages/yew/src/virtual_dom/vtag.rs @@ -511,6 +511,7 @@ mod feat_ssr { } #[cfg(not(target_arch = "wasm32"))] +#[cfg(feature = "ssr")] #[cfg(test)] mod ssr_tests { use tokio::test; diff --git a/packages/yew/src/virtual_dom/vtext.rs b/packages/yew/src/virtual_dom/vtext.rs index c85fd77a509..6beea53a286 100644 --- a/packages/yew/src/virtual_dom/vtext.rs +++ b/packages/yew/src/virtual_dom/vtext.rs @@ -53,6 +53,7 @@ mod feat_ssr { } #[cfg(not(target_arch = "wasm32"))] +#[cfg(feature = "ssr")] #[cfg(test)] mod ssr_tests { use tokio::test; From 64d2ee4860d2080350ad776bf7ef1c31b14b039d Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sat, 25 Jun 2022 22:51:45 +0900 Subject: [PATCH 29/50] Fix CI. --- .github/workflows/main-checks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main-checks.yml b/.github/workflows/main-checks.yml index 44c951bdd18..f4276c660c5 100644 --- a/.github/workflows/main-checks.yml +++ b/.github/workflows/main-checks.yml @@ -59,7 +59,7 @@ jobs: - name: Lint feature soundness run: | - cargo clippy --release --all-targets-- --all-targets --deny=warnings + cargo clippy --release --all-targets -- --deny=warnings cargo clippy --release --features=ssr --all-targets -- --deny=warnings cargo clippy --release --features=csr --all-targets -- --deny=warnings cargo clippy --release --features=hydration --all-targets -- --deny=warnings From 47caa5212c4abf76801c4b5fb2edbdc979209368 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sat, 25 Jun 2022 23:03:01 +0900 Subject: [PATCH 30/50] Inlining. --- packages/yew/src/platform/rt_tokio/sync/mpsc/mod.rs | 4 ++++ packages/yew/src/platform/rt_wasm_bindgen/sync/mpsc/mod.rs | 4 ++++ packages/yew/src/platform/sync/mpsc/mod.rs | 1 + 3 files changed, 9 insertions(+) diff --git a/packages/yew/src/platform/rt_tokio/sync/mpsc/mod.rs b/packages/yew/src/platform/rt_tokio/sync/mpsc/mod.rs index b1e21e49aaf..774b1957000 100644 --- a/packages/yew/src/platform/rt_tokio/sync/mpsc/mod.rs +++ b/packages/yew/src/platform/rt_tokio/sync/mpsc/mod.rs @@ -28,6 +28,7 @@ impl Clone for UnboundedSender { } } +#[inline] pub(crate) fn unbounded() -> (UnboundedSender, UnboundedReceiver) { let (inner_tx, inner_rx) = imp::unbounded_channel(); @@ -40,6 +41,7 @@ pub(crate) fn unbounded() -> (UnboundedSender, UnboundedReceiver) { } impl UnboundedSender { + #[inline] pub fn send(&self, value: T) -> Result<(), SendError> { self.inner.send(value).map_err(|e| SendError(e.0)) } @@ -48,11 +50,13 @@ impl UnboundedSender { impl Stream for UnboundedReceiver { type Item = T; + #[inline] fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = self.project(); this.inner.poll_next(cx) } + #[inline] fn size_hint(&self) -> (usize, Option) { self.inner.size_hint() } diff --git a/packages/yew/src/platform/rt_wasm_bindgen/sync/mpsc/mod.rs b/packages/yew/src/platform/rt_wasm_bindgen/sync/mpsc/mod.rs index 95f6d3c4044..9a7498c0637 100644 --- a/packages/yew/src/platform/rt_wasm_bindgen/sync/mpsc/mod.rs +++ b/packages/yew/src/platform/rt_wasm_bindgen/sync/mpsc/mod.rs @@ -27,6 +27,7 @@ impl Clone for UnboundedSender { } } +#[inline] pub(crate) fn unbounded() -> (UnboundedSender, UnboundedReceiver) { let (inner_tx, inner_rx) = imp::unbounded(); @@ -37,6 +38,7 @@ pub(crate) fn unbounded() -> (UnboundedSender, UnboundedReceiver) { } impl UnboundedSender { + #[inline] pub fn send(&self, value: T) -> Result<(), SendError> { self.inner .unbounded_send(value) @@ -47,11 +49,13 @@ impl UnboundedSender { impl Stream for UnboundedReceiver { type Item = T; + #[inline] fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = self.project(); this.inner.poll_next(cx) } + #[inline] fn size_hint(&self) -> (usize, Option) { self.inner.size_hint() } diff --git a/packages/yew/src/platform/sync/mpsc/mod.rs b/packages/yew/src/platform/sync/mpsc/mod.rs index 96460e06f68..79c8081cb73 100644 --- a/packages/yew/src/platform/sync/mpsc/mod.rs +++ b/packages/yew/src/platform/sync/mpsc/mod.rs @@ -45,6 +45,7 @@ impl Clone for UnboundedSender { } /// Creates an unbounded channel. +#[inline] pub fn unbounded() -> (UnboundedSender, UnboundedReceiver) { let (inner_tx, inner_rx) = imp::unbounded(); From f277d4185c0b0d2b3b7ad2609d46c90d31e1c224 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 26 Jun 2022 00:05:12 +0900 Subject: [PATCH 31/50] Add documentation. --- packages/yew/src/platform/mod.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/yew/src/platform/mod.rs b/packages/yew/src/platform/mod.rs index 6fd3d087e75..846df8c3a8b 100644 --- a/packages/yew/src/platform/mod.rs +++ b/packages/yew/src/platform/mod.rs @@ -1,5 +1,25 @@ //! This module provides io compatibility over browser tasks and other asyncio runtimes (e.g.: //! tokio). +//! +//! Yew implements a single-threaded runtime that executes `!Send` futures. When your application +//! starts with `yew::Renderer` or is rendered by `yew::ServerRenderer`, it is executed within the +//! Yew runtime. The renderer will select a worker thread from the internal worker +//! pool of Yew runtime. All tasks spawned with `spawn_local` will run on the same worker thread as +//! the rendering thread the renderer has selected. When the renderer runs in a WebAssembly target, +//! all tasks will be scheduled on the main thread. +//! +//! Yew runtime is implemented with native runtimes depending on the target platform and can use +//! all features (timers / IO / task synchronisation) from the selected native runtime: +//! +//! - `wasm-bindgen-futures` (WebAssembly targets) +//! - `tokio` (non-WebAssembly targets) +//! +//! Yew runtime alleviates the implementation requirement of `Send` futures when running with +//! multi-threaded runtimes like `tokio` and `!Send` futures on WebAssembly platforms and produces +//! good performance when the workload is IO-bounded and have similar runtime cost. When you have an +//! expensive CPU-bounded task, it should be spawned with a `Send`-aware spawning mechanism provided +//! by the native runtime, `std::thread::spawn` or `yew-agent` and communicates with the application +//! using channels or agent bridges. use std::future::Future; From 704f2a8377db1fba111efcfae3e1c4b1e64a9859 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 26 Jun 2022 00:37:37 +0900 Subject: [PATCH 32/50] Punctuation. --- packages/yew/src/platform/sync/mpsc/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yew/src/platform/sync/mpsc/mod.rs b/packages/yew/src/platform/sync/mpsc/mod.rs index 79c8081cb73..6bd0d672595 100644 --- a/packages/yew/src/platform/sync/mpsc/mod.rs +++ b/packages/yew/src/platform/sync/mpsc/mod.rs @@ -1,4 +1,4 @@ -//! A multi-producer, single-receiver channel; +//! A multi-producer, single-receiver channel. use std::error::Error; use std::fmt; From dacdce8b1489741c1fc6f5be46527d14fc4a7961 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 26 Jun 2022 01:18:51 +0900 Subject: [PATCH 33/50] Switch to tokio channel. --- packages/yew/Cargo.toml | 7 +- packages/yew/src/html/component/lifecycle.rs | 2 +- packages/yew/src/io.rs | 6 +- packages/yew/src/platform/mod.rs | 12 ++- .../platform/{rt_null/mod.rs => rt_none.rs} | 3 - packages/yew/src/platform/rt_null/sync/mod.rs | 1 - .../yew/src/platform/rt_null/sync/mpsc/mod.rs | 48 ----------- .../platform/{rt_tokio/mod.rs => rt_tokio.rs} | 3 - .../yew/src/platform/rt_tokio/sync/mod.rs | 1 - .../src/platform/rt_tokio/sync/mpsc/mod.rs | 63 --------------- .../mod.rs => rt_wasm_bindgen.rs} | 3 - .../src/platform/rt_wasm_bindgen/sync/mod.rs | 1 - .../platform/rt_wasm_bindgen/sync/mpsc/mod.rs | 62 --------------- packages/yew/src/platform/sync/mod.rs | 5 +- packages/yew/src/platform/sync/mpsc.rs | 6 ++ packages/yew/src/platform/sync/mpsc/mod.rs | 79 ------------------- packages/yew/src/server_renderer.rs | 6 +- 17 files changed, 29 insertions(+), 279 deletions(-) rename packages/yew/src/platform/{rt_null/mod.rs => rt_none.rs} (94%) delete mode 100644 packages/yew/src/platform/rt_null/sync/mod.rs delete mode 100644 packages/yew/src/platform/rt_null/sync/mpsc/mod.rs rename packages/yew/src/platform/{rt_tokio/mod.rs => rt_tokio.rs} (93%) delete mode 100644 packages/yew/src/platform/rt_tokio/sync/mod.rs delete mode 100644 packages/yew/src/platform/rt_tokio/sync/mpsc/mod.rs rename packages/yew/src/platform/{rt_wasm_bindgen/mod.rs => rt_wasm_bindgen.rs} (87%) delete mode 100644 packages/yew/src/platform/rt_wasm_bindgen/sync/mod.rs delete mode 100644 packages/yew/src/platform/rt_wasm_bindgen/sync/mpsc/mod.rs create mode 100644 packages/yew/src/platform/sync/mpsc.rs delete mode 100644 packages/yew/src/platform/sync/mpsc/mod.rs diff --git a/packages/yew/Cargo.toml b/packages/yew/Cargo.toml index e54edd3abcb..e399b6c8a1d 100644 --- a/packages/yew/Cargo.toml +++ b/packages/yew/Cargo.toml @@ -26,7 +26,7 @@ slab = "0.4" wasm-bindgen = "0.2" yew-macro = { version = "^0.19.0", path = "../yew-macro" } thiserror = "1.0" - +tokio = "1.15.0" futures = { version = "0.3", optional = true } html-escape = { version = "0.2.9", optional = true } implicit-clone = { version = "0.2", features = ["map"] } @@ -75,7 +75,6 @@ features = [ wasm-bindgen-futures = "0.4" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -tokio = { version = "1.15.0", features = ["rt"], optional = true } num_cpus = { version = "1.13", optional = true } tokio-util = { version = "0.7", features = ["rt"], optional = true } once_cell = "1" @@ -97,8 +96,8 @@ features = [ ] [features] -tokio = ["dep:tokio", "dep:num_cpus", "dep:tokio-util", "dep:tokio-stream"] -ssr = ["dep:futures", "dep:html-escape", "dep:base64ct", "dep:bincode", "dep:pin-project"] +tokio = ["tokio/rt", "dep:num_cpus", "dep:tokio-util"] +ssr = ["tokio/sync", "dep:tokio-stream", "dep:futures", "dep:html-escape", "dep:base64ct", "dep:bincode", "dep:pin-project"] csr = [] hydration = ["csr", "dep:bincode"] nightly = ["yew-macro/nightly"] diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index c12c7a2e1b6..75841b65904 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -41,7 +41,7 @@ pub(crate) enum ComponentRenderState { #[cfg(feature = "ssr")] Ssr { - sender: Option>, + sender: Option>, }, } diff --git a/packages/yew/src/io.rs b/packages/yew/src/io.rs index 62b49d07561..2c8c3df4116 100644 --- a/packages/yew/src/io.rs +++ b/packages/yew/src/io.rs @@ -4,7 +4,7 @@ use std::borrow::Cow; use futures::stream::Stream; -use crate::platform::sync::mpsc::{self, UnboundedSender}; +use crate::platform::sync::mpsc::{self, UnboundedReceiverStream, UnboundedSender}; // Same as std::io::BufWriter and futures::io::BufWriter. pub(crate) const DEFAULT_BUF_SIZE: usize = 8 * 1024; @@ -18,7 +18,7 @@ pub(crate) struct BufWriter { /// Creates a Buffer pair. pub(crate) fn buffer(capacity: usize) -> (BufWriter, impl Stream) { - let (tx, rx) = mpsc::unbounded::(); + let (tx, rx) = mpsc::unbounded_channel::(); let tx = BufWriter { buf: String::with_capacity(capacity), @@ -26,7 +26,7 @@ pub(crate) fn buffer(capacity: usize) -> (BufWriter, impl Stream) capacity, }; - (tx, rx) + (tx, UnboundedReceiverStream::new(rx)) } // Implementation Notes: diff --git a/packages/yew/src/platform/mod.rs b/packages/yew/src/platform/mod.rs index 846df8c3a8b..2bde1c5e073 100644 --- a/packages/yew/src/platform/mod.rs +++ b/packages/yew/src/platform/mod.rs @@ -20,6 +20,12 @@ //! expensive CPU-bounded task, it should be spawned with a `Send`-aware spawning mechanism provided //! by the native runtime, `std::thread::spawn` or `yew-agent` and communicates with the application //! using channels or agent bridges. +//! +//! Yew's ServerRenderer can also be executed in applications using the `async-std` runtime. +//! Rendering tasks will enter Yew runtime and be executed with `tokio`. When the rendering task +//! finishes, the result is returned to the `async-std` runtime. This process is transparent to the +//! future that executes the renderer. The Yew application still needs to use `tokio`'s timer, IO +//! and task synchronisation primitives. use std::future::Future; @@ -27,13 +33,13 @@ use std::future::Future; pub(crate) mod sync; #[cfg(not(any(feature = "tokio", target_arch = "wasm32")))] -#[path = "rt_null/mod.rs"] +#[path = "rt_none.rs"] mod imp; #[cfg(all(not(target_arch = "wasm32"), feature = "tokio"))] -#[path = "rt_tokio/mod.rs"] +#[path = "rt_tokio.rs"] mod imp; #[cfg(target_arch = "wasm32")] -#[path = "rt_wasm_bindgen/mod.rs"] +#[path = "rt_wasm_bindgen.rs"] mod imp; /// Spawns a task on current thread. diff --git a/packages/yew/src/platform/rt_null/mod.rs b/packages/yew/src/platform/rt_none.rs similarity index 94% rename from packages/yew/src/platform/rt_null/mod.rs rename to packages/yew/src/platform/rt_none.rs index f8ecb83bfde..3a8dc6a671f 100644 --- a/packages/yew/src/platform/rt_null/mod.rs +++ b/packages/yew/src/platform/rt_none.rs @@ -1,8 +1,5 @@ use std::future::Future; -#[cfg(feature = "ssr")] -pub(crate) mod sync; - #[inline(always)] pub(super) fn spawn_local(_f: F) where diff --git a/packages/yew/src/platform/rt_null/sync/mod.rs b/packages/yew/src/platform/rt_null/sync/mod.rs deleted file mode 100644 index 44f9c4390e1..00000000000 --- a/packages/yew/src/platform/rt_null/sync/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub(crate) mod mpsc; diff --git a/packages/yew/src/platform/rt_null/sync/mpsc/mod.rs b/packages/yew/src/platform/rt_null/sync/mpsc/mod.rs deleted file mode 100644 index 86c45c89899..00000000000 --- a/packages/yew/src/platform/rt_null/sync/mpsc/mod.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::marker::PhantomData; -use std::pin::Pin; -use std::task::{Context, Poll}; - -use futures::stream::Stream; - -use crate::platform::sync::mpsc::SendError; - -#[derive(Debug)] -pub(crate) struct UnboundedSender { - inner: PhantomData, -} - -#[derive(Debug)] -pub(crate) struct UnboundedReceiver { - inner: PhantomData, -} - -impl Clone for UnboundedSender { - fn clone(&self) -> Self { - Self { inner: PhantomData } - } -} - -pub(crate) fn unbounded() -> (UnboundedSender, UnboundedReceiver) { - panic!( - r#"No runtime configured for this platform, features that requires task spawning can't be used. - Either compile with `target_arch = "wasm32", or enable the `tokio` feature."# - ); -} - -impl UnboundedSender { - pub fn send(&self, _value: T) -> Result<(), SendError> { - unimplemented!(); - } -} - -impl Stream for UnboundedReceiver { - type Item = T; - - fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { - unimplemented!(); - } - - fn size_hint(&self) -> (usize, Option) { - unimplemented!(); - } -} diff --git a/packages/yew/src/platform/rt_tokio/mod.rs b/packages/yew/src/platform/rt_tokio.rs similarity index 93% rename from packages/yew/src/platform/rt_tokio/mod.rs rename to packages/yew/src/platform/rt_tokio.rs index a6a6f3bab21..2516732b635 100644 --- a/packages/yew/src/platform/rt_tokio/mod.rs +++ b/packages/yew/src/platform/rt_tokio.rs @@ -2,9 +2,6 @@ use std::future::Future; -#[cfg(feature = "ssr")] -pub(crate) mod sync; - #[cfg(feature = "ssr")] pub(super) async fn run_pinned(create_task: F) -> Fut::Output where diff --git a/packages/yew/src/platform/rt_tokio/sync/mod.rs b/packages/yew/src/platform/rt_tokio/sync/mod.rs deleted file mode 100644 index 44f9c4390e1..00000000000 --- a/packages/yew/src/platform/rt_tokio/sync/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub(crate) mod mpsc; diff --git a/packages/yew/src/platform/rt_tokio/sync/mpsc/mod.rs b/packages/yew/src/platform/rt_tokio/sync/mpsc/mod.rs deleted file mode 100644 index 774b1957000..00000000000 --- a/packages/yew/src/platform/rt_tokio/sync/mpsc/mod.rs +++ /dev/null @@ -1,63 +0,0 @@ -use std::pin::Pin; -use std::task::{Context, Poll}; - -use futures::stream::Stream; -use pin_project::pin_project; -use tokio::sync::mpsc as imp; -use tokio_stream::wrappers::UnboundedReceiverStream; - -use crate::platform::sync::mpsc::SendError; - -#[derive(Debug)] -pub(crate) struct UnboundedSender { - inner: imp::UnboundedSender, -} - -#[derive(Debug)] -#[pin_project] -pub(crate) struct UnboundedReceiver { - #[pin] - inner: UnboundedReceiverStream, -} - -impl Clone for UnboundedSender { - fn clone(&self) -> Self { - Self { - inner: self.inner.clone(), - } - } -} - -#[inline] -pub(crate) fn unbounded() -> (UnboundedSender, UnboundedReceiver) { - let (inner_tx, inner_rx) = imp::unbounded_channel(); - - let tx = UnboundedSender { inner: inner_tx }; - let rx = UnboundedReceiver { - inner: UnboundedReceiverStream::new(inner_rx), - }; - - (tx, rx) -} - -impl UnboundedSender { - #[inline] - pub fn send(&self, value: T) -> Result<(), SendError> { - self.inner.send(value).map_err(|e| SendError(e.0)) - } -} - -impl Stream for UnboundedReceiver { - type Item = T; - - #[inline] - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let this = self.project(); - this.inner.poll_next(cx) - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.inner.size_hint() - } -} diff --git a/packages/yew/src/platform/rt_wasm_bindgen/mod.rs b/packages/yew/src/platform/rt_wasm_bindgen.rs similarity index 87% rename from packages/yew/src/platform/rt_wasm_bindgen/mod.rs rename to packages/yew/src/platform/rt_wasm_bindgen.rs index ad3f77a163a..0c37b168d5a 100644 --- a/packages/yew/src/platform/rt_wasm_bindgen/mod.rs +++ b/packages/yew/src/platform/rt_wasm_bindgen.rs @@ -1,8 +1,5 @@ use std::future::Future; -#[cfg(feature = "ssr")] -pub(crate) mod sync; - pub(super) use wasm_bindgen_futures::spawn_local; #[cfg(feature = "ssr")] diff --git a/packages/yew/src/platform/rt_wasm_bindgen/sync/mod.rs b/packages/yew/src/platform/rt_wasm_bindgen/sync/mod.rs deleted file mode 100644 index 44f9c4390e1..00000000000 --- a/packages/yew/src/platform/rt_wasm_bindgen/sync/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub(crate) mod mpsc; diff --git a/packages/yew/src/platform/rt_wasm_bindgen/sync/mpsc/mod.rs b/packages/yew/src/platform/rt_wasm_bindgen/sync/mpsc/mod.rs deleted file mode 100644 index 9a7498c0637..00000000000 --- a/packages/yew/src/platform/rt_wasm_bindgen/sync/mpsc/mod.rs +++ /dev/null @@ -1,62 +0,0 @@ -use std::pin::Pin; -use std::task::{Context, Poll}; - -use futures::channel::mpsc as imp; -use futures::stream::Stream; -use pin_project::pin_project; - -use crate::platform::sync::mpsc::SendError; - -#[derive(Debug)] -pub(crate) struct UnboundedSender { - inner: imp::UnboundedSender, -} - -#[derive(Debug)] -#[pin_project] -pub(crate) struct UnboundedReceiver { - #[pin] - inner: imp::UnboundedReceiver, -} - -impl Clone for UnboundedSender { - fn clone(&self) -> Self { - Self { - inner: self.inner.clone(), - } - } -} - -#[inline] -pub(crate) fn unbounded() -> (UnboundedSender, UnboundedReceiver) { - let (inner_tx, inner_rx) = imp::unbounded(); - - let tx = UnboundedSender { inner: inner_tx }; - let rx = UnboundedReceiver { inner: inner_rx }; - - (tx, rx) -} - -impl UnboundedSender { - #[inline] - pub fn send(&self, value: T) -> Result<(), SendError> { - self.inner - .unbounded_send(value) - .map_err(|e| SendError(e.into_inner())) - } -} - -impl Stream for UnboundedReceiver { - type Item = T; - - #[inline] - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let this = self.project(); - this.inner.poll_next(cx) - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.inner.size_hint() - } -} diff --git a/packages/yew/src/platform/sync/mod.rs b/packages/yew/src/platform/sync/mod.rs index 33a48c3ebde..63c99dec41a 100644 --- a/packages/yew/src/platform/sync/mod.rs +++ b/packages/yew/src/platform/sync/mod.rs @@ -1,4 +1,5 @@ -//! A module that provides task synchronization primitives. +//! A module that provides task synchronisation primitives. +#[doc(inline)] +pub use tokio::sync::oneshot; pub mod mpsc; -pub use futures::channel::oneshot; diff --git a/packages/yew/src/platform/sync/mpsc.rs b/packages/yew/src/platform/sync/mpsc.rs new file mode 100644 index 00000000000..de09d342bc9 --- /dev/null +++ b/packages/yew/src/platform/sync/mpsc.rs @@ -0,0 +1,6 @@ +//! A multi-producer, single-receiver channel. + +#[doc(inline)] +pub use tokio::sync::mpsc::*; +#[doc(inline)] +pub use tokio_stream::wrappers::{ReceiverStream, UnboundedReceiverStream}; diff --git a/packages/yew/src/platform/sync/mpsc/mod.rs b/packages/yew/src/platform/sync/mpsc/mod.rs deleted file mode 100644 index 6bd0d672595..00000000000 --- a/packages/yew/src/platform/sync/mpsc/mod.rs +++ /dev/null @@ -1,79 +0,0 @@ -//! A multi-producer, single-receiver channel. - -use std::error::Error; -use std::fmt; -use std::pin::Pin; -use std::task::{Context, Poll}; - -use futures::stream::Stream; -use pin_project::pin_project; - -use crate::platform::imp::sync::mpsc as imp; - -/// The channel has closed when attempting sending. -#[derive(Debug)] -pub struct SendError(pub T); - -impl fmt::Display for SendError { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(fmt, "channel closed") - } -} - -impl Error for SendError {} - -/// An unbounded sender for a multi-producer, single receiver channel. -#[derive(Debug)] -pub struct UnboundedSender { - inner: imp::UnboundedSender, -} - -/// An unbounded receiver for a multi-producer, single receiver channel. -#[derive(Debug)] -#[pin_project] -pub struct UnboundedReceiver { - #[pin] - inner: imp::UnboundedReceiver, -} - -impl Clone for UnboundedSender { - fn clone(&self) -> Self { - Self { - inner: self.inner.clone(), - } - } -} - -/// Creates an unbounded channel. -#[inline] -pub fn unbounded() -> (UnboundedSender, UnboundedReceiver) { - let (inner_tx, inner_rx) = imp::unbounded(); - - let tx = UnboundedSender { inner: inner_tx }; - let rx = UnboundedReceiver { inner: inner_rx }; - - (tx, rx) -} - -impl UnboundedSender { - /// Send the value to the receiver. - #[inline] - pub fn send(&self, value: T) -> Result<(), SendError> { - self.inner.send(value) - } -} - -impl Stream for UnboundedReceiver { - type Item = T; - - #[inline] - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let this = self.project(); - this.inner.poll_next(cx) - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.inner.size_hint() - } -} diff --git a/packages/yew/src/server_renderer.rs b/packages/yew/src/server_renderer.rs index 78075fbbc92..dade9d02d70 100644 --- a/packages/yew/src/server_renderer.rs +++ b/packages/yew/src/server_renderer.rs @@ -106,8 +106,10 @@ where /// A Yew Server-side Renderer. /// -/// The renderer spawns the rendering task with [`run_pinned`] which maintains an internal worker -/// pool. +/// This renderer spawns the rendering task to an internal worker pool and receives result when +/// the rendering process has finished. +/// +/// See [`yew::platform`] for more information. #[cfg_attr(documenting, doc(cfg(feature = "ssr")))] #[derive(Debug)] pub struct ServerRenderer From 901cffd38265c1b04399fa2a40d5da1a5b570a97 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 26 Jun 2022 01:21:03 +0900 Subject: [PATCH 34/50] Remvoe pin-project. --- packages/yew/Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/yew/Cargo.toml b/packages/yew/Cargo.toml index e399b6c8a1d..f753d5cbc44 100644 --- a/packages/yew/Cargo.toml +++ b/packages/yew/Cargo.toml @@ -33,7 +33,6 @@ implicit-clone = { version = "0.2", features = ["map"] } base64ct = { version = "1.5.0", features = ["std"], optional = true } bincode = { version = "1.3.3", optional = true } serde = { version = "1", features = ["derive"] } -pin-project = { version = "1.0.10", optional = true } [dependencies.web-sys] version = "0.3" @@ -97,7 +96,7 @@ features = [ [features] tokio = ["tokio/rt", "dep:num_cpus", "dep:tokio-util"] -ssr = ["tokio/sync", "dep:tokio-stream", "dep:futures", "dep:html-escape", "dep:base64ct", "dep:bincode", "dep:pin-project"] +ssr = ["tokio/sync", "dep:tokio-stream", "dep:futures", "dep:html-escape", "dep:base64ct", "dep:bincode"] csr = [] hydration = ["csr", "dep:bincode"] nightly = ["yew-macro/nightly"] From 4511ffd4be8f26a173d5631d62e5f3a79f9c15b6 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 26 Jun 2022 01:54:41 +0900 Subject: [PATCH 35/50] Fix feature soundness. --- .github/workflows/main-checks.yml | 18 ++++------------- ci/feature-soundness-release.sh | 32 +++++++++++++++++++++++++++++++ ci/feature-soundness.sh | 32 +++++++++++++++++++++++++++++++ packages/yew/Cargo.toml | 6 +++--- packages/yew/Makefile.toml | 15 ++------------- 5 files changed, 73 insertions(+), 30 deletions(-) create mode 100755 ci/feature-soundness-release.sh create mode 100755 ci/feature-soundness.sh diff --git a/.github/workflows/main-checks.yml b/.github/workflows/main-checks.yml index f4276c660c5..7e400b4a9b9 100644 --- a/.github/workflows/main-checks.yml +++ b/.github/workflows/main-checks.yml @@ -25,15 +25,10 @@ jobs: uses: actions-rs/cargo@v1 with: command: clippy - args: --all-targets --features "csr,ssr,hydration" -- -D warnings + args: --all-targets --features "csr,ssr,hydration,tokio" -- -D warnings - name: Lint feature soundness - run: | - cargo clippy --all-targets -- --deny=warnings - cargo clippy --features=ssr --all-targets -- --deny=warnings - cargo clippy --features=csr --all-targets -- --deny=warnings - cargo clippy --features=hydration --all-targets -- --deny=warnings - cargo clippy --features "csr,ssr,hydration,tokio" --all-targets -- --deny=warnings + run: bash ci/feature-soundness.sh working-directory: packages/yew @@ -55,15 +50,10 @@ jobs: uses: actions-rs/cargo@v1 with: command: clippy - args: --all-targets --features "csr,ssr,hydration" --release -- -D warnings + args: --all-targets --features "csr,ssr,hydration,tokio" --release -- -D warnings - name: Lint feature soundness - run: | - cargo clippy --release --all-targets -- --deny=warnings - cargo clippy --release --features=ssr --all-targets -- --deny=warnings - cargo clippy --release --features=csr --all-targets -- --deny=warnings - cargo clippy --release --features=hydration --all-targets -- --deny=warnings - cargo clippy --release --features "csr,ssr,hydration,tokio" --all-targets -- --deny=warnings + run: bash ci/feature-soundness-release.sh working-directory: packages/yew spell_check: diff --git a/ci/feature-soundness-release.sh b/ci/feature-soundness-release.sh new file mode 100755 index 00000000000..6de18c3ab23 --- /dev/null +++ b/ci/feature-soundness-release.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -xe + +# You can extract the feature list with the following command: +# cargo hack check --feature-powerset --exclude-features nightly + +# You need to run this script in packages/yew + +cargo clippy --release --no-default-features -- --deny=warnings +cargo clippy --release --no-default-features --features csr -- --deny=warnings +cargo clippy --release --no-default-features --features default -- --deny=warnings +cargo clippy --release --no-default-features --features csr,default -- --deny=warnings +cargo clippy --release --no-default-features --features hydration -- --deny=warnings +cargo clippy --release --no-default-features --features default,hydration -- --deny=warnings +cargo clippy --release --no-default-features --features ssr -- --deny=warnings +cargo clippy --release --no-default-features --features csr,ssr -- --deny=warnings +cargo clippy --release --no-default-features --features default,ssr -- --deny=warnings +cargo clippy --release --no-default-features --features csr,default,ssr -- --deny=warnings +cargo clippy --release --no-default-features --features hydration,ssr -- --deny=warnings +cargo clippy --release --no-default-features --features default,hydration,ssr -- --deny=warnings +cargo clippy --release --no-default-features --features tokio -- --deny=warnings +cargo clippy --release --no-default-features --features csr,tokio -- --deny=warnings +cargo clippy --release --no-default-features --features default,tokio -- --deny=warnings +cargo clippy --release --no-default-features --features csr,default,tokio -- --deny=warnings +cargo clippy --release --no-default-features --features hydration,tokio -- --deny=warnings +cargo clippy --release --no-default-features --features default,hydration,tokio -- --deny=warnings +cargo clippy --release --no-default-features --features ssr,tokio -- --deny=warnings +cargo clippy --release --no-default-features --features csr,ssr,tokio -- --deny=warnings +cargo clippy --release --no-default-features --features default,ssr,tokio -- --deny=warnings +cargo clippy --release --no-default-features --features csr,default,ssr,tokio -- --deny=warnings +cargo clippy --release --no-default-features --features hydration,ssr,tokio -- --deny=warnings +cargo clippy --release --no-default-features --features default,hydration,ssr,tokio -- --deny=warnings diff --git a/ci/feature-soundness.sh b/ci/feature-soundness.sh new file mode 100755 index 00000000000..5686b2fa392 --- /dev/null +++ b/ci/feature-soundness.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -xe + +# You can extract the feature list with the following command: +# cargo hack check --feature-powerset --exclude-features nightly + +# You need to run this script in packages/yew + +cargo clippy --no-default-features -- --deny=warnings +cargo clippy --no-default-features --features csr -- --deny=warnings +cargo clippy --no-default-features --features default -- --deny=warnings +cargo clippy --no-default-features --features csr,default -- --deny=warnings +cargo clippy --no-default-features --features hydration -- --deny=warnings +cargo clippy --no-default-features --features default,hydration -- --deny=warnings +cargo clippy --no-default-features --features ssr -- --deny=warnings +cargo clippy --no-default-features --features csr,ssr -- --deny=warnings +cargo clippy --no-default-features --features default,ssr -- --deny=warnings +cargo clippy --no-default-features --features csr,default,ssr -- --deny=warnings +cargo clippy --no-default-features --features hydration,ssr -- --deny=warnings +cargo clippy --no-default-features --features default,hydration,ssr -- --deny=warnings +cargo clippy --no-default-features --features tokio -- --deny=warnings +cargo clippy --no-default-features --features csr,tokio -- --deny=warnings +cargo clippy --no-default-features --features default,tokio -- --deny=warnings +cargo clippy --no-default-features --features csr,default,tokio -- --deny=warnings +cargo clippy --no-default-features --features hydration,tokio -- --deny=warnings +cargo clippy --no-default-features --features default,hydration,tokio -- --deny=warnings +cargo clippy --no-default-features --features ssr,tokio -- --deny=warnings +cargo clippy --no-default-features --features csr,ssr,tokio -- --deny=warnings +cargo clippy --no-default-features --features default,ssr,tokio -- --deny=warnings +cargo clippy --no-default-features --features csr,default,ssr,tokio -- --deny=warnings +cargo clippy --no-default-features --features hydration,ssr,tokio -- --deny=warnings +cargo clippy --no-default-features --features default,hydration,ssr,tokio -- --deny=warnings diff --git a/packages/yew/Cargo.toml b/packages/yew/Cargo.toml index f753d5cbc44..9430678d126 100644 --- a/packages/yew/Cargo.toml +++ b/packages/yew/Cargo.toml @@ -26,13 +26,14 @@ slab = "0.4" wasm-bindgen = "0.2" yew-macro = { version = "^0.19.0", path = "../yew-macro" } thiserror = "1.0" -tokio = "1.15.0" +tokio = { version = "1.15.0", features = ["sync"], default-features = false } futures = { version = "0.3", optional = true } html-escape = { version = "0.2.9", optional = true } implicit-clone = { version = "0.2", features = ["map"] } base64ct = { version = "1.5.0", features = ["std"], optional = true } bincode = { version = "1.3.3", optional = true } serde = { version = "1", features = ["derive"] } +tokio-stream = "0.1.9" [dependencies.web-sys] version = "0.3" @@ -77,7 +78,6 @@ wasm-bindgen-futures = "0.4" num_cpus = { version = "1.13", optional = true } tokio-util = { version = "0.7", features = ["rt"], optional = true } once_cell = "1" -tokio-stream = { version = "0.1.9", optional = true } [dev-dependencies] wasm-bindgen-test = "0.3" @@ -96,7 +96,7 @@ features = [ [features] tokio = ["tokio/rt", "dep:num_cpus", "dep:tokio-util"] -ssr = ["tokio/sync", "dep:tokio-stream", "dep:futures", "dep:html-escape", "dep:base64ct", "dep:bincode"] +ssr = ["dep:futures", "dep:html-escape", "dep:base64ct", "dep:bincode"] csr = [] hydration = ["csr", "dep:bincode"] nightly = ["yew-macro/nightly"] diff --git a/packages/yew/Makefile.toml b/packages/yew/Makefile.toml index 9948520c47c..da4903e1b63 100644 --- a/packages/yew/Makefile.toml +++ b/packages/yew/Makefile.toml @@ -25,17 +25,6 @@ dependencies = ["native-test", "wasm-test"] script = ''' #!/usr/bin/env bash set -ex -cargo clippy -- --deny=warnings -cargo clippy --features=ssr -- --deny=warnings -cargo clippy --features=ssr,tokio -- --deny=warnings -cargo clippy --features=csr -- --deny=warnings -cargo clippy --features=hydration -- --deny=warnings -cargo clippy --all-features --all-targets -- --deny=warnings - -cargo clippy --release -- --deny=warnings -cargo clippy --release --features=ssr -- --deny=warnings -cargo clippy --release --features=ssr,tokio -- --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 +bash ../../ci/feature-soundness.sh +bash ../../ci/feature-soundness-release.sh ''' From 318f1ef8b4b4c1ab08f6579d1ee13a3356104530 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 26 Jun 2022 01:57:01 +0900 Subject: [PATCH 36/50] Typo. --- packages/yew/src/io.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yew/src/io.rs b/packages/yew/src/io.rs index 2c8c3df4116..74ff8e02081 100644 --- a/packages/yew/src/io.rs +++ b/packages/yew/src/io.rs @@ -1,4 +1,4 @@ -//! This module contains types for I/O funtionality. +//! This module contains types for I/O functionality. use std::borrow::Cow; From d656fc715886379ede874d9bf3b327478703195b Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 26 Jun 2022 02:15:05 +0900 Subject: [PATCH 37/50] Move io to platform. --- packages/yew/src/html/component/scope.rs | 2 +- packages/yew/src/lib.rs | 3 --- packages/yew/src/{ => platform}/io.rs | 3 +++ packages/yew/src/platform/mod.rs | 4 +++- packages/yew/src/server_renderer.rs | 2 +- packages/yew/src/virtual_dom/mod.rs | 2 +- packages/yew/src/virtual_dom/vcomp.rs | 2 +- packages/yew/src/virtual_dom/vlist.rs | 2 +- packages/yew/src/virtual_dom/vnode.rs | 2 +- packages/yew/src/virtual_dom/vsuspense.rs | 2 +- packages/yew/src/virtual_dom/vtag.rs | 2 +- packages/yew/src/virtual_dom/vtext.rs | 2 +- 12 files changed, 15 insertions(+), 13 deletions(-) rename packages/yew/src/{ => platform}/io.rs (96%) diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index 7fd6a8789e5..519d7e5a397 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -264,7 +264,7 @@ mod feat_ssr { use crate::html::component::lifecycle::{ ComponentRenderState, CreateRunner, DestroyRunner, RenderRunner, }; - use crate::io::BufWriter; + use crate::platform::io::BufWriter; use crate::platform::sync::oneshot; use crate::scheduler; use crate::virtual_dom::Collectable; diff --git a/packages/yew/src/lib.rs b/packages/yew/src/lib.rs index f7ba4f19894..5371cda33ee 100644 --- a/packages/yew/src/lib.rs +++ b/packages/yew/src/lib.rs @@ -293,9 +293,6 @@ pub mod virtual_dom; #[cfg(feature = "ssr")] pub use server_renderer::*; -#[cfg(feature = "ssr")] -mod io; - #[cfg(feature = "csr")] mod app_handle; #[cfg(feature = "csr")] diff --git a/packages/yew/src/io.rs b/packages/yew/src/platform/io.rs similarity index 96% rename from packages/yew/src/io.rs rename to packages/yew/src/platform/io.rs index 74ff8e02081..5f4e8125d3e 100644 --- a/packages/yew/src/io.rs +++ b/packages/yew/src/platform/io.rs @@ -1,5 +1,8 @@ //! This module contains types for I/O functionality. +// This module remains private until impl trait type alias becomes available so +// `BufReader` can be produced with an existential type. + use std::borrow::Cow; use futures::stream::Stream; diff --git a/packages/yew/src/platform/mod.rs b/packages/yew/src/platform/mod.rs index 2bde1c5e073..0dbab224a3f 100644 --- a/packages/yew/src/platform/mod.rs +++ b/packages/yew/src/platform/mod.rs @@ -30,7 +30,9 @@ use std::future::Future; #[cfg(feature = "ssr")] -pub(crate) mod sync; +pub(crate) mod io; + +pub mod sync; #[cfg(not(any(feature = "tokio", target_arch = "wasm32")))] #[path = "rt_none.rs"] diff --git a/packages/yew/src/server_renderer.rs b/packages/yew/src/server_renderer.rs index dade9d02d70..4dbd638ab60 100644 --- a/packages/yew/src/server_renderer.rs +++ b/packages/yew/src/server_renderer.rs @@ -1,7 +1,7 @@ use futures::stream::{Stream, StreamExt}; use crate::html::{BaseComponent, Scope}; -use crate::io::{self, DEFAULT_BUF_SIZE}; +use crate::platform::io::{self, DEFAULT_BUF_SIZE}; use crate::platform::{run_pinned, spawn_local}; /// A Yew Server-side Renderer that renders on the current thread. diff --git a/packages/yew/src/virtual_dom/mod.rs b/packages/yew/src/virtual_dom/mod.rs index 8b2cd3f8a83..d97e3184b22 100644 --- a/packages/yew/src/virtual_dom/mod.rs +++ b/packages/yew/src/virtual_dom/mod.rs @@ -112,7 +112,7 @@ pub(crate) use feat_ssr_hydration::*; #[cfg(feature = "ssr")] mod feat_ssr { use super::*; - use crate::io::BufWriter; + use crate::platform::io::BufWriter; impl Collectable { pub(crate) fn write_open_tag(&self, w: &mut BufWriter) { diff --git a/packages/yew/src/virtual_dom/vcomp.rs b/packages/yew/src/virtual_dom/vcomp.rs index 6cac9259506..55a5603bd88 100644 --- a/packages/yew/src/virtual_dom/vcomp.rs +++ b/packages/yew/src/virtual_dom/vcomp.rs @@ -20,7 +20,7 @@ use crate::html::Scoped; use crate::html::{AnyScope, Scope}; use crate::html::{BaseComponent, NodeRef}; #[cfg(feature = "ssr")] -use crate::io::BufWriter; +use crate::platform::io::BufWriter; /// A virtual component. pub struct VComp { diff --git a/packages/yew/src/virtual_dom/vlist.rs b/packages/yew/src/virtual_dom/vlist.rs index 449748bc7a7..5b9c64b93f0 100644 --- a/packages/yew/src/virtual_dom/vlist.rs +++ b/packages/yew/src/virtual_dom/vlist.rs @@ -160,7 +160,7 @@ mod feat_ssr { use super::*; use crate::html::AnyScope; - use crate::io::{self, BufWriter}; + use crate::platform::io::{self, BufWriter}; impl VList { pub(crate) async fn render_into_stream( diff --git a/packages/yew/src/virtual_dom/vnode.rs b/packages/yew/src/virtual_dom/vnode.rs index 34cf4adb6b2..7c84fbfa0c5 100644 --- a/packages/yew/src/virtual_dom/vnode.rs +++ b/packages/yew/src/virtual_dom/vnode.rs @@ -151,7 +151,7 @@ mod feat_ssr { use super::*; use crate::html::AnyScope; - use crate::io::BufWriter; + use crate::platform::io::BufWriter; impl VNode { pub(crate) fn render_into_stream<'a>( diff --git a/packages/yew/src/virtual_dom/vsuspense.rs b/packages/yew/src/virtual_dom/vsuspense.rs index e1ee1be22cc..35dd30253a1 100644 --- a/packages/yew/src/virtual_dom/vsuspense.rs +++ b/packages/yew/src/virtual_dom/vsuspense.rs @@ -28,7 +28,7 @@ impl VSuspense { mod feat_ssr { use super::*; use crate::html::AnyScope; - use crate::io::BufWriter; + use crate::platform::io::BufWriter; use crate::virtual_dom::Collectable; impl VSuspense { diff --git a/packages/yew/src/virtual_dom/vtag.rs b/packages/yew/src/virtual_dom/vtag.rs index a779043a8b3..cf69128ec31 100644 --- a/packages/yew/src/virtual_dom/vtag.rs +++ b/packages/yew/src/virtual_dom/vtag.rs @@ -430,7 +430,7 @@ impl PartialEq for VTag { mod feat_ssr { use super::*; use crate::html::AnyScope; - use crate::io::BufWriter; + use crate::platform::io::BufWriter; use crate::virtual_dom::VText; // Elements that cannot have any child elements. diff --git a/packages/yew/src/virtual_dom/vtext.rs b/packages/yew/src/virtual_dom/vtext.rs index 6beea53a286..5ee6df06bfe 100644 --- a/packages/yew/src/virtual_dom/vtext.rs +++ b/packages/yew/src/virtual_dom/vtext.rs @@ -37,7 +37,7 @@ mod feat_ssr { use super::*; use crate::html::AnyScope; - use crate::io::BufWriter; + use crate::platform::io::BufWriter; impl VText { pub(crate) async fn render_into_stream( From 6b4f31f9d32c57de24d971f61f0bed6774b776d0 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 26 Jun 2022 02:26:43 +0900 Subject: [PATCH 38/50] Tokio does not compile. --- packages/yew/Cargo.toml | 9 ++- packages/yew/src/platform/io.rs | 6 +- packages/yew/src/platform/mod.rs | 9 ++- .../platform/{rt_none.rs => rt_none/mod.rs} | 3 + packages/yew/src/platform/rt_none/sync/mod.rs | 1 + .../yew/src/platform/rt_none/sync/mpsc/mod.rs | 48 +++++++++++ .../platform/{rt_tokio.rs => rt_tokio/mod.rs} | 3 + .../yew/src/platform/rt_tokio/sync/mod.rs | 1 + .../src/platform/rt_tokio/sync/mpsc/mod.rs | 63 +++++++++++++++ .../mod.rs} | 3 + .../src/platform/rt_wasm_bindgen/sync/mod.rs | 1 + .../platform/rt_wasm_bindgen/sync/mpsc/mod.rs | 62 +++++++++++++++ packages/yew/src/platform/sync/mod.rs | 5 +- packages/yew/src/platform/sync/mpsc.rs | 6 -- packages/yew/src/platform/sync/mpsc/mod.rs | 79 +++++++++++++++++++ 15 files changed, 279 insertions(+), 20 deletions(-) rename packages/yew/src/platform/{rt_none.rs => rt_none/mod.rs} (94%) create mode 100644 packages/yew/src/platform/rt_none/sync/mod.rs create mode 100644 packages/yew/src/platform/rt_none/sync/mpsc/mod.rs rename packages/yew/src/platform/{rt_tokio.rs => rt_tokio/mod.rs} (93%) create mode 100644 packages/yew/src/platform/rt_tokio/sync/mod.rs create mode 100644 packages/yew/src/platform/rt_tokio/sync/mpsc/mod.rs rename packages/yew/src/platform/{rt_wasm_bindgen.rs => rt_wasm_bindgen/mod.rs} (87%) create mode 100644 packages/yew/src/platform/rt_wasm_bindgen/sync/mod.rs create mode 100644 packages/yew/src/platform/rt_wasm_bindgen/sync/mpsc/mod.rs delete mode 100644 packages/yew/src/platform/sync/mpsc.rs create mode 100644 packages/yew/src/platform/sync/mpsc/mod.rs diff --git a/packages/yew/Cargo.toml b/packages/yew/Cargo.toml index 9430678d126..1c409f53360 100644 --- a/packages/yew/Cargo.toml +++ b/packages/yew/Cargo.toml @@ -26,14 +26,13 @@ slab = "0.4" wasm-bindgen = "0.2" yew-macro = { version = "^0.19.0", path = "../yew-macro" } thiserror = "1.0" -tokio = { version = "1.15.0", features = ["sync"], default-features = false } futures = { version = "0.3", optional = true } html-escape = { version = "0.2.9", optional = true } implicit-clone = { version = "0.2", features = ["map"] } base64ct = { version = "1.5.0", features = ["std"], optional = true } bincode = { version = "1.3.3", optional = true } serde = { version = "1", features = ["derive"] } -tokio-stream = "0.1.9" +pin-project = { version = "1.0.10", optional = true } [dependencies.web-sys] version = "0.3" @@ -75,6 +74,8 @@ features = [ wasm-bindgen-futures = "0.4" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio = { version = "1.15.0", features = ["rt"], optional = true } +tokio-stream = { version = "0.1.9", optional = true } num_cpus = { version = "1.13", optional = true } tokio-util = { version = "0.7", features = ["rt"], optional = true } once_cell = "1" @@ -95,8 +96,8 @@ features = [ ] [features] -tokio = ["tokio/rt", "dep:num_cpus", "dep:tokio-util"] -ssr = ["dep:futures", "dep:html-escape", "dep:base64ct", "dep:bincode"] +tokio = ["dep:tokio", "dep:num_cpus", "dep:tokio-util", "dep:tokio-stream"] +ssr = ["dep:futures", "dep:html-escape", "dep:base64ct", "dep:bincode", "dep:pin-project"] csr = [] hydration = ["csr", "dep:bincode"] nightly = ["yew-macro/nightly"] diff --git a/packages/yew/src/platform/io.rs b/packages/yew/src/platform/io.rs index 5f4e8125d3e..51a6cdf8805 100644 --- a/packages/yew/src/platform/io.rs +++ b/packages/yew/src/platform/io.rs @@ -7,7 +7,7 @@ use std::borrow::Cow; use futures::stream::Stream; -use crate::platform::sync::mpsc::{self, UnboundedReceiverStream, UnboundedSender}; +use crate::platform::sync::mpsc::{self, UnboundedSender}; // Same as std::io::BufWriter and futures::io::BufWriter. pub(crate) const DEFAULT_BUF_SIZE: usize = 8 * 1024; @@ -21,7 +21,7 @@ pub(crate) struct BufWriter { /// Creates a Buffer pair. pub(crate) fn buffer(capacity: usize) -> (BufWriter, impl Stream) { - let (tx, rx) = mpsc::unbounded_channel::(); + let (tx, rx) = mpsc::unbounded::(); let tx = BufWriter { buf: String::with_capacity(capacity), @@ -29,7 +29,7 @@ pub(crate) fn buffer(capacity: usize) -> (BufWriter, impl Stream) capacity, }; - (tx, UnboundedReceiverStream::new(rx)) + (tx, rx) } // Implementation Notes: diff --git a/packages/yew/src/platform/mod.rs b/packages/yew/src/platform/mod.rs index 0dbab224a3f..413aa40f6c2 100644 --- a/packages/yew/src/platform/mod.rs +++ b/packages/yew/src/platform/mod.rs @@ -32,16 +32,17 @@ use std::future::Future; #[cfg(feature = "ssr")] pub(crate) mod io; -pub mod sync; +#[cfg(feature = "ssr")] +pub(crate) mod sync; #[cfg(not(any(feature = "tokio", target_arch = "wasm32")))] -#[path = "rt_none.rs"] +#[path = "rt_none/mod.rs"] mod imp; #[cfg(all(not(target_arch = "wasm32"), feature = "tokio"))] -#[path = "rt_tokio.rs"] +#[path = "rt_tokio/mod.rs"] mod imp; #[cfg(target_arch = "wasm32")] -#[path = "rt_wasm_bindgen.rs"] +#[path = "rt_wasm_bindgen/mod.rs"] mod imp; /// Spawns a task on current thread. diff --git a/packages/yew/src/platform/rt_none.rs b/packages/yew/src/platform/rt_none/mod.rs similarity index 94% rename from packages/yew/src/platform/rt_none.rs rename to packages/yew/src/platform/rt_none/mod.rs index 3a8dc6a671f..f8ecb83bfde 100644 --- a/packages/yew/src/platform/rt_none.rs +++ b/packages/yew/src/platform/rt_none/mod.rs @@ -1,5 +1,8 @@ use std::future::Future; +#[cfg(feature = "ssr")] +pub(crate) mod sync; + #[inline(always)] pub(super) fn spawn_local(_f: F) where diff --git a/packages/yew/src/platform/rt_none/sync/mod.rs b/packages/yew/src/platform/rt_none/sync/mod.rs new file mode 100644 index 00000000000..44f9c4390e1 --- /dev/null +++ b/packages/yew/src/platform/rt_none/sync/mod.rs @@ -0,0 +1 @@ +pub(crate) mod mpsc; diff --git a/packages/yew/src/platform/rt_none/sync/mpsc/mod.rs b/packages/yew/src/platform/rt_none/sync/mpsc/mod.rs new file mode 100644 index 00000000000..86c45c89899 --- /dev/null +++ b/packages/yew/src/platform/rt_none/sync/mpsc/mod.rs @@ -0,0 +1,48 @@ +use std::marker::PhantomData; +use std::pin::Pin; +use std::task::{Context, Poll}; + +use futures::stream::Stream; + +use crate::platform::sync::mpsc::SendError; + +#[derive(Debug)] +pub(crate) struct UnboundedSender { + inner: PhantomData, +} + +#[derive(Debug)] +pub(crate) struct UnboundedReceiver { + inner: PhantomData, +} + +impl Clone for UnboundedSender { + fn clone(&self) -> Self { + Self { inner: PhantomData } + } +} + +pub(crate) fn unbounded() -> (UnboundedSender, UnboundedReceiver) { + panic!( + r#"No runtime configured for this platform, features that requires task spawning can't be used. + Either compile with `target_arch = "wasm32", or enable the `tokio` feature."# + ); +} + +impl UnboundedSender { + pub fn send(&self, _value: T) -> Result<(), SendError> { + unimplemented!(); + } +} + +impl Stream for UnboundedReceiver { + type Item = T; + + fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + unimplemented!(); + } + + fn size_hint(&self) -> (usize, Option) { + unimplemented!(); + } +} diff --git a/packages/yew/src/platform/rt_tokio.rs b/packages/yew/src/platform/rt_tokio/mod.rs similarity index 93% rename from packages/yew/src/platform/rt_tokio.rs rename to packages/yew/src/platform/rt_tokio/mod.rs index 2516732b635..a6a6f3bab21 100644 --- a/packages/yew/src/platform/rt_tokio.rs +++ b/packages/yew/src/platform/rt_tokio/mod.rs @@ -2,6 +2,9 @@ use std::future::Future; +#[cfg(feature = "ssr")] +pub(crate) mod sync; + #[cfg(feature = "ssr")] pub(super) async fn run_pinned(create_task: F) -> Fut::Output where diff --git a/packages/yew/src/platform/rt_tokio/sync/mod.rs b/packages/yew/src/platform/rt_tokio/sync/mod.rs new file mode 100644 index 00000000000..44f9c4390e1 --- /dev/null +++ b/packages/yew/src/platform/rt_tokio/sync/mod.rs @@ -0,0 +1 @@ +pub(crate) mod mpsc; diff --git a/packages/yew/src/platform/rt_tokio/sync/mpsc/mod.rs b/packages/yew/src/platform/rt_tokio/sync/mpsc/mod.rs new file mode 100644 index 00000000000..774b1957000 --- /dev/null +++ b/packages/yew/src/platform/rt_tokio/sync/mpsc/mod.rs @@ -0,0 +1,63 @@ +use std::pin::Pin; +use std::task::{Context, Poll}; + +use futures::stream::Stream; +use pin_project::pin_project; +use tokio::sync::mpsc as imp; +use tokio_stream::wrappers::UnboundedReceiverStream; + +use crate::platform::sync::mpsc::SendError; + +#[derive(Debug)] +pub(crate) struct UnboundedSender { + inner: imp::UnboundedSender, +} + +#[derive(Debug)] +#[pin_project] +pub(crate) struct UnboundedReceiver { + #[pin] + inner: UnboundedReceiverStream, +} + +impl Clone for UnboundedSender { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + } + } +} + +#[inline] +pub(crate) fn unbounded() -> (UnboundedSender, UnboundedReceiver) { + let (inner_tx, inner_rx) = imp::unbounded_channel(); + + let tx = UnboundedSender { inner: inner_tx }; + let rx = UnboundedReceiver { + inner: UnboundedReceiverStream::new(inner_rx), + }; + + (tx, rx) +} + +impl UnboundedSender { + #[inline] + pub fn send(&self, value: T) -> Result<(), SendError> { + self.inner.send(value).map_err(|e| SendError(e.0)) + } +} + +impl Stream for UnboundedReceiver { + type Item = T; + + #[inline] + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.project(); + this.inner.poll_next(cx) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} diff --git a/packages/yew/src/platform/rt_wasm_bindgen.rs b/packages/yew/src/platform/rt_wasm_bindgen/mod.rs similarity index 87% rename from packages/yew/src/platform/rt_wasm_bindgen.rs rename to packages/yew/src/platform/rt_wasm_bindgen/mod.rs index 0c37b168d5a..ad3f77a163a 100644 --- a/packages/yew/src/platform/rt_wasm_bindgen.rs +++ b/packages/yew/src/platform/rt_wasm_bindgen/mod.rs @@ -1,5 +1,8 @@ use std::future::Future; +#[cfg(feature = "ssr")] +pub(crate) mod sync; + pub(super) use wasm_bindgen_futures::spawn_local; #[cfg(feature = "ssr")] diff --git a/packages/yew/src/platform/rt_wasm_bindgen/sync/mod.rs b/packages/yew/src/platform/rt_wasm_bindgen/sync/mod.rs new file mode 100644 index 00000000000..44f9c4390e1 --- /dev/null +++ b/packages/yew/src/platform/rt_wasm_bindgen/sync/mod.rs @@ -0,0 +1 @@ +pub(crate) mod mpsc; diff --git a/packages/yew/src/platform/rt_wasm_bindgen/sync/mpsc/mod.rs b/packages/yew/src/platform/rt_wasm_bindgen/sync/mpsc/mod.rs new file mode 100644 index 00000000000..9a7498c0637 --- /dev/null +++ b/packages/yew/src/platform/rt_wasm_bindgen/sync/mpsc/mod.rs @@ -0,0 +1,62 @@ +use std::pin::Pin; +use std::task::{Context, Poll}; + +use futures::channel::mpsc as imp; +use futures::stream::Stream; +use pin_project::pin_project; + +use crate::platform::sync::mpsc::SendError; + +#[derive(Debug)] +pub(crate) struct UnboundedSender { + inner: imp::UnboundedSender, +} + +#[derive(Debug)] +#[pin_project] +pub(crate) struct UnboundedReceiver { + #[pin] + inner: imp::UnboundedReceiver, +} + +impl Clone for UnboundedSender { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + } + } +} + +#[inline] +pub(crate) fn unbounded() -> (UnboundedSender, UnboundedReceiver) { + let (inner_tx, inner_rx) = imp::unbounded(); + + let tx = UnboundedSender { inner: inner_tx }; + let rx = UnboundedReceiver { inner: inner_rx }; + + (tx, rx) +} + +impl UnboundedSender { + #[inline] + pub fn send(&self, value: T) -> Result<(), SendError> { + self.inner + .unbounded_send(value) + .map_err(|e| SendError(e.into_inner())) + } +} + +impl Stream for UnboundedReceiver { + type Item = T; + + #[inline] + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.project(); + this.inner.poll_next(cx) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} diff --git a/packages/yew/src/platform/sync/mod.rs b/packages/yew/src/platform/sync/mod.rs index 63c99dec41a..33a48c3ebde 100644 --- a/packages/yew/src/platform/sync/mod.rs +++ b/packages/yew/src/platform/sync/mod.rs @@ -1,5 +1,4 @@ -//! A module that provides task synchronisation primitives. +//! A module that provides task synchronization primitives. -#[doc(inline)] -pub use tokio::sync::oneshot; pub mod mpsc; +pub use futures::channel::oneshot; diff --git a/packages/yew/src/platform/sync/mpsc.rs b/packages/yew/src/platform/sync/mpsc.rs deleted file mode 100644 index de09d342bc9..00000000000 --- a/packages/yew/src/platform/sync/mpsc.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! A multi-producer, single-receiver channel. - -#[doc(inline)] -pub use tokio::sync::mpsc::*; -#[doc(inline)] -pub use tokio_stream::wrappers::{ReceiverStream, UnboundedReceiverStream}; diff --git a/packages/yew/src/platform/sync/mpsc/mod.rs b/packages/yew/src/platform/sync/mpsc/mod.rs new file mode 100644 index 00000000000..6bd0d672595 --- /dev/null +++ b/packages/yew/src/platform/sync/mpsc/mod.rs @@ -0,0 +1,79 @@ +//! A multi-producer, single-receiver channel. + +use std::error::Error; +use std::fmt; +use std::pin::Pin; +use std::task::{Context, Poll}; + +use futures::stream::Stream; +use pin_project::pin_project; + +use crate::platform::imp::sync::mpsc as imp; + +/// The channel has closed when attempting sending. +#[derive(Debug)] +pub struct SendError(pub T); + +impl fmt::Display for SendError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(fmt, "channel closed") + } +} + +impl Error for SendError {} + +/// An unbounded sender for a multi-producer, single receiver channel. +#[derive(Debug)] +pub struct UnboundedSender { + inner: imp::UnboundedSender, +} + +/// An unbounded receiver for a multi-producer, single receiver channel. +#[derive(Debug)] +#[pin_project] +pub struct UnboundedReceiver { + #[pin] + inner: imp::UnboundedReceiver, +} + +impl Clone for UnboundedSender { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + } + } +} + +/// Creates an unbounded channel. +#[inline] +pub fn unbounded() -> (UnboundedSender, UnboundedReceiver) { + let (inner_tx, inner_rx) = imp::unbounded(); + + let tx = UnboundedSender { inner: inner_tx }; + let rx = UnboundedReceiver { inner: inner_rx }; + + (tx, rx) +} + +impl UnboundedSender { + /// Send the value to the receiver. + #[inline] + pub fn send(&self, value: T) -> Result<(), SendError> { + self.inner.send(value) + } +} + +impl Stream for UnboundedReceiver { + type Item = T; + + #[inline] + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.project(); + this.inner.poll_next(cx) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} From 8cc2a57cc5a0c33855fa3e5d9d640bb168531f37 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 26 Jun 2022 02:30:52 +0900 Subject: [PATCH 39/50] Fix workflow. --- .github/workflows/main-checks.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main-checks.yml b/.github/workflows/main-checks.yml index 7e400b4a9b9..57ea4c92fa5 100644 --- a/.github/workflows/main-checks.yml +++ b/.github/workflows/main-checks.yml @@ -28,7 +28,7 @@ jobs: args: --all-targets --features "csr,ssr,hydration,tokio" -- -D warnings - name: Lint feature soundness - run: bash ci/feature-soundness.sh + run: bash ../../ci/feature-soundness.sh working-directory: packages/yew @@ -53,7 +53,7 @@ jobs: args: --all-targets --features "csr,ssr,hydration,tokio" --release -- -D warnings - name: Lint feature soundness - run: bash ci/feature-soundness-release.sh + run: bash ../../ci/feature-soundness-release.sh working-directory: packages/yew spell_check: From 4e0396911ce6e6b66957e6912f57c77235794fb5 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 26 Jun 2022 02:41:37 +0900 Subject: [PATCH 40/50] Restore wrongly removed docs. --- packages/yew/src/virtual_dom/vnode.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/yew/src/virtual_dom/vnode.rs b/packages/yew/src/virtual_dom/vnode.rs index 7c84fbfa0c5..2624e4a772d 100644 --- a/packages/yew/src/virtual_dom/vnode.rs +++ b/packages/yew/src/virtual_dom/vnode.rs @@ -1,3 +1,5 @@ +//! This module contains the implementation of abstract virtual node. + use std::cmp::PartialEq; use std::fmt; use std::iter::FromIterator; From 5f90233668f76a290dc7097531f7ecf4c0d7a0f3 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 26 Jun 2022 03:22:19 +0900 Subject: [PATCH 41/50] Does tokio work? --- packages/yew/Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/yew/Cargo.toml b/packages/yew/Cargo.toml index 1c409f53360..d60b6e82e15 100644 --- a/packages/yew/Cargo.toml +++ b/packages/yew/Cargo.toml @@ -33,6 +33,8 @@ base64ct = { version = "1.5.0", features = ["std"], optional = true } bincode = { version = "1.3.3", optional = true } serde = { version = "1", features = ["derive"] } pin-project = { version = "1.0.10", optional = true } +tokio = { version = "1.19", features = ["sync"] } +tokio-stream = "0.1.9" [dependencies.web-sys] version = "0.3" @@ -74,8 +76,6 @@ features = [ wasm-bindgen-futures = "0.4" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -tokio = { version = "1.15.0", features = ["rt"], optional = true } -tokio-stream = { version = "0.1.9", optional = true } num_cpus = { version = "1.13", optional = true } tokio-util = { version = "0.7", features = ["rt"], optional = true } once_cell = "1" @@ -96,7 +96,7 @@ features = [ ] [features] -tokio = ["dep:tokio", "dep:num_cpus", "dep:tokio-util", "dep:tokio-stream"] +tokio = ["tokio/rt", "dep:num_cpus", "dep:tokio-util"] ssr = ["dep:futures", "dep:html-escape", "dep:base64ct", "dep:bincode", "dep:pin-project"] csr = [] hydration = ["csr", "dep:bincode"] @@ -104,7 +104,7 @@ nightly = ["yew-macro/nightly"] default = [] [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] -tokio = { version = "1.15.0", features = ["full"] } +tokio = { version = "1.19", features = ["io-util", "io-std", "macros", "parking_lot", "rt", "rt-multi-thread", "sync", "time"] } [package.metadata.docs.rs] all-features = true From a6a342302ff731451e989861ba463da86fe68f32 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 26 Jun 2022 03:39:31 +0900 Subject: [PATCH 42/50] Switch back to tokio. --- packages/yew/src/platform/io.rs | 6 +- packages/yew/src/platform/mod.rs | 9 +-- .../platform/{rt_none/mod.rs => rt_none.rs} | 3 - packages/yew/src/platform/rt_none/sync/mod.rs | 1 - .../yew/src/platform/rt_none/sync/mpsc/mod.rs | 48 ----------- .../platform/{rt_tokio/mod.rs => rt_tokio.rs} | 3 - .../yew/src/platform/rt_tokio/sync/mod.rs | 1 - .../src/platform/rt_tokio/sync/mpsc/mod.rs | 63 --------------- .../mod.rs => rt_wasm_bindgen.rs} | 3 - .../src/platform/rt_wasm_bindgen/sync/mod.rs | 1 - .../platform/rt_wasm_bindgen/sync/mpsc/mod.rs | 62 --------------- packages/yew/src/platform/sync/mod.rs | 5 +- packages/yew/src/platform/sync/mpsc.rs | 6 ++ packages/yew/src/platform/sync/mpsc/mod.rs | 79 ------------------- 14 files changed, 16 insertions(+), 274 deletions(-) rename packages/yew/src/platform/{rt_none/mod.rs => rt_none.rs} (94%) delete mode 100644 packages/yew/src/platform/rt_none/sync/mod.rs delete mode 100644 packages/yew/src/platform/rt_none/sync/mpsc/mod.rs rename packages/yew/src/platform/{rt_tokio/mod.rs => rt_tokio.rs} (93%) delete mode 100644 packages/yew/src/platform/rt_tokio/sync/mod.rs delete mode 100644 packages/yew/src/platform/rt_tokio/sync/mpsc/mod.rs rename packages/yew/src/platform/{rt_wasm_bindgen/mod.rs => rt_wasm_bindgen.rs} (87%) delete mode 100644 packages/yew/src/platform/rt_wasm_bindgen/sync/mod.rs delete mode 100644 packages/yew/src/platform/rt_wasm_bindgen/sync/mpsc/mod.rs create mode 100644 packages/yew/src/platform/sync/mpsc.rs delete mode 100644 packages/yew/src/platform/sync/mpsc/mod.rs diff --git a/packages/yew/src/platform/io.rs b/packages/yew/src/platform/io.rs index 51a6cdf8805..5f4e8125d3e 100644 --- a/packages/yew/src/platform/io.rs +++ b/packages/yew/src/platform/io.rs @@ -7,7 +7,7 @@ use std::borrow::Cow; use futures::stream::Stream; -use crate::platform::sync::mpsc::{self, UnboundedSender}; +use crate::platform::sync::mpsc::{self, UnboundedReceiverStream, UnboundedSender}; // Same as std::io::BufWriter and futures::io::BufWriter. pub(crate) const DEFAULT_BUF_SIZE: usize = 8 * 1024; @@ -21,7 +21,7 @@ pub(crate) struct BufWriter { /// Creates a Buffer pair. pub(crate) fn buffer(capacity: usize) -> (BufWriter, impl Stream) { - let (tx, rx) = mpsc::unbounded::(); + let (tx, rx) = mpsc::unbounded_channel::(); let tx = BufWriter { buf: String::with_capacity(capacity), @@ -29,7 +29,7 @@ pub(crate) fn buffer(capacity: usize) -> (BufWriter, impl Stream) capacity, }; - (tx, rx) + (tx, UnboundedReceiverStream::new(rx)) } // Implementation Notes: diff --git a/packages/yew/src/platform/mod.rs b/packages/yew/src/platform/mod.rs index 413aa40f6c2..0dbab224a3f 100644 --- a/packages/yew/src/platform/mod.rs +++ b/packages/yew/src/platform/mod.rs @@ -32,17 +32,16 @@ use std::future::Future; #[cfg(feature = "ssr")] pub(crate) mod io; -#[cfg(feature = "ssr")] -pub(crate) mod sync; +pub mod sync; #[cfg(not(any(feature = "tokio", target_arch = "wasm32")))] -#[path = "rt_none/mod.rs"] +#[path = "rt_none.rs"] mod imp; #[cfg(all(not(target_arch = "wasm32"), feature = "tokio"))] -#[path = "rt_tokio/mod.rs"] +#[path = "rt_tokio.rs"] mod imp; #[cfg(target_arch = "wasm32")] -#[path = "rt_wasm_bindgen/mod.rs"] +#[path = "rt_wasm_bindgen.rs"] mod imp; /// Spawns a task on current thread. diff --git a/packages/yew/src/platform/rt_none/mod.rs b/packages/yew/src/platform/rt_none.rs similarity index 94% rename from packages/yew/src/platform/rt_none/mod.rs rename to packages/yew/src/platform/rt_none.rs index f8ecb83bfde..3a8dc6a671f 100644 --- a/packages/yew/src/platform/rt_none/mod.rs +++ b/packages/yew/src/platform/rt_none.rs @@ -1,8 +1,5 @@ use std::future::Future; -#[cfg(feature = "ssr")] -pub(crate) mod sync; - #[inline(always)] pub(super) fn spawn_local(_f: F) where diff --git a/packages/yew/src/platform/rt_none/sync/mod.rs b/packages/yew/src/platform/rt_none/sync/mod.rs deleted file mode 100644 index 44f9c4390e1..00000000000 --- a/packages/yew/src/platform/rt_none/sync/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub(crate) mod mpsc; diff --git a/packages/yew/src/platform/rt_none/sync/mpsc/mod.rs b/packages/yew/src/platform/rt_none/sync/mpsc/mod.rs deleted file mode 100644 index 86c45c89899..00000000000 --- a/packages/yew/src/platform/rt_none/sync/mpsc/mod.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::marker::PhantomData; -use std::pin::Pin; -use std::task::{Context, Poll}; - -use futures::stream::Stream; - -use crate::platform::sync::mpsc::SendError; - -#[derive(Debug)] -pub(crate) struct UnboundedSender { - inner: PhantomData, -} - -#[derive(Debug)] -pub(crate) struct UnboundedReceiver { - inner: PhantomData, -} - -impl Clone for UnboundedSender { - fn clone(&self) -> Self { - Self { inner: PhantomData } - } -} - -pub(crate) fn unbounded() -> (UnboundedSender, UnboundedReceiver) { - panic!( - r#"No runtime configured for this platform, features that requires task spawning can't be used. - Either compile with `target_arch = "wasm32", or enable the `tokio` feature."# - ); -} - -impl UnboundedSender { - pub fn send(&self, _value: T) -> Result<(), SendError> { - unimplemented!(); - } -} - -impl Stream for UnboundedReceiver { - type Item = T; - - fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { - unimplemented!(); - } - - fn size_hint(&self) -> (usize, Option) { - unimplemented!(); - } -} diff --git a/packages/yew/src/platform/rt_tokio/mod.rs b/packages/yew/src/platform/rt_tokio.rs similarity index 93% rename from packages/yew/src/platform/rt_tokio/mod.rs rename to packages/yew/src/platform/rt_tokio.rs index a6a6f3bab21..2516732b635 100644 --- a/packages/yew/src/platform/rt_tokio/mod.rs +++ b/packages/yew/src/platform/rt_tokio.rs @@ -2,9 +2,6 @@ use std::future::Future; -#[cfg(feature = "ssr")] -pub(crate) mod sync; - #[cfg(feature = "ssr")] pub(super) async fn run_pinned(create_task: F) -> Fut::Output where diff --git a/packages/yew/src/platform/rt_tokio/sync/mod.rs b/packages/yew/src/platform/rt_tokio/sync/mod.rs deleted file mode 100644 index 44f9c4390e1..00000000000 --- a/packages/yew/src/platform/rt_tokio/sync/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub(crate) mod mpsc; diff --git a/packages/yew/src/platform/rt_tokio/sync/mpsc/mod.rs b/packages/yew/src/platform/rt_tokio/sync/mpsc/mod.rs deleted file mode 100644 index 774b1957000..00000000000 --- a/packages/yew/src/platform/rt_tokio/sync/mpsc/mod.rs +++ /dev/null @@ -1,63 +0,0 @@ -use std::pin::Pin; -use std::task::{Context, Poll}; - -use futures::stream::Stream; -use pin_project::pin_project; -use tokio::sync::mpsc as imp; -use tokio_stream::wrappers::UnboundedReceiverStream; - -use crate::platform::sync::mpsc::SendError; - -#[derive(Debug)] -pub(crate) struct UnboundedSender { - inner: imp::UnboundedSender, -} - -#[derive(Debug)] -#[pin_project] -pub(crate) struct UnboundedReceiver { - #[pin] - inner: UnboundedReceiverStream, -} - -impl Clone for UnboundedSender { - fn clone(&self) -> Self { - Self { - inner: self.inner.clone(), - } - } -} - -#[inline] -pub(crate) fn unbounded() -> (UnboundedSender, UnboundedReceiver) { - let (inner_tx, inner_rx) = imp::unbounded_channel(); - - let tx = UnboundedSender { inner: inner_tx }; - let rx = UnboundedReceiver { - inner: UnboundedReceiverStream::new(inner_rx), - }; - - (tx, rx) -} - -impl UnboundedSender { - #[inline] - pub fn send(&self, value: T) -> Result<(), SendError> { - self.inner.send(value).map_err(|e| SendError(e.0)) - } -} - -impl Stream for UnboundedReceiver { - type Item = T; - - #[inline] - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let this = self.project(); - this.inner.poll_next(cx) - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.inner.size_hint() - } -} diff --git a/packages/yew/src/platform/rt_wasm_bindgen/mod.rs b/packages/yew/src/platform/rt_wasm_bindgen.rs similarity index 87% rename from packages/yew/src/platform/rt_wasm_bindgen/mod.rs rename to packages/yew/src/platform/rt_wasm_bindgen.rs index ad3f77a163a..0c37b168d5a 100644 --- a/packages/yew/src/platform/rt_wasm_bindgen/mod.rs +++ b/packages/yew/src/platform/rt_wasm_bindgen.rs @@ -1,8 +1,5 @@ use std::future::Future; -#[cfg(feature = "ssr")] -pub(crate) mod sync; - pub(super) use wasm_bindgen_futures::spawn_local; #[cfg(feature = "ssr")] diff --git a/packages/yew/src/platform/rt_wasm_bindgen/sync/mod.rs b/packages/yew/src/platform/rt_wasm_bindgen/sync/mod.rs deleted file mode 100644 index 44f9c4390e1..00000000000 --- a/packages/yew/src/platform/rt_wasm_bindgen/sync/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub(crate) mod mpsc; diff --git a/packages/yew/src/platform/rt_wasm_bindgen/sync/mpsc/mod.rs b/packages/yew/src/platform/rt_wasm_bindgen/sync/mpsc/mod.rs deleted file mode 100644 index 9a7498c0637..00000000000 --- a/packages/yew/src/platform/rt_wasm_bindgen/sync/mpsc/mod.rs +++ /dev/null @@ -1,62 +0,0 @@ -use std::pin::Pin; -use std::task::{Context, Poll}; - -use futures::channel::mpsc as imp; -use futures::stream::Stream; -use pin_project::pin_project; - -use crate::platform::sync::mpsc::SendError; - -#[derive(Debug)] -pub(crate) struct UnboundedSender { - inner: imp::UnboundedSender, -} - -#[derive(Debug)] -#[pin_project] -pub(crate) struct UnboundedReceiver { - #[pin] - inner: imp::UnboundedReceiver, -} - -impl Clone for UnboundedSender { - fn clone(&self) -> Self { - Self { - inner: self.inner.clone(), - } - } -} - -#[inline] -pub(crate) fn unbounded() -> (UnboundedSender, UnboundedReceiver) { - let (inner_tx, inner_rx) = imp::unbounded(); - - let tx = UnboundedSender { inner: inner_tx }; - let rx = UnboundedReceiver { inner: inner_rx }; - - (tx, rx) -} - -impl UnboundedSender { - #[inline] - pub fn send(&self, value: T) -> Result<(), SendError> { - self.inner - .unbounded_send(value) - .map_err(|e| SendError(e.into_inner())) - } -} - -impl Stream for UnboundedReceiver { - type Item = T; - - #[inline] - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let this = self.project(); - this.inner.poll_next(cx) - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.inner.size_hint() - } -} diff --git a/packages/yew/src/platform/sync/mod.rs b/packages/yew/src/platform/sync/mod.rs index 33a48c3ebde..63c99dec41a 100644 --- a/packages/yew/src/platform/sync/mod.rs +++ b/packages/yew/src/platform/sync/mod.rs @@ -1,4 +1,5 @@ -//! A module that provides task synchronization primitives. +//! A module that provides task synchronisation primitives. +#[doc(inline)] +pub use tokio::sync::oneshot; pub mod mpsc; -pub use futures::channel::oneshot; diff --git a/packages/yew/src/platform/sync/mpsc.rs b/packages/yew/src/platform/sync/mpsc.rs new file mode 100644 index 00000000000..de09d342bc9 --- /dev/null +++ b/packages/yew/src/platform/sync/mpsc.rs @@ -0,0 +1,6 @@ +//! A multi-producer, single-receiver channel. + +#[doc(inline)] +pub use tokio::sync::mpsc::*; +#[doc(inline)] +pub use tokio_stream::wrappers::{ReceiverStream, UnboundedReceiverStream}; diff --git a/packages/yew/src/platform/sync/mpsc/mod.rs b/packages/yew/src/platform/sync/mpsc/mod.rs deleted file mode 100644 index 6bd0d672595..00000000000 --- a/packages/yew/src/platform/sync/mpsc/mod.rs +++ /dev/null @@ -1,79 +0,0 @@ -//! A multi-producer, single-receiver channel. - -use std::error::Error; -use std::fmt; -use std::pin::Pin; -use std::task::{Context, Poll}; - -use futures::stream::Stream; -use pin_project::pin_project; - -use crate::platform::imp::sync::mpsc as imp; - -/// The channel has closed when attempting sending. -#[derive(Debug)] -pub struct SendError(pub T); - -impl fmt::Display for SendError { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(fmt, "channel closed") - } -} - -impl Error for SendError {} - -/// An unbounded sender for a multi-producer, single receiver channel. -#[derive(Debug)] -pub struct UnboundedSender { - inner: imp::UnboundedSender, -} - -/// An unbounded receiver for a multi-producer, single receiver channel. -#[derive(Debug)] -#[pin_project] -pub struct UnboundedReceiver { - #[pin] - inner: imp::UnboundedReceiver, -} - -impl Clone for UnboundedSender { - fn clone(&self) -> Self { - Self { - inner: self.inner.clone(), - } - } -} - -/// Creates an unbounded channel. -#[inline] -pub fn unbounded() -> (UnboundedSender, UnboundedReceiver) { - let (inner_tx, inner_rx) = imp::unbounded(); - - let tx = UnboundedSender { inner: inner_tx }; - let rx = UnboundedReceiver { inner: inner_rx }; - - (tx, rx) -} - -impl UnboundedSender { - /// Send the value to the receiver. - #[inline] - pub fn send(&self, value: T) -> Result<(), SendError> { - self.inner.send(value) - } -} - -impl Stream for UnboundedReceiver { - type Item = T; - - #[inline] - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let this = self.project(); - this.inner.poll_next(cx) - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.inner.size_hint() - } -} From ecbcacaa9c90cc675f9f77140e7517dbe1de8caf Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 26 Jun 2022 03:46:52 +0900 Subject: [PATCH 43/50] Remove pin-project. --- packages/yew/Cargo.toml | 3 +-- packages/yew/src/platform/io.rs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/yew/Cargo.toml b/packages/yew/Cargo.toml index d60b6e82e15..c8145fcd04b 100644 --- a/packages/yew/Cargo.toml +++ b/packages/yew/Cargo.toml @@ -32,7 +32,6 @@ implicit-clone = { version = "0.2", features = ["map"] } base64ct = { version = "1.5.0", features = ["std"], optional = true } bincode = { version = "1.3.3", optional = true } serde = { version = "1", features = ["derive"] } -pin-project = { version = "1.0.10", optional = true } tokio = { version = "1.19", features = ["sync"] } tokio-stream = "0.1.9" @@ -97,7 +96,7 @@ features = [ [features] tokio = ["tokio/rt", "dep:num_cpus", "dep:tokio-util"] -ssr = ["dep:futures", "dep:html-escape", "dep:base64ct", "dep:bincode", "dep:pin-project"] +ssr = ["dep:futures", "dep:html-escape", "dep:base64ct", "dep:bincode"] csr = [] hydration = ["csr", "dep:bincode"] nightly = ["yew-macro/nightly"] diff --git a/packages/yew/src/platform/io.rs b/packages/yew/src/platform/io.rs index 5f4e8125d3e..dd60ad95bd7 100644 --- a/packages/yew/src/platform/io.rs +++ b/packages/yew/src/platform/io.rs @@ -1,6 +1,6 @@ //! This module contains types for I/O functionality. -// This module remains private until impl trait type alias becomes available so +// This module should remain private until impl trait type alias becomes available so // `BufReader` can be produced with an existential type. use std::borrow::Cow; @@ -34,7 +34,7 @@ pub(crate) fn buffer(capacity: usize) -> (BufWriter, impl Stream) // Implementation Notes: // -// When jemalloc is used and a reasonable buffer is chosen, +// When jemalloc is used and a reasonable buffer length is chosen, // performance of this buffer is related to the number of allocations // instead of the amount of memory that is allocated. // From 01494ce5971969e2946754d786026c4ba32f926f Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 26 Jun 2022 04:23:24 +0900 Subject: [PATCH 44/50] Use cargo resolver 2. --- Cargo.toml | 1 + packages/yew/Cargo.toml | 4 ++-- packages/yew/src/platform/rt_tokio.rs | 2 -- packages/yew/src/platform/rt_wasm_bindgen.rs | 1 + 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bc3ed7f88e0..2b716ffd3b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,3 +45,4 @@ members = [ "tools/process-benchmark-results", "tools/website-test", ] +resolver = "2" diff --git a/packages/yew/Cargo.toml b/packages/yew/Cargo.toml index c8145fcd04b..bb1a2a98311 100644 --- a/packages/yew/Cargo.toml +++ b/packages/yew/Cargo.toml @@ -33,7 +33,7 @@ base64ct = { version = "1.5.0", features = ["std"], optional = true } bincode = { version = "1.3.3", optional = true } serde = { version = "1", features = ["derive"] } tokio = { version = "1.19", features = ["sync"] } -tokio-stream = "0.1.9" +tokio-stream = { version = "0.1.9", features = ["sync"] } [dependencies.web-sys] version = "0.3" @@ -103,7 +103,7 @@ nightly = ["yew-macro/nightly"] default = [] [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] -tokio = { version = "1.19", features = ["io-util", "io-std", "macros", "parking_lot", "rt", "rt-multi-thread", "sync", "time"] } +tokio = { version = "1.19", features = ["full"] } [package.metadata.docs.rs] all-features = true diff --git a/packages/yew/src/platform/rt_tokio.rs b/packages/yew/src/platform/rt_tokio.rs index 2516732b635..ee8e2251a71 100644 --- a/packages/yew/src/platform/rt_tokio.rs +++ b/packages/yew/src/platform/rt_tokio.rs @@ -1,5 +1,3 @@ -//! Tokio Implementation - use std::future::Future; #[cfg(feature = "ssr")] diff --git a/packages/yew/src/platform/rt_wasm_bindgen.rs b/packages/yew/src/platform/rt_wasm_bindgen.rs index 0c37b168d5a..8307bfd4350 100644 --- a/packages/yew/src/platform/rt_wasm_bindgen.rs +++ b/packages/yew/src/platform/rt_wasm_bindgen.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "ssr")] use std::future::Future; pub(super) use wasm_bindgen_futures::spawn_local; From 466bed48634864cb91abf87a706930fb3507923b Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 26 Jun 2022 17:51:22 +0900 Subject: [PATCH 45/50] Add panic notice. --- packages/yew/src/platform/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/yew/src/platform/mod.rs b/packages/yew/src/platform/mod.rs index 0dbab224a3f..7b9521f2064 100644 --- a/packages/yew/src/platform/mod.rs +++ b/packages/yew/src/platform/mod.rs @@ -45,6 +45,10 @@ mod imp; mod imp; /// Spawns a task on current thread. +/// +/// # Panics +/// +/// This function will panic when not being executed from within a Yew Application. #[inline(always)] pub fn spawn_local(f: F) where From 4855da4949ddf07221d0de7f5928c0bd0734f54c Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Mon, 27 Jun 2022 01:50:17 +0900 Subject: [PATCH 46/50] Update documentation. --- packages/yew/src/platform/mod.rs | 53 ++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/packages/yew/src/platform/mod.rs b/packages/yew/src/platform/mod.rs index 7b9521f2064..1a152d2e573 100644 --- a/packages/yew/src/platform/mod.rs +++ b/packages/yew/src/platform/mod.rs @@ -1,31 +1,44 @@ -//! This module provides io compatibility over browser tasks and other asyncio runtimes (e.g.: -//! tokio). +//! Compatibility between JavaScript Runtime and Native Runtimes. //! -//! Yew implements a single-threaded runtime that executes `!Send` futures. When your application -//! starts with `yew::Renderer` or is rendered by `yew::ServerRenderer`, it is executed within the -//! Yew runtime. The renderer will select a worker thread from the internal worker -//! pool of Yew runtime. All tasks spawned with `spawn_local` will run on the same worker thread as -//! the rendering thread the renderer has selected. When the renderer runs in a WebAssembly target, -//! all tasks will be scheduled on the main thread. +//! When designing components and libraries that works on both WebAssembly targets backed by +//! JavaScript Runtime and non-WebAssembly targets with Native Runtimes. Developers usually face +//! challenges that requires applying multiple feature flags throughout their application: //! -//! Yew runtime is implemented with native runtimes depending on the target platform and can use +//! 1. Select I/O and timers that works with the target runtime. +//! 2. Native Runtimes usually require `Send` futures and WebAssembly usually use `!Send` +//! primitives for better performance during Client-side Rendering. +//! +//! To alleviate these issues, Yew implements a single-threaded runtime that executes `?Send` +//! (`Send` or `!Send`) futures. When your application starts with `yew::Renderer` or is rendered by +//! `yew::ServerRenderer`, it is executed within the Yew runtime. On systems with multi-threading +//! support, it spawns multiple independent runtimes in a worker pool proportional to the CPU +//! core number. The renderer will randomly select a worker thread from the internal pool. All tasks +//! spawned with `spawn_local` in the application will run on the same thread as the +//! rendering thread the renderer has selected. When the renderer runs in a WebAssembly target, all +//! tasks will be scheduled on the main thread. +//! +//! This runtime is designed in favour of IO-bounded workload with similar runtime cost. It produces +//! better performance by pinning tasks to a single worker thread. However, this means that if a +//! worker thread is back-logged, other threads will not be able to "help" by running tasks +//! scheduled on the busy thread. When you have a CPU-bounded task where CPU time is significantly +//! more expensive than rendering tasks, it should be spawned with a dedicated thread or +//! `yew-agent` and communicates with the application using channels or agent bridges. +//! +//! # Runtime Backend +//! +//! Yew runtime is implemented with different runtimes depending on the target platform and can use //! all features (timers / IO / task synchronisation) from the selected native runtime: //! //! - `wasm-bindgen-futures` (WebAssembly targets) //! - `tokio` (non-WebAssembly targets) //! -//! Yew runtime alleviates the implementation requirement of `Send` futures when running with -//! multi-threaded runtimes like `tokio` and `!Send` futures on WebAssembly platforms and produces -//! good performance when the workload is IO-bounded and have similar runtime cost. When you have an -//! expensive CPU-bounded task, it should be spawned with a `Send`-aware spawning mechanism provided -//! by the native runtime, `std::thread::spawn` or `yew-agent` and communicates with the application -//! using channels or agent bridges. +//! # Compatibility with other async runtimes //! -//! Yew's ServerRenderer can also be executed in applications using the `async-std` runtime. -//! Rendering tasks will enter Yew runtime and be executed with `tokio`. When the rendering task -//! finishes, the result is returned to the `async-std` runtime. This process is transparent to the -//! future that executes the renderer. The Yew application still needs to use `tokio`'s timer, IO -//! and task synchronisation primitives. +//! Yew's ServerRenderer can also be executed in applications using other async runtimes(e.g.: +//! `async-std`). Rendering tasks will enter Yew runtime and be executed with `tokio`. When the +//! rendering task finishes, the result is returned to the original runtime. This process is +//! transparent to the future that executes the renderer. The Yew application still needs to use +//! `tokio`'s timer, IO and task synchronisation primitives. use std::future::Future; From 0d477f4e0f94709118fcb284b843bd37cac23cfc Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Tue, 28 Jun 2022 19:47:39 +0900 Subject: [PATCH 47/50] Properties does not have to be send. --- examples/function_router/src/app.rs | 2 +- .../ssr_router/src/bin/ssr_router_server.rs | 6 ++- packages/yew/src/server_renderer.rs | 38 ++++++++++++++----- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/examples/function_router/src/app.rs b/examples/function_router/src/app.rs index 23a2f759036..87408e8123b 100644 --- a/examples/function_router/src/app.rs +++ b/examples/function_router/src/app.rs @@ -54,7 +54,7 @@ pub fn App() -> Html { #[derive(Properties, PartialEq, Debug)] pub struct ServerAppProps { - pub url: String, + pub url: AttrValue, pub queries: HashMap, } diff --git a/examples/ssr_router/src/bin/ssr_router_server.rs b/examples/ssr_router/src/bin/ssr_router_server.rs index ce77e456510..5aa8c1c86ea 100644 --- a/examples/ssr_router/src/bin/ssr_router_server.rs +++ b/examples/ssr_router/src/bin/ssr_router_server.rs @@ -31,8 +31,10 @@ async fn render( ) -> impl IntoResponse { let url = url.uri().to_string(); - let server_app_props = ServerAppProps { url, queries }; - let renderer = yew::ServerRenderer::::with_props(server_app_props); + let renderer = yew::ServerRenderer::::with_props(move || ServerAppProps { + url: url.into(), + queries, + }); StreamBody::new( stream::once(async move { index_html_before }) diff --git a/packages/yew/src/server_renderer.rs b/packages/yew/src/server_renderer.rs index 4dbd638ab60..45c6c79c2e9 100644 --- a/packages/yew/src/server_renderer.rs +++ b/packages/yew/src/server_renderer.rs @@ -1,3 +1,5 @@ +use std::fmt; + use futures::stream::{Stream, StreamExt}; use crate::html::{BaseComponent, Scope}; @@ -111,31 +113,38 @@ where /// /// See [`yew::platform`] for more information. #[cfg_attr(documenting, doc(cfg(feature = "ssr")))] -#[derive(Debug)] pub struct ServerRenderer where COMP: BaseComponent, - COMP::Properties: Send, { - props: COMP::Properties, + create_props: Box COMP::Properties>, hydratable: bool, capacity: usize, } +impl fmt::Debug for ServerRenderer +where + COMP: BaseComponent, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("ServerRenderer<_>") + } +} + impl Default for ServerRenderer where COMP: BaseComponent, - COMP::Properties: Default + Send, + COMP::Properties: Default, { fn default() -> Self { - Self::with_props(COMP::Properties::default()) + Self::with_props(Default::default) } } impl ServerRenderer where COMP: BaseComponent, - COMP::Properties: Default + Send, + COMP::Properties: Default, { /// Creates a [ServerRenderer] with default properties. pub fn new() -> Self { @@ -146,12 +155,19 @@ where impl ServerRenderer where COMP: BaseComponent, - COMP::Properties: Send, { /// Creates a [ServerRenderer] with custom properties. - pub fn with_props(props: COMP::Properties) -> Self { + /// + /// # Note + /// + /// The properties does not have to implement `Send`. + /// However, the function to create properties needs to be `Send`. + pub fn with_props(create_props: F) -> Self + where + F: 'static + Send + FnOnce() -> COMP::Properties, + { Self { - props, + create_props: Box::new(create_props), hydratable: true, capacity: DEFAULT_BUF_SIZE, } @@ -205,11 +221,13 @@ where // We use run_pinned to switch to our runtime. run_pinned(move || async move { let Self { - props, + create_props, hydratable, capacity, } = self; + let props = create_props(); + LocalServerRenderer::::with_props(props) .hydratable(hydratable) .capacity(capacity) From 9c366398c06720d8be41c642f61caf52b5da840c Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Wed, 29 Jun 2022 20:20:30 +0900 Subject: [PATCH 48/50] Fix capacity checking as pointed in the review. --- packages/yew/src/platform/io.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/yew/src/platform/io.rs b/packages/yew/src/platform/io.rs index dd60ad95bd7..ff696c17c6c 100644 --- a/packages/yew/src/platform/io.rs +++ b/packages/yew/src/platform/io.rs @@ -64,19 +64,20 @@ impl BufWriter { self.buf.reserve(self.capacity); } + /// Returns `True` if the internal buffer has capacity to fit a string of certain length. + #[inline] + fn has_capacity_of(&self, next_part_len: usize) -> bool { + self.buf.capacity() >= self.buf.len() + next_part_len + } + /// Writes a string into the buffer, optionally drains the buffer. pub fn write(&mut self, s: Cow<'_, str>) { - if self.buf.capacity() < s.len() { + if !self.has_capacity_of(s.len()) { // There isn't enough capacity, we drain the buffer. self.drain(); } - // It's important to check self.buf.capacity() >= s.len(): - // - // 1. self.buf.reserve() may choose to over reserve than capacity. - // 2. When self.buf.capacity() == s.len(), the previous buffer is not drained. So it needs - // to push onto the buffer instead of sending. - if self.buf.capacity() >= s.len() { + if self.has_capacity_of(s.len()) { // The next part is going to fit into the buffer, we push it onto the buffer. self.buf.push_str(&s); } else { From ba236717c4e863cc52b540b67e3e4eda5c370519 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Wed, 29 Jun 2022 20:24:07 +0900 Subject: [PATCH 49/50] Implementation order. --- packages/yew/src/platform/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/yew/src/platform/mod.rs b/packages/yew/src/platform/mod.rs index 1a152d2e573..48b35ddd4b0 100644 --- a/packages/yew/src/platform/mod.rs +++ b/packages/yew/src/platform/mod.rs @@ -47,14 +47,14 @@ pub(crate) mod io; pub mod sync; -#[cfg(not(any(feature = "tokio", target_arch = "wasm32")))] -#[path = "rt_none.rs"] +#[cfg(target_arch = "wasm32")] +#[path = "rt_wasm_bindgen.rs"] mod imp; #[cfg(all(not(target_arch = "wasm32"), feature = "tokio"))] #[path = "rt_tokio.rs"] mod imp; -#[cfg(target_arch = "wasm32")] -#[path = "rt_wasm_bindgen.rs"] +#[cfg(all(not(target_arch = "wasm32"), not(feature = "tokio")))] +#[path = "rt_none.rs"] mod imp; /// Spawns a task on current thread. From 8462ec4a751fe425c1348392e6c83a13ff72f91c Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Wed, 29 Jun 2022 21:03:58 +0900 Subject: [PATCH 50/50] Update note. --- packages/yew/src/platform/io.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/yew/src/platform/io.rs b/packages/yew/src/platform/io.rs index ff696c17c6c..14e0e11d56e 100644 --- a/packages/yew/src/platform/io.rs +++ b/packages/yew/src/platform/io.rs @@ -83,10 +83,10 @@ impl BufWriter { } else { // if the next part is more than buffer size, we send the next part. - // We don't need to drain the buffer here as the self.buf.capacity() only changes if - // the buffer was drained. If the buffer capacity didn't change, then it means - // self.buf.capacity() > s.len() which will be guaranteed to be matched by - // self.buf.capacity() >= s.len(). + // We don't need to drain the buffer here as the result of self.has_capacity_of() only + // changes if the buffer was drained. If the buffer capacity didn't change, + // then it means self.has_capacity_of() has returned true the first time which will be + // guaranteed to be matched by the left hand side of this implementation. let _ = self.tx.send(s.into_owned()); } }